Completed
Pull Request — master (#181)
by Roman
02:54
created

DataGrid::addColumnLink()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 12
Code Lines 7

Duplication

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

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
333
334
		/**
335
		 * Invoke possible events
336
		 */
337
		$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...
338
339
		/**
340
		 * Prepare data for rendering (datagrid may render just one item)
341
		 */
342
		$rows = [];
343
344
		if (!empty($this->redraw_item)) {
345
			$items = $this->dataModel->filterRow($this->redraw_item);
346
		} else {
347
			$items = Nette\Utils\Callback::invokeArgs(
348
				[$this->dataModel, 'filterData'],
349
				[
350
					$this->getPaginator(),
351
					new Sorting($this->sort, $this->sort_callback),
352
					$this->assableFilters()
353
				]
354
			);
355
		}
356
357
		$callback = $this->rowCallback ?: NULL;
358
359
		foreach ($items as $item) {
360
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
361
362
			if ($callback) {
363
				$callback($item, $row->getControl());
364
			}
365
		}
366
367
		if ($this->isTreeView()) {
368
			$this->template->add('tree_view_has_children_column', $this->tree_view_has_children_column);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
369
		}
370
371
		$this->template->add('rows', $rows);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
372
373
		$this->template->add('columns', $this->getColumns());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
374
		$this->template->add('actions', $this->actions);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
375
		$this->template->add('exports', $this->exports);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
376
		$this->template->add('filters', $this->filters);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
377
378
		$this->template->add('filter_active', $this->isFilterActive());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
379
		$this->template->add('original_template', $this->getOriginalTemplateFile());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
380
		$this->template->add('icon_prefix', static::$icon_prefix);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
381
		$this->template->add('items_detail', $this->items_detail);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
382
		$this->template->add('columns_visibility', $this->columns_visibility);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
383
384
		$this->template->add('inlineEdit', $this->inlineEdit);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
385
386
		/**
387
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
388
		 */
389
		$this->template->add('filter', $this['filter']);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
390
391
		/**
392
		 * Set template file and render it
393
		 */
394
		$this->template->setFile($this->getTemplateFile());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
395
		$this->template->render();
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
396
	}
397
398
399
	/********************************************************************************
400
	 *                                 ROW CALLBACK                                 *
401
	 ********************************************************************************/
402
403
404
	/**
405
	 * Each row can be modified with user callback
406
	 * @param  callable  $callback
407
	 * @return static
408
	 */
409
	public function setRowCallback(callable $callback)
410
	{
411
		$this->rowCallback = $callback;
412
413
		return $this;
414
	}
415
416
417
	/********************************************************************************
418
	 *                                 DATA SOURCE                                  *
419
	 ********************************************************************************/
420
421
422
	/**
423
	 * By default ID, you can change that
424
	 * @param string $primary_key
425
	 * @return static
426
	 */
427
	public function setPrimaryKey($primary_key)
428
	{
429
		if ($this->dataModel instanceof DataModel) {
430
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
431
		}
432
433
		$this->primary_key = $primary_key;
434
435
		return $this;
436
	}
437
438
439
	/**
440
	 * Set Grid data source
441
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
442
	 * @return static
443
	 */
444
	public function setDataSource($source)
445
	{
446
		$this->dataModel = new DataModel($source, $this->primary_key);
447
448
		return $this;
449
	}
450
451
452
	/********************************************************************************
453
	 *                                  TEMPLATING                                  *
454
	 ********************************************************************************/
455
456
457
	/**
458
	 * Set custom template file to render
459
	 * @param string $template_file
460
	 * @return static
461
	 */
462
	public function setTemplateFile($template_file)
463
	{
464
		$this->template_file = $template_file;
465
466
		return $this;
467
	}
468
469
470
	/**
471
	 * Get DataGrid template file
472
	 * @return string
473
	 * @return static
474
	 */
475
	public function getTemplateFile()
476
	{
477
		return $this->template_file ?: $this->getOriginalTemplateFile();
478
	}
479
480
481
	/**
482
	 * Get DataGrid original template file
483
	 * @return string
484
	 */
485
	public function getOriginalTemplateFile()
486
	{
487
		return __DIR__.'/templates/datagrid.latte';
488
	}
489
490
491
	/**
492
	 * Tell datagrid wheteher to use or not happy components
493
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
494
	 * @return void|bool
495
	 */
496
	public function useHappyComponents($use = NULL)
497
	{
498
		if (NULL === $use) {
499
			return $this->use_happy_components;
500
		}
501
502
		$this->use_happy_components = (bool) $use;
503
	}
504
505
506
	/********************************************************************************
507
	 *                                   SORTING                                    *
508
	 ********************************************************************************/
509
510
511
	/**
512
	 * Set default sorting
513
	 * @param array $sort
514
	 * @return static
515
	 */
516
	public function setDefaultSort($sort)
517
	{
518
		if (is_string($sort)) {
519
			$sort = [$sort => 'ASC'];
520
		} else {
521
			$sort = (array) $sort;
522
		}
523
524
		$this->default_sort = $sort;
525
526
		return $this;
527
	}
528
529
530
	/**
531
	 * User may set default sorting, apply it
532
	 * @return void
533
	 */
534
	public function findDefaultSort()
535
	{
536
		if (!empty($this->sort)) {
537
			return;
538
		}
539
540
		if (!empty($this->default_sort)) {
541
			$this->sort = $this->default_sort;
542
		}
543
544
		$this->saveSessionData('_grid_sort', $this->sort);
545
	}
546
547
548
	/**
549
	 * Set grido to be sortable
550
	 * @param bool $sortable
551
	 * @return static
552
	 */
553
	public function setSortable($sortable = TRUE)
554
	{
555
		if ($this->getItemsDetail()) {
556
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
557
		}
558
559
		$this->sortable = (bool) $sortable;
560
561
		return $this;
562
	}
563
564
565
	/**
566
	 * Set sortable handle
567
	 * @param string $handler
568
	 * @return static
569
	 */
570
	public function setSortableHandler($handler = 'sort!')
571
	{
572
		$this->sortable_handler = (string) $handler;
573
574
		return $this;
575
	}
576
577
578
	/**
579
	 * Tell whether DataGrid is sortable
580
	 * @return bool
581
	 */
582
	public function isSortable()
583
	{
584
		return $this->sortable;
585
	}
586
587
	/**
588
	 * Return sortable handle name
589
	 * @return string
590
	 */
591
	public function getSortableHandler()
592
	{
593
		return $this->sortable_handler;
594
	}
595
596
597
	/********************************************************************************
598
	 *                                  TREE VIEW                                   *
599
	 ********************************************************************************/
600
601
602
	/**
603
	 * Is tree view set?
604
	 * @return boolean
605
	 */
606
	public function isTreeView()
607
	{
608
		return (bool) $this->tree_view_children_callback;
609
	}
610
611
612
	/**
613
	 * Setting tree view
614
	 * @param callable $get_children_callback
615
	 * @param string|callable $tree_view_has_children_column
616
	 * @return static
617
	 */
618
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
619
	{
620
		if (!is_callable($get_children_callback)) {
621
			throw new DataGridException(
622
				'Parameters to method DataGrid::setTreeView must be of type callable'
623
			);
624
		}
625
626
		if (is_callable($tree_view_has_children_column)) {
627
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
628
			$tree_view_has_children_column = NULL;
629
		}
630
631
		$this->tree_view_children_callback = $get_children_callback;
632
		$this->tree_view_has_children_column = $tree_view_has_children_column;
633
634
		/**
635
		 * TUrn off pagination
636
		 */
637
		$this->setPagination(FALSE);
638
639
		/**
640
		 * Set tree view template file
641
		 */
642
		if (!$this->template_file) {
643
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
644
		}
645
646
		return $this;
647
	}
648
649
650
	/**
651
	 * Is tree view children callback set?
652
	 * @return boolean
653
	 */
654
	public function hasTreeViewChildrenCallback()
655
	{
656
		return is_callable($this->tree_view_has_children_callback);
657
	}
658
659
660
	/**
661
	 * @param  mixed $item
662
	 * @return boolean
663
	 */
664
	public function treeViewChildrenCallback($item)
665
	{
666
		return call_user_func($this->tree_view_has_children_callback, $item);
667
	}
668
669
670
	/********************************************************************************
671
	 *                                    COLUMNS                                   *
672
	 ********************************************************************************/
673
674
675
	/**
676
	 * Add text column with no other formating
677
	 * @param  string      $key
678
	 * @param  string      $name
679
	 * @param  string|null $column
680
	 * @return Column\ColumnText
681
	 */
682
	public function addColumnText($key, $name, $column = NULL)
683
	{
684
		$this->addColumnCheck($key);
685
		$column = $column ?: $key;
686
687
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
688
	}
689
690
691
	/**
692
	 * Add column with link
693
	 * @param  string      $key
694
	 * @param  string      $name
695
	 * @param  string|null $column
696
	 * @return Column\ColumnLink
697
	 */
698
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
699
	{
700
		$this->addColumnCheck($key);
701
		$column = $column ?: $key;
702
		$href = $href ?: $key;
703
704
		if (NULL === $params) {
705
			$params = [$this->primary_key];
706
		}
707
708
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
709
	}
710
711
712
	/**
713
	 * Add column with possible number formating
714
	 * @param  string      $key
715
	 * @param  string      $name
716
	 * @param  string|null $column
717
	 * @return Column\ColumnNumber
718
	 */
719
	public function addColumnNumber($key, $name, $column = NULL)
720
	{
721
		$this->addColumnCheck($key);
722
		$column = $column ?: $key;
723
724
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
725
	}
726
727
728
	/**
729
	 * Add column with date formating
730
	 * @param  string      $key
731
	 * @param  string      $name
732
	 * @param  string|null $column
733
	 * @return Column\ColumnDateTime
734
	 */
735
	public function addColumnDateTime($key, $name, $column = NULL)
736
	{
737
		$this->addColumnCheck($key);
738
		$column = $column ?: $key;
739
740
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
741
	}
742
743
744
	/**
745
	 * Add column status
746
	 * @param  string      $key
747
	 * @param  string      $name
748
	 * @param  string|null $column
749
	 * @return Column\ColumnStatus
750
	 */
751
	public function addColumnStatus($key, $name, $column = NULL)
752
	{
753
		$this->addColumnCheck($key);
754
		$column = $column ?: $key;
755
756
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
757
	}
758
759
760
	/**
761
	 * @param string $key
762
	 * @param Column\Column $column
763
	 * @return Column\Column
764
	 */
765
	protected function addColumn($key, Column\Column $column)
766
	{
767
		$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...
768
769
		$this->columns_visibility[$key] = [
770
			'visible' => TRUE,
771
			'name' => $column->getName()
772
		];
773
774
		return $this->columns[$key] = $column;
775
	}
776
777
778
	/**
779
	 * Return existing column
780
	 * @param  string $key
781
	 * @return Column\Column
782
	 * @throws DataGridException
783
	 */
784
	public function getColumn($key)
785
	{
786
		if (!isset($this->columns[$key])) {
787
			throw new DataGridException("There is no column at key [$key] defined.");
788
		}
789
790
		return $this->columns[$key];
791
	}
792
793
794
	/**
795
	 * Remove column
796
	 * @param string $key
797
	 * @return void
798
	 */
799
	public function removeColumn($key)
800
	{
801
		unset($this->columns[$key]);
802
	}
803
804
805
	/**
806
	 * Check whether given key already exists in $this->columns
807
	 * @param  string $key
808
	 * @throws DataGridException
809
	 */
810
	protected function addColumnCheck($key)
811
	{
812
		if (isset($this->columns[$key])) {
813
			throw new DataGridException("There is already column at key [$key] defined.");
814
		}
815
	}
816
817
818
	/********************************************************************************
819
	 *                                    ACTIONS                                   *
820
	 ********************************************************************************/
821
822
823
	/**
824
	 * Create action
825
	 * @param string     $key
826
	 * @param string     $name
827
	 * @param string     $href
828
	 * @param array|null $params
829
	 * @return Column\Action
830
	 */
831
	public function addAction($key, $name, $href = NULL, array $params = NULL)
832
	{
833
		$this->addActionCheck($key);
834
		$href = $href ?: $key;
835
836
		if (NULL === $params) {
837
			$params = [$this->primary_key];
838
		}
839
840
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
841
	}
842
843
844
	/**
845
	 * Create action callback
846
	 * @param string     $key
847
	 * @param string     $name
848
	 * @return Column\Action
849
	 */
850
	public function addActionCallback($key, $name, $callback = NULL)
851
	{
852
		$this->addActionCheck($key);
853
		$params = ['__id' => $this->primary_key];
854
855
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
856
857
		if ($callback) {
858
			if (!is_callable($callback)) {
859
				throw new DataGridException('ActionCallback callback has to be callable.');
860
			}
861
862
			$action->onClick[] = $callback;
863
		}
864
865
		return $action;
866
	}
867
868
869
	/**
870
	 * Get existing action
871
	 * @param  string       $key
872
	 * @return Column\Action
873
	 * @throws DataGridException
874
	 */
875
	public function getAction($key)
876
	{
877
		if (!isset($this->actions[$key])) {
878
			throw new DataGridException("There is no action at key [$key] defined.");
879
		}
880
881
		return $this->actions[$key];
882
	}
883
884
885
	/**
886
	 * Remove action
887
	 * @param string $key
888
	 * @return void
889
	 */
890
	public function removeAction($key)
891
	{
892
		unset($this->actions[$key]);
893
	}
894
895
896
	/**
897
	 * Check whether given key already exists in $this->filters
898
	 * @param  string $key
899
	 * @throws DataGridException
900
	 */
901
	protected function addActionCheck($key)
902
	{
903
		if (isset($this->actions[$key])) {
904
			throw new DataGridException("There is already action at key [$key] defined.");
905
		}
906
	}
907
908
909
	/********************************************************************************
910
	 *                                    FILTERS                                   *
911
	 ********************************************************************************/
912
913
914
	/**
915
	 * Add filter fot text search
916
	 * @param string       $key
917
	 * @param string       $name
918
	 * @param array|string $columns
919
	 * @return Filter\FilterText
920
	 * @throws DataGridException
921
	 */
922
	public function addFilterText($key, $name, $columns = NULL)
923
	{
924
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
925
926
		if (!is_array($columns)) {
927
			throw new DataGridException("Filter Text can except only array or string.");
928
		}
929
930
		$this->addFilterCheck($key);
931
932
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
933
	}
934
935
936
	/**
937
	 * Add select box filter
938
	 * @param string $key
939
	 * @param string $name
940
	 * @param array  $options
941
	 * @param string $column
942
	 * @return Filter\FilterSelect
943
	 * @throws DataGridException
944
	 */
945 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...
946
	{
947
		$column = $column ?: $key;
948
949
		if (!is_string($column)) {
950
			throw new DataGridException("Filter Select can only filter through one column.");
951
		}
952
953
		$this->addFilterCheck($key);
954
955
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
956
	}
957
958
959
	/**
960
	 * Add datepicker filter
961
	 * @param string $key
962
	 * @param string $name
963
	 * @param string $column
964
	 * @return Filter\FilterDate
965
	 * @throws DataGridException
966
	 */
967
	public function addFilterDate($key, $name, $column = NULL)
968
	{
969
		$column = $column ?: $key;
970
971
		if (!is_string($column)) {
972
			throw new DataGridException("FilterDate can only filter through one column.");
973
		}
974
975
		$this->addFilterCheck($key);
976
977
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
978
	}
979
980
981
	/**
982
	 * Add range filter (from - to)
983
	 * @param string $key
984
	 * @param string $name
985
	 * @param string $column
986
	 * @return Filter\FilterRange
987
	 * @throws DataGridException
988
	 */
989 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...
990
	{
991
		$column = $column ?: $key;
992
993
		if (!is_string($column)) {
994
			throw new DataGridException("FilterRange can only filter through one column.");
995
		}
996
997
		$this->addFilterCheck($key);
998
999
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1000
	}
1001
1002
1003
	/**
1004
	 * Add datepicker filter (from - to)
1005
	 * @param string $key
1006
	 * @param string $name
1007
	 * @param string $column
1008
	 * @return Filter\FilterDateRange
1009
	 * @throws DataGridException
1010
	 */
1011 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...
1012
	{
1013
		$column = $column ?: $key;
1014
1015
		if (!is_string($column)) {
1016
			throw new DataGridException("FilterDateRange can only filter through one column.");
1017
		}
1018
1019
		$this->addFilterCheck($key);
1020
1021
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1022
	}
1023
1024
1025
	/**
1026
	 * Check whether given key already exists in $this->filters
1027
	 * @param  string $key
1028
	 * @throws DataGridException
1029
	 */
1030
	protected function addFilterCheck($key)
1031
	{
1032
		if (isset($this->filters[$key])) {
1033
			throw new DataGridException("There is already action at key [$key] defined.");
1034
		}
1035
	}
1036
1037
1038
	/**
1039
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1040
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1041
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1042
	 */
1043
	public function assableFilters()
1044
	{
1045
		foreach ($this->filter as $key => $value) {
1046
			if (!isset($this->filters[$key])) {
1047
				$this->deleteSesssionData($key);
1048
1049
				continue;
1050
			}
1051
1052
			if (is_array($value) || $value instanceof \Traversable) {
1053
				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...
1054
					$this->filters[$key]->setValue($value);
1055
				}
1056
			} else {
1057
				if ($value !== '' && $value !== NULL) {
1058
					$this->filters[$key]->setValue($value);
1059
				}
1060
			}
1061
		}
1062
1063
		foreach ($this->columns as $column) {
1064
			if (isset($this->sort[$column->getSortingColumn()])) {
1065
				$column->setSort($this->sort);
1066
			}
1067
		}
1068
1069
		return $this->filters;
1070
	}
1071
1072
1073
	/**
1074
	 * Remove filter
1075
	 * @param string $key
1076
	 * @return void
1077
	 */
1078
	public function removeFilter($key)
1079
	{
1080
		unset($this->filters[$key]);
1081
	}
1082
1083
1084
	/********************************************************************************
1085
	 *                                  FILTERING                                   *
1086
	 ********************************************************************************/
1087
1088
1089
	/**
1090
	 * Is filter active?
1091
	 * @return boolean
1092
	 */
1093
	public function isFilterActive()
1094
	{
1095
		$is_filter = ArraysHelper::testTruthy($this->filter);
1096
1097
		return ($is_filter) || $this->force_filter_active;
1098
	}
1099
1100
1101
	/**
1102
	 * Tell that filter is active from whatever reasons
1103
	 * return static
1104
	 */
1105
	public function setFilterActive()
1106
	{
1107
		$this->force_filter_active = TRUE;
1108
1109
		return $this;
1110
	}
1111
1112
1113
	/**
1114
	 * If we want to sent some initial filter
1115
	 * @param array $filter
1116
	 * @return static
1117
	 */
1118
	public function setFilter(array $filter)
1119
	{
1120
		$this->filter = $filter;
1121
1122
		return $this;
1123
	}
1124
1125
1126
	/**
1127
	 * FilterAndGroupAction form factory
1128
	 * @return Form
1129
	 */
1130
	public function createComponentFilter()
1131
	{
1132
		$form = new Form($this, 'filter');
1133
1134
		$form->setMethod('get');
1135
1136
		$form->setTranslator($this->getTranslator());
1137
1138
		/**
1139
		 * InlineEdit part
1140
		 */
1141
		$inline_edit_container = $form->addContainer('inline_edit');
1142
1143
		if ($this->inlineEdit instanceof InlineEdit) {
1144
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1145
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1146
				->setValidationScope(FALSE);
1147
1148
			$this->inlineEdit->onControlAdd($inline_edit_container);
1149
		}
1150
1151
		/**
1152
		 * Filter part
1153
		 */
1154
		$filter_container = $form->addContainer('filter');
1155
1156
		foreach ($this->filters as $filter) {
1157
			$filter->addToFormContainer($filter_container);
1158
		}
1159
1160
		/**
1161
		 * Group action part
1162
		 */
1163
		$group_action_container = $form->addContainer('group_action');
1164
1165
		if ($this->hasGroupActions()) {
1166
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1167
		}
1168
1169
		$form->setDefaults(['filter' => $this->filter]);
1170
1171
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1172
1173
		return $form;
1174
	}
1175
1176
1177
	/**
1178
	 * Set $this->filter values after filter form submitted
1179
	 * @param  Form $form
1180
	 * @return void
1181
	 */
1182
	public function filterSucceeded(Form $form)
1183
	{
1184
		$values = $form->getValues();
1185
1186
		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...
1187
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1188
				return;
1189
			}
1190
		}
1191
1192
		$inline_edit = $form['inline_edit'];
1193
1194
		if (isset($inline_edit) && isset($inline_edit['submit']) && isset($inline_edit['cancel'])) {
1195
			if ($inline_edit['submit']->isSubmittedBy() || $inline_edit['cancel']->isSubmittedBy()) {
1196
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1197
				$primary_where_column = $form->getHttpData(
1198
					Form::DATA_LINE,
1199
					'inline_edit[_primary_where_column]'
1200
				);
1201
1202
				if ($inline_edit['submit']->isSubmittedBy()) {
1203
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1204
1205
					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...
1206
						$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...
1207
					}
1208
				}
1209
1210
				$this->redrawItem($id, $primary_where_column);
1211
1212
				return;
1213
			}
1214
		}
1215
1216
		$values = $values['filter'];
1217
1218
		foreach ($values as $key => $value) {
1219
			/**
1220
			 * Session stuff
1221
			 */
1222
			$this->saveSessionData($key, $value);
1223
1224
			/**
1225
			 * Other stuff
1226
			 */
1227
			$this->filter[$key] = $value;
1228
		}
1229
1230
		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...
1231
			$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...
1232
1233
			foreach ($this->columns as $key => $column) {
1234
				if ($column->isSortable()) {
1235
					$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...
1236
						'sort' => $column->getSortNext()
1237
					]);
1238
				}
1239
			}
1240
		}
1241
1242
		$this->reload();
1243
	}
1244
1245
1246
	/**
1247
	 * Should be datagrid filters rendered separately?
1248
	 * @param boolean $out
1249
	 * @return static
1250
	 */
1251
	public function setOuterFilterRendering($out = TRUE)
1252
	{
1253
		$this->outer_filter_rendering = (bool) $out;
1254
1255
		return $this;
1256
	}
1257
1258
1259
	/**
1260
	 * Are datagrid filters rendered separately?
1261
	 * @return boolean
1262
	 */
1263
	public function hasOuterFilterRendering()
1264
	{
1265
		return $this->outer_filter_rendering;
1266
	}
1267
1268
1269
	/**
1270
	 * Try to restore session stuff
1271
	 * @return void
1272
	 */
1273
	public function findSessionFilters()
1274
	{
1275
		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...
1276
			return;
1277
		}
1278
1279
		if (!$this->remember_state) {
1280
			return;
1281
		}
1282
1283
		if ($page = $this->getSessionData('_grid_page')) {
1284
			$this->page = $page;
1285
		}
1286
1287
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1288
			$this->per_page = $per_page;
1289
		}
1290
1291
		if ($sort = $this->getSessionData('_grid_sort')) {
1292
			$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...
1293
		}
1294
1295
		foreach ($this->getSessionData() as $key => $value) {
1296
			$other_session_keys = [
1297
				'_grid_per_page',
1298
				'_grid_sort',
1299
				'_grid_page',
1300
				'_grid_hidden_columns',
1301
				'_grid_hidden_columns_manipulated'
1302
			];
1303
1304
			if (!in_array($key, $other_session_keys)) {
1305
				$this->filter[$key] = $value;
1306
			}
1307
		}
1308
	}
1309
1310
1311
	/********************************************************************************
1312
	 *                                    EXPORTS                                   *
1313
	 ********************************************************************************/
1314
1315
1316
	/**
1317
	 * Add export of type callback
1318
	 * @param string $text
1319
	 * @param callable $callback
1320
	 * @param boolean $filtered
1321
	 * @return Export\Export
1322
	 */
1323
	public function addExportCallback($text, $callback, $filtered = FALSE)
1324
	{
1325
		if (!is_callable($callback)) {
1326
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1327
		}
1328
1329
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1330
	}
1331
1332
1333
	/**
1334
	 * Add already implemented csv export
1335
	 * @param string      $text
1336
	 * @param string      $csv_file_name
1337
	 * @param string|null $output_encoding
1338
	 * @param string|null $delimiter
1339
	 * @return Export\Export
1340
	 */
1341
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1342
	{
1343
		return $this->addToExports(new Export\ExportCsv(
1344
			$text,
1345
			$csv_file_name,
1346
			FALSE,
1347
			$output_encoding,
1348
			$delimiter
1349
		));
1350
	}
1351
1352
1353
	/**
1354
	 * Add already implemented csv export, but for filtered data
1355
	 * @param string      $text
1356
	 * @param string      $csv_file_name
1357
	 * @param string|null $output_encoding
1358
	 * @param string|null $delimiter
1359
	 * @return Export\Export
1360
	 */
1361
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1362
	{
1363
		return $this->addToExports(new Export\ExportCsv(
1364
			$text,
1365
			$csv_file_name,
1366
			TRUE,
1367
			$output_encoding,
1368
			$delimiter
1369
		));
1370
	}
1371
1372
1373
	/**
1374
	 * Add export to array
1375
	 * @param Export\Export $export
1376
	 * @return Export\Export
1377
	 */
1378
	protected function addToExports(Export\Export $export)
1379
	{
1380
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1381
1382
		$export->setLink($this->link('export!', ['id' => $id]));
1383
1384
		return $this->exports[$id] = $export;
1385
	}
1386
1387
1388
	public function resetExportsLinks()
1389
	{
1390
		foreach ($this->exports as $id => $export) {
1391
			$export->setLink($this->link('export!', ['id' => $id]));
1392
		}
1393
	}
1394
1395
1396
	/********************************************************************************
1397
	 *                                 GROUP ACTIONS                                *
1398
	 ********************************************************************************/
1399
1400
1401
	/**
1402
	 * Alias for add group select action
1403
	 * @param string $title
1404
	 * @param array  $options
1405
	 * @return GroupAction\GroupAction
1406
	 */
1407
	public function addGroupAction($title, $options = [])
1408
	{
1409
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1410
	}
1411
1412
	/**
1413
	 * Add group action (select box)
1414
	 * @param string $title
1415
	 * @param array  $options
1416
	 * @return GroupAction\GroupAction
1417
	 */
1418
	public function addGroupSelectAction($title, $options = [])
1419
	{
1420
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1421
	}
1422
1423
	/**
1424
	 * Add group action (text input)
1425
	 * @param string $title
1426
	 * @return GroupAction\GroupAction
1427
	 */
1428
	public function addGroupTextAction($title)
1429
	{
1430
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1431
	}
1432
1433
	/**
1434
	 * Get collection of all group actions
1435
	 * @return GroupAction\GroupActionCollection
1436
	 */
1437
	public function getGroupActionCollection()
1438
	{
1439
		if (!$this->group_action_collection) {
1440
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1441
		}
1442
1443
		return $this->group_action_collection;
1444
	}
1445
1446
1447
	/**
1448
	 * Has datagrid some group actions?
1449
	 * @return boolean
1450
	 */
1451
	public function hasGroupActions()
1452
	{
1453
		return (bool) $this->group_action_collection;
1454
	}
1455
1456
1457
	/********************************************************************************
1458
	 *                                   HANDLERS                                   *
1459
	 ********************************************************************************/
1460
1461
1462
	/**
1463
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1464
	 * @param  int  $page
1465
	 * @return void
1466
	 */
1467
	public function handlePage($page)
1468
	{
1469
		/**
1470
		 * Session stuff
1471
		 */
1472
		$this->page = $page;
1473
		$this->saveSessionData('_grid_page', $page);
1474
1475
		$this->reload(['table']);
1476
	}
1477
1478
1479
	/**
1480
	 * Handler for sorting
1481
	 * @param array $sort
1482
	 * @return void
1483
	 */
1484
	public function handleSort(array $sort)
1485
	{
1486
		$new_sort = [];
1487
1488
		/**
1489
		 * Find apropirate column
1490
		 */
1491
		foreach ($sort as $key => $value) {
1492
			if (empty($this->columns[$key])) {
1493
				throw new DataGridException("Column <$key> not found");
1494
			}
1495
1496
			$column = $this->columns[$key];
1497
			$new_sort = [$column->getSortingColumn() => $value];
1498
1499
			/**
1500
			 * Pagination may be reseted after sorting
1501
			 */
1502
			if ($column->sortableResetPagination()) {
1503
				$this->page = 1;
1504
				$this->saveSessionData('_grid_page', 1);
1505
			}
1506
1507
			/**
1508
			 * Custom sorting callback may be applied
1509
			 */
1510
			if ($column->getSortableCallback()) {
1511
				$this->sort_callback = $column->getSortableCallback();
1512
			}
1513
		}
1514
1515
		/**
1516
		 * Session stuff
1517
		 */
1518
		$this->sort = $new_sort;
1519
		$this->saveSessionData('_grid_sort', $this->sort);
1520
1521
		$this->reload(['table']);
1522
	}
1523
1524
1525
	/**
1526
	 * handler for reseting the filter
1527
	 * @return void
1528
	 */
1529
	public function handleResetFilter()
1530
	{
1531
		/**
1532
		 * Session stuff
1533
		 */
1534
		$this->deleteSesssionData('_grid_page');
1535
1536
		foreach ($this->getSessionData() as $key => $value) {
1537
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1538
				$this->deleteSesssionData($key);
1539
			}
1540
		}
1541
1542
		$this->filter = [];
1543
1544
		$this->reload(['grid']);
1545
	}
1546
1547
1548
	/**
1549
	 * Handler for export
1550
	 * @param  int $id Key for particular export class in array $this->exports
1551
	 * @return void
1552
	 */
1553
	public function handleExport($id)
1554
	{
1555
		if (!isset($this->exports[$id])) {
1556
			throw new Nette\Application\ForbiddenRequestException;
1557
		}
1558
1559
		if (!empty($this->columns_export_order)) {
1560
			$this->setColumnsOrder($this->columns_export_order);
1561
		}
1562
1563
		$export = $this->exports[$id];
1564
1565
		if ($export->isFiltered()) {
1566
			$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...
1567
			$filter    = $this->assableFilters();
1568
		} else {
1569
			$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...
1570
			$filter    = [];
1571
		}
1572
1573
		if (NULL === $this->dataModel) {
1574
			throw new DataGridException('You have to set a data source first.');
1575
		}
1576
1577
		$rows = [];
1578
1579
		$items = Nette\Utils\Callback::invokeArgs(
1580
			[$this->dataModel, 'filterData'], [
1581
				NULL,
1582
				new Sorting($this->sort, $this->sort_callback),
1583
				$filter
1584
			]
1585
		);
1586
1587
		foreach ($items as $item) {
1588
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1589
		}
1590
1591
		if ($export instanceof Export\ExportCsv) {
1592
			$export->invoke($rows, $this);
1593
		} else {
1594
			$export->invoke($items, $this);
1595
		}
1596
1597
		if ($export->isAjax()) {
1598
			$this->reload();
1599
		}
1600
	}
1601
1602
1603
	/**
1604
	 * Handler for getting children of parent item (e.g. category)
1605
	 * @param  int $parent
1606
	 * @return void
1607
	 */
1608
	public function handleGetChildren($parent)
1609
	{
1610
		$this->setDataSource(
1611
			call_user_func($this->tree_view_children_callback, $parent)
1612
		);
1613
1614
		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...
1615
			$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...
1616
			$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...
1617
1618
			$this->redrawControl('items');
1619
1620
			$this->onRedraw();
1621
		} else {
1622
			$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...
1623
		}
1624
	}
1625
1626
1627
	/**
1628
	 * Handler for getting item detail
1629
	 * @param  mixed $id
1630
	 * @return void
1631
	 */
1632
	public function handleGetItemDetail($id)
1633
	{
1634
		$this->template->add('toggle_detail', $id);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

Since the property has write access only, you can use the @property-write annotation instead.

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

See also the PhpDoc documentation for @property.

Loading history...
1635
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1636
1637
		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...
1638
			$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...
1639
			$this->redrawControl('items');
1640
1641
			$this->onRedraw();
1642
		} else {
1643
			$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...
1644
		}
1645
	}
1646
1647
1648
	/**
1649
	 * Handler for inline editing
1650
	 * @param  mixed $id
1651
	 * @param  mixed $key
1652
	 * @return void
1653
	 */
1654
	public function handleEdit($id, $key)
1655
	{
1656
		$column = $this->getColumn($key);
1657
		$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...
1658
1659
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1660
	}
1661
1662
1663
	/**
1664
	 * Redraw $this
1665
	 * @return void
1666
	 */
1667
	public function reload($snippets = [])
1668
	{
1669
		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...
1670
			$this->redrawControl('tbody');
1671
			$this->redrawControl('pagination');
1672
1673
			/**
1674
			 * manualy reset exports links...
1675
			 */
1676
			$this->resetExportsLinks();
1677
			$this->redrawControl('exports');
1678
1679
			foreach ($snippets as $snippet) {
1680
				$this->redrawControl($snippet);
1681
			}
1682
1683
			$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...
1684
1685
			$this->onRedraw();
1686
		} else {
1687
			$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...
1688
		}
1689
	}
1690
1691
1692
	/**
1693
	 * Handler for column status
1694
	 * @param  string $id
1695
	 * @param  string $key
1696
	 * @param  string $value
1697
	 * @return void
1698
	 */
1699
	public function handleChangeStatus($id, $key, $value)
1700
	{
1701
		if (empty($this->columns[$key])) {
1702
			throw new DataGridException("ColumnStatus[$key] does not exist");
1703
		}
1704
1705
		$this->columns[$key]->onChange($id, $value);
1706
	}
1707
1708
1709
	/**
1710
	 * Redraw just one row via ajax
1711
	 * @param  int   $id
1712
	 * @param  mixed $primary_where_column
1713
	 * @return void
1714
	 */
1715
	public function redrawItem($id, $primary_where_column = NULL)
1716
	{
1717
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1718
1719
		$this->redrawControl('items');
1720
		$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...
1721
1722
		$this->onRedraw();
1723
	}
1724
1725
1726
	/**
1727
	 * Tell datagrid to display all columns
1728
	 * @return void
1729
	 */
1730
	public function handleShowAllColumns()
1731
	{
1732
		$this->deleteSesssionData('_grid_hidden_columns');
1733
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1734
1735
		$this->redrawControl();
1736
1737
		$this->onRedraw();
1738
	}
1739
1740
1741
	/**
1742
	 * Reveal particular column
1743
	 * @param  string $column
1744
	 * @return void
1745
	 */
1746 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...
1747
	{
1748
		$columns = $this->getSessionData('_grid_hidden_columns');
1749
1750
		if (!empty($columns)) {
1751
			$pos = array_search($column, $columns);
1752
1753
			if ($pos !== FALSE) {
1754
				unset($columns[$pos]);
1755
			}
1756
		}
1757
1758
		$this->saveSessionData('_grid_hidden_columns', $columns);
1759
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1760
1761
		$this->redrawControl();
1762
1763
		$this->onRedraw();
1764
	}
1765
1766
1767
	/**
1768
	 * Notice datagrid to not display particular columns
1769
	 * @param  string $column
1770
	 * @return void
1771
	 */
1772 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...
1773
	{
1774
		/**
1775
		 * Store info about hiding a column to session
1776
		 */
1777
		$columns = $this->getSessionData('_grid_hidden_columns');
1778
1779
		if (empty($columns)) {
1780
			$columns = [$column];
1781
		} else if (!in_array($column, $columns)) {
1782
			array_push($columns, $column);
1783
		}
1784
1785
		$this->saveSessionData('_grid_hidden_columns', $columns);
1786
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1787
1788
		$this->redrawControl();
1789
1790
		$this->onRedraw();
1791
	}
1792
1793
1794
	public function handleActionCallback($__key, $__id)
1795
	{
1796
		$action = $this->getAction($__key);
1797
1798
		if (!($action instanceof Column\ActionCallback)) {
1799
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1800
		}
1801
1802
		$action->onClick($__id);
1803
	}
1804
1805
1806
	/********************************************************************************
1807
	 *                                  PAGINATION                                  *
1808
	 ********************************************************************************/
1809
1810
1811
	/**
1812
	 * Set options of select "items_per_page"
1813
	 * @param array $items_per_page_list
1814
	 * @return static
1815
	 */
1816
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1817
	{
1818
		$this->items_per_page_list = $items_per_page_list;
1819
1820
		if ($include_all) {
1821
			$this->items_per_page_list[] = 'all';
1822
		}
1823
1824
		return $this;
1825
	}
1826
1827
1828
	/**
1829
	 * Paginator factory
1830
	 * @return Components\DataGridPaginator\DataGridPaginator
1831
	 */
1832
	public function createComponentPaginator()
1833
	{
1834
		/**
1835
		 * Init paginator
1836
		 */
1837
		$component = new Components\DataGridPaginator\DataGridPaginator(
1838
			$this->getTranslator()
1839
		);
1840
		$paginator = $component->getPaginator();
1841
1842
		$paginator->setPage($this->page);
1843
		$paginator->setItemsPerPage($this->getPerPage());
1844
1845
		return $component;
1846
	}
1847
1848
1849
	/**
1850
	 * PerPage form factory
1851
	 * @return Form
1852
	 */
1853
	public function createComponentPerPage()
1854
	{
1855
		$form = new Form;
1856
1857
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1858
			->setValue($this->getPerPage());
1859
1860
		$form->addSubmit('submit', '');
1861
1862
		$saveSessionData = [$this, 'saveSessionData'];
1863
1864
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1865
			/**
1866
			 * Session stuff
1867
			 */
1868
			$saveSessionData('_grid_per_page', $values->per_page);
1869
1870
			/**
1871
			 * Other stuff
1872
			 */
1873
			$this->per_page = $values->per_page;
1874
			$this->reload();
1875
		};
1876
1877
		return $form;
1878
	}
1879
1880
1881
	/**
1882
	 * Get parameter per_page
1883
	 * @return int
1884
	 */
1885
	public function getPerPage()
1886
	{
1887
		$items_per_page_list = $this->getItemsPerPageList();
1888
1889
		$per_page = $this->per_page ?: reset($items_per_page_list);
1890
1891
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1892
			$per_page = reset($items_per_page_list);
1893
		}
1894
1895
		return $per_page;
1896
	}
1897
1898
1899
	/**
1900
	 * Get associative array of items_per_page_list
1901
	 * @return array
1902
	 */
1903
	public function getItemsPerPageList()
1904
	{
1905
		if (empty($this->items_per_page_list)) {
1906
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1907
		}
1908
1909
		$list = array_flip($this->items_per_page_list);
1910
1911
		foreach ($list as $key => $value) {
1912
			$list[$key] = $key;
1913
		}
1914
1915
		if (array_key_exists('all', $list)) {
1916
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1917
		}
1918
1919
		return $list;
1920
	}
1921
1922
1923
	/**
1924
	 * Order Grid to "be paginated"
1925
	 * @param bool $do
1926
	 * @return static
1927
	 */
1928
	public function setPagination($do)
1929
	{
1930
		$this->do_paginate = (bool) $do;
1931
1932
		return $this;
1933
	}
1934
1935
1936
	/**
1937
	 * Tell whether Grid is paginated
1938
	 * @return bool
1939
	 */
1940
	public function isPaginated()
1941
	{
1942
		return $this->do_paginate;
1943
	}
1944
1945
1946
	/**
1947
	 * Return current paginator class
1948
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1949
	 */
1950
	public function getPaginator()
1951
	{
1952
		if ($this->isPaginated() && $this->per_page !== 'all') {
1953
			return $this['paginator'];
1954
		}
1955
1956
		return NULL;
1957
	}
1958
1959
1960
	/********************************************************************************
1961
	 *                                     I18N                                     *
1962
	 ********************************************************************************/
1963
1964
1965
	/**
1966
	 * Set datagrid translator
1967
	 * @param Nette\Localization\ITranslator $translator
1968
	 * @return static
1969
	 */
1970
	public function setTranslator(Nette\Localization\ITranslator $translator)
1971
	{
1972
		$this->translator = $translator;
1973
1974
		return $this;
1975
	}
1976
1977
1978
	/**
1979
	 * Get translator for datagrid
1980
	 * @return Nette\Localization\ITranslator
1981
	 */
1982
	public function getTranslator()
1983
	{
1984
		if (!$this->translator) {
1985
			$this->translator = new Localization\SimpleTranslator;
1986
		}
1987
1988
		return $this->translator;
1989
	}
1990
1991
1992
	/********************************************************************************
1993
	 *                                 COLUMNS ORDER                                *
1994
	 ********************************************************************************/
1995
1996
1997
	/**
1998
	 * Set order of datagrid columns
1999
	 * @param array $order
2000
	 * @return static
2001
	 */
2002
	public function setColumnsOrder($order)
2003
	{
2004
		$new_order = [];
2005
2006
		foreach ($order as $key) {
2007
			if (isset($this->columns[$key])) {
2008
				$new_order[$key] = $this->columns[$key];
2009
			}
2010
		}
2011
2012
		if (sizeof($new_order) === sizeof($this->columns)) {
2013
			$this->columns = $new_order;
2014
		} else {
2015
			throw new DataGridException('When changing columns order, you have to specify all columns');
2016
		}
2017
2018
		return $this;
2019
	}
2020
2021
2022
	/**
2023
	 * Columns order may be different for export and normal grid
2024
	 * @param array $order
2025
	 */
2026
	public function setColumnsExportOrder($order)
2027
	{
2028
		$this->columns_export_order = (array) $order;
2029
	}
2030
2031
2032
	/********************************************************************************
2033
	 *                                SESSION & URL                                 *
2034
	 ********************************************************************************/
2035
2036
2037
	/**
2038
	 * Find some unique session key name
2039
	 * @return string
2040
	 */
2041
	public function getSessionSectionName()
2042
	{
2043
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2044
	}
2045
2046
2047
	/**
2048
	 * Should datagrid remember its filters/pagination/etc using session?
2049
	 * @param bool $remember
2050
	 * @return static
2051
	 */
2052
	public function setRememberState($remember = TRUE)
2053
	{
2054
		$this->remember_state = (bool) $remember;
2055
2056
		return $this;
2057
	}
2058
2059
2060
	/**
2061
	 * Should datagrid refresh url using history API?
2062
	 * @param bool $refresh
2063
	 * @return static
2064
	 */
2065
	public function setRefreshUrl($refresh = TRUE)
2066
	{
2067
		$this->refresh_url = (bool) $refresh;
2068
2069
2070
		return $this;
2071
	}
2072
2073
2074
	/**
2075
	 * Get session data if functionality is enabled
2076
	 * @param  string $key
2077
	 * @return mixed
2078
	 */
2079
	public function getSessionData($key = NULL, $default_value = NULL)
2080
	{
2081
		if (!$this->remember_state) {
2082
			return $key ? $default_value : [];
2083
		}
2084
2085
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2086
	}
2087
2088
2089
	/**
2090
	 * Save session data - just if it is enabled
2091
	 * @param  string $key
2092
	 * @param  mixed  $value
2093
	 * @return void
2094
	 */
2095
	public function saveSessionData($key, $value)
2096
	{
2097
		if ($this->remember_state) {
2098
			$this->grid_session->{$key} = $value;
2099
		}
2100
	}
2101
2102
2103
	/**
2104
	 * Delete session data
2105
	 * @return void
2106
	 */
2107
	public function deleteSesssionData($key)
2108
	{
2109
		unset($this->grid_session->{$key});
2110
	}
2111
2112
2113
	/********************************************************************************
2114
	 *                                  ITEM DETAIL                                 *
2115
	 ********************************************************************************/
2116
2117
2118
	/**
2119
	 * Get items detail parameters
2120
	 * @return array
2121
	 */
2122
	public function getItemsDetail()
2123
	{
2124
		return $this->items_detail;
2125
	}
2126
2127
2128
	/**
2129
	 * Items can have thair detail - toggled
2130
	 * @param mixed $detail callable|string|bool
2131
	 * @param bool|NULL $primary_where_column
2132
	 * @return Column\ItemDetail
2133
	 */
2134
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2135
	{
2136
		if ($this->isSortable()) {
2137
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2138
		}
2139
2140
		$this->items_detail = new Column\ItemDetail(
2141
			$this,
2142
			$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...
2143
		);
2144
2145
		if (is_string($detail)) {
2146
			/**
2147
			 * Item detail will be in separate template
2148
			 */
2149
			$this->items_detail->setType('template');
2150
			$this->items_detail->setTemplate($detail);
2151
2152
		} else if (is_callable($detail)) {
2153
			/**
2154
			 * Item detail will be rendered via custom callback renderer
2155
			 */
2156
			$this->items_detail->setType('renderer');
2157
			$this->items_detail->setRenderer($detail);
2158
2159
		} else if (TRUE === $detail) {
2160
			/**
2161
			 * Item detail will be rendered probably via block #detail
2162
			 */
2163
			$this->items_detail->setType('block');
2164
2165
		} else {
2166
			throw new DataGridException(
2167
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2168
			);
2169
		}
2170
2171
		return $this->items_detail;
2172
	}
2173
2174
2175
	/**
2176
	 * Factory for components from callback
2177
	 * @return Nette\ComponentModel\IComponent
2178
	 */
2179
	protected function createComponent($name)
2180
	{
2181
		return (isset($this->components[$name]))
2182
			? $this->components[$name]()
2183
			: parent::createComponent($name);
2184
	}
2185
2186
2187
	/********************************************************************************
2188
	 *                                ROW PRIVILEGES                                *
2189
	 ********************************************************************************/
2190
2191
2192
	/**
2193
	 * @param  callable $condition
2194
	 * @return void
2195
	 */
2196
	public function allowRowsGroupAction(callable $condition)
2197
	{
2198
		$this->row_conditions['group_action'] = $condition;
2199
	}
2200
2201
2202
	/**
2203
	 * @param  string   $key
2204
	 * @param  callable $condition
2205
	 * @return void
2206
	 */
2207
	public function allowRowsAction($key, callable $condition)
2208
	{
2209
		$this->row_conditions['action'][$key] = $condition;
2210
	}
2211
2212
2213
	/**
2214
	 * @param  string      $name
2215
	 * @param  string|null $key
2216
	 * @return bool|callable
2217
	 */
2218
	public function getRowCondition($name, $key = NULL)
2219
	{
2220
		if (!isset($this->row_conditions[$name])) {
2221
			return FALSE;
2222
		}
2223
2224
		$condition = $this->row_conditions[$name];
2225
2226
		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...
2227
			return $condition;
2228
		}
2229
2230
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2231
	}
2232
2233
2234
	/********************************************************************************
2235
	 *                               COLUMN CALLBACK                                *
2236
	 ********************************************************************************/
2237
2238
2239
	/**
2240
	 * @param  string   $key
2241
	 * @param  callable $callback
2242
	 * @return void
2243
	 */
2244
	public function addColumnCallback($key, callable $callback)
2245
	{
2246
		$this->column_callbacks[$key] = $callback;
2247
	}
2248
2249
2250
	/**
2251
	 * @param  string $key
2252
	 * @return callable|null
2253
	 */
2254
	public function getColumnCallback($key)
2255
	{
2256
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2257
	}
2258
2259
2260
	/********************************************************************************
2261
	 *                                 INLINE EDIT                                  *
2262
	 ********************************************************************************/
2263
2264
2265
	/**
2266
	 * @return InlineEdit
2267
	 */
2268
	public function addInlineEdit($primary_where_column = NULL)
2269
	{
2270
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2271
2272
		return $this->inlineEdit;
2273
	}
2274
2275
2276
	/**
2277
	 * @return InlineEdit|null
2278
	 */
2279
	public function getInlineEdit()
2280
	{
2281
		return $this->inlineEdit;
2282
	}
2283
2284
2285
	public function handleInlineEdit($id)
2286
	{
2287
		if ($this->inlineEdit) {
2288
			$this->inlineEdit->setItemId($id);
2289
2290
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2291
2292
			$this['filter']['inline_edit']->addHidden('_id', $id);
2293
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2294
2295
			$this->redrawItem($id, $primary_where_column);
2296
		}
2297
	}
2298
2299
2300
	/********************************************************************************
2301
	 *                               HIDEABLE COLUMNS                               *
2302
	 ********************************************************************************/
2303
2304
2305
	/**
2306
	 * Can datagrid hide colums?
2307
	 * @return boolean
2308
	 */
2309
	public function canHideColumns()
2310
	{
2311
		return (bool) $this->can_hide_columns;
2312
	}
2313
2314
2315
	/**
2316
	 * Order Grid to set columns hideable.
2317
	 * @return static
2318
	 */
2319
	public function setColumnsHideable()
2320
	{
2321
		$this->can_hide_columns = TRUE;
2322
2323
		return $this;
2324
	}
2325
2326
2327
	/********************************************************************************
2328
	 *                                   INTERNAL                                   *
2329
	 ********************************************************************************/
2330
2331
2332
	/**
2333
	 * Get cont of columns
2334
	 * @return int
2335
	 */
2336
	public function getColumnsCount()
2337
	{
2338
		$count = sizeof($this->getColumns());
2339
2340
		if (!empty($this->actions) || $this->isSortable() || $this->getItemsDetail() || $this->getInlineEdit()) {
2341
			$count++;
2342
		}
2343
2344
		if ($this->hasGroupActions()) {
2345
			$count++;
2346
		}
2347
2348
		return $count;
2349
	}
2350
2351
2352
	/**
2353
	 * Get primary key of datagrid data source
2354
	 * @return string
2355
	 */
2356
	public function getPrimaryKey()
2357
	{
2358
		return $this->primary_key;
2359
	}
2360
2361
2362
	/**
2363
	 * Get set of set columns
2364
	 * @return Column\IColumn[]
2365
	 */
2366
	public function getColumns()
2367
	{
2368
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2369
			$columns_to_hide = [];
2370
2371
			foreach ($this->columns as $key => $column) {
2372
				if ($column->getDefaultHide()) {
2373
					$columns_to_hide[] = $key;
2374
				}
2375
			}
2376
2377
			if (!empty($columns_to_hide)) {
2378
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2379
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2380
			}
2381
		}
2382
2383
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2384
		
2385
		foreach ($hidden_columns as $column) {
2386
			if (!empty($this->columns[$column])) {
2387
				$this->columns_visibility[$column] = [
2388
					'visible' => FALSE,
2389
					'name' => $this->columns[$column]->getName()
2390
				];
2391
2392
				$this->removeColumn($column);
2393
			}
2394
		}
2395
2396
		return $this->columns;
2397
	}
2398
2399
2400
	/**
2401
	 * @return PresenterComponent
2402
	 */
2403
	public function getParent()
2404
	{
2405
		$parent = parent::getParent();
2406
2407
		if (!($parent instanceof PresenterComponent)) {
2408
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2409
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2410
			);
2411
		}
2412
2413
		return $parent;
2414
	}
2415
2416
}
2417