Completed
Pull Request — master (#156)
by
unknown
02:42
created

DataGrid::handlePage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

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

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
329
330
		/**
331
		 * Prepare data for rendering (datagrid may render just one item)
332
		 */
333
		$rows = [];
334
335
		if (!empty($this->redraw_item)) {
336
			$items = $this->dataModel->filterRow($this->redraw_item);
337
		} else {
338
			$items = Nette\Utils\Callback::invokeArgs(
339
				[$this->dataModel, 'filterData'],
340
				[
341
					$this->getPaginator(),
342
					new Sorting($this->sort, $this->sort_callback),
343
					$this->assableFilters()
344
				]
345
			);
346
		}
347
348
		$callback = $this->rowCallback ?: NULL;
349
350
		foreach ($items as $item) {
351
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
352
353
			if ($callback) {
354
				$callback($item, $row->getControl());
355
			}
356
		}
357
358
		if ($this->isTreeView()) {
359
			$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...
360
		}
361
362
		$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...
363
364
		$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...
365
		$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...
366
		$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...
367
		$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...
368
369
		$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...
370
		$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...
371
		$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...
372
		$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...
373
		$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...
374
375
		$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...
376
377
		/**
378
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
379
		 */
380
		$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...
381
382
		/**
383
		 * Set template file and render it
384
		 */
385
		$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...
386
		$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...
387
	}
388
389
390
	/********************************************************************************
391
	 *                                 ROW CALLBACK                                 *
392
	 ********************************************************************************/
393
394
395
	/**
396
	 * Each row can be modified with user callback
397
	 * @param  callable  $callback
398
	 * @return static
399
	 */
400
	public function setRowCallback(callable $callback)
401
	{
402
		$this->rowCallback = $callback;
403
404
		return $this;
405
	}
406
407
408
	/********************************************************************************
409
	 *                                 DATA SOURCE                                  *
410
	 ********************************************************************************/
411
412
413
	/**
414
	 * By default ID, you can change that
415
	 * @param string $primary_key
416
	 * @return static
417
	 */
418
	public function setPrimaryKey($primary_key)
419
	{
420
		if ($this->dataModel instanceof DataModel) {
421
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
422
		}
423
424
		$this->primary_key = $primary_key;
425
426
		return $this;
427
	}
428
429
430
	/**
431
	 * Set Grid data source
432
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
433
	 * @return static
434
	 */
435
	public function setDataSource($source)
436
	{
437
		$this->dataModel = new DataModel($source, $this->primary_key);
438
439
		return $this;
440
	}
441
442
443
	/********************************************************************************
444
	 *                                  TEMPLATING                                  *
445
	 ********************************************************************************/
446
447
448
	/**
449
	 * Set custom template file to render
450
	 * @param string $template_file
451
	 * @return static
452
	 */
453
	public function setTemplateFile($template_file)
454
	{
455
		$this->template_file = $template_file;
456
457
		return $this;
458
	}
459
460
461
	/**
462
	 * Get DataGrid template file
463
	 * @return string
464
	 * @return static
465
	 */
466
	public function getTemplateFile()
467
	{
468
		return $this->template_file ?: $this->getOriginalTemplateFile();
469
	}
470
471
472
	/**
473
	 * Get DataGrid original template file
474
	 * @return string
475
	 */
476
	public function getOriginalTemplateFile()
477
	{
478
		return __DIR__.'/templates/datagrid.latte';
479
	}
480
481
482
	/**
483
	 * Tell datagrid wheteher to use or not happy components
484
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
485
	 * @return void|bool
486
	 */
487
	public function useHappyComponents($use = NULL)
488
	{
489
		if (NULL === $use) {
490
			return $this->use_happy_components;
491
		}
492
493
		$this->use_happy_components = (bool) $use;
494
	}
495
496
497
	/********************************************************************************
498
	 *                                   SORTING                                    *
499
	 ********************************************************************************/
500
501
502
	/**
503
	 * Set default sorting
504
	 * @param array $sort
505
	 * @return static
506
	 */
507
	public function setDefaultSort($sort)
508
	{
509
		if (is_string($sort)) {
510
			$sort = [$sort => 'ASC'];
511
		} else {
512
			$sort = (array) $sort;
513
		}
514
515
		$this->default_sort = $sort;
516
517
		return $this;
518
	}
519
520
521
	/**
522
	 * User may set default sorting, apply it
523
	 * @return void
524
	 */
525
	public function findDefaultSort()
526
	{
527
		if (!empty($this->sort)) {
528
			return;
529
		}
530
531
		if (!empty($this->default_sort)) {
532
			$this->sort = $this->default_sort;
533
		}
534
535
		$this->saveSessionData('_grid_sort', $this->sort);
536
	}
537
538
539
	/**
540
	 * Set grido to be sortable
541
	 * @param bool $sortable
542
	 * @return static
543
	 */
544
	public function setSortable($sortable = TRUE)
545
	{
546
		if ($this->getItemsDetail()) {
547
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
548
		}
549
550
		$this->sortable = (bool) $sortable;
551
552
		return $this;
553
	}
554
555
556
	/**
557
	 * Set sortable handle
558
	 * @param string $handler
559
	 * @return static
560
	 */
561
	public function setSortableHandler($handler = 'sort!')
562
	{
563
		$this->sortable_handler = (string) $handler;
564
565
		return $this;
566
	}
567
568
569
	/**
570
	 * Tell whether DataGrid is sortable
571
	 * @return bool
572
	 */
573
	public function isSortable()
574
	{
575
		return $this->sortable;
576
	}
577
578
	/**
579
	 * Return sortable handle name
580
	 * @return string
581
	 */
582
	public function getSortableHandler()
583
	{
584
		return $this->sortable_handler;
585
	}
586
587
588
	/********************************************************************************
589
	 *                                  TREE VIEW                                   *
590
	 ********************************************************************************/
591
592
593
	/**
594
	 * Is tree view set?
595
	 * @return boolean
596
	 */
597
	public function isTreeView()
598
	{
599
		return (bool) $this->tree_view_children_callback;
600
	}
601
602
603
	/**
604
	 * Setting tree view
605
	 * @param callable $get_children_callback
606
	 * @param string|callable $tree_view_has_children_column
607
	 * @return static
608
	 */
609
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
610
	{
611
		if (!is_callable($get_children_callback)) {
612
			throw new DataGridException(
613
				'Parameters to method DataGrid::setTreeView must be of type callable'
614
			);
615
		}
616
617
		if (is_callable($tree_view_has_children_column)) {
618
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
619
			$tree_view_has_children_column = NULL;
620
		}
621
622
		$this->tree_view_children_callback = $get_children_callback;
623
		$this->tree_view_has_children_column = $tree_view_has_children_column;
624
625
		/**
626
		 * TUrn off pagination
627
		 */
628
		$this->setPagination(FALSE);
629
630
		/**
631
		 * Set tree view template file
632
		 */
633
		if (!$this->template_file) {
634
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
635
		}
636
637
		return $this;
638
	}
639
640
641
	/**
642
	 * Is tree view children callback set?
643
	 * @return boolean
644
	 */
645
	public function hasTreeViewChildrenCallback()
646
	{
647
		return is_callable($this->tree_view_has_children_callback);
648
	}
649
650
651
	/**
652
	 * @param  mixed $item
653
	 * @return boolean
654
	 */
655
	public function treeViewChildrenCallback($item)
656
	{
657
		return call_user_func($this->tree_view_has_children_callback, $item);
658
	}
659
660
661
	/********************************************************************************
662
	 *                                    COLUMNS                                   *
663
	 ********************************************************************************/
664
665
666
	/**
667
	 * Add text column with no other formating
668
	 * @param  string      $key
669
	 * @param  string      $name
670
	 * @param  string|null $column
671
	 * @return Column\ColumnText
672
	 */
673
	public function addColumnText($key, $name, $column = NULL)
674
	{
675
		$this->addColumnCheck($key);
676
		$column = $column ?: $key;
677
678
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
679
	}
680
681
682
	/**
683
	 * Add column with link
684
	 * @param  string      $key
685
	 * @param  string      $name
686
	 * @param  string|null $column
687
	 * @return Column\ColumnLink
688
	 */
689
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
690
	{
691
		$this->addColumnCheck($key);
692
		$column = $column ?: $key;
693
		$href = $href ?: $key;
694
695
		if (NULL === $params) {
696
			$params = [$this->primary_key];
697
		}
698
699
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
700
	}
701
702
703
	/**
704
	 * Add column with possible number formating
705
	 * @param  string      $key
706
	 * @param  string      $name
707
	 * @param  string|null $column
708
	 * @return Column\ColumnNumber
709
	 */
710
	public function addColumnNumber($key, $name, $column = NULL)
711
	{
712
		$this->addColumnCheck($key);
713
		$column = $column ?: $key;
714
715
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
716
	}
717
718
719
	/**
720
	 * Add column with date formating
721
	 * @param  string      $key
722
	 * @param  string      $name
723
	 * @param  string|null $column
724
	 * @return Column\ColumnDateTime
725
	 */
726
	public function addColumnDateTime($key, $name, $column = NULL)
727
	{
728
		$this->addColumnCheck($key);
729
		$column = $column ?: $key;
730
731
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
732
	}
733
734
735
	/**
736
	 * Add column status
737
	 * @param  string      $key
738
	 * @param  string      $name
739
	 * @param  string|null $column
740
	 * @return Column\ColumnStatus
741
	 */
742
	public function addColumnStatus($key, $name, $column = NULL)
743
	{
744
		$this->addColumnCheck($key);
745
		$column = $column ?: $key;
746
747
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
748
	}
749
750
751
	/**
752
	 * @param string $key
753
	 * @param Column\Column $column
754
	 * @return Column\Column
755
	 */
756
	protected function addColumn($key, Column\Column $column)
757
	{
758
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Documentation Bug introduced by
The method onColumnAdd does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
759
760
		$this->columns_visibility[$key] = [
761
			'visible' => TRUE,
762
			'name' => $column->getName()
763
		];
764
765
		return $this->columns[$key] = $column;
766
	}
767
768
769
	/**
770
	 * Return existing column
771
	 * @param  string $key
772
	 * @return Column\Column
773
	 * @throws DataGridException
774
	 */
775
	public function getColumn($key)
776
	{
777
		if (!isset($this->columns[$key])) {
778
			throw new DataGridException("There is no column at key [$key] defined.");
779
		}
780
781
		return $this->columns[$key];
782
	}
783
784
785
	/**
786
	 * Remove column
787
	 * @param string $key
788
	 * @return void
789
	 */
790
	public function removeColumn($key)
791
	{
792
		unset($this->columns[$key]);
793
	}
794
795
796
	/**
797
	 * Check whether given key already exists in $this->columns
798
	 * @param  string $key
799
	 * @throws DataGridException
800
	 */
801
	protected function addColumnCheck($key)
802
	{
803
		if (isset($this->columns[$key])) {
804
			throw new DataGridException("There is already column at key [$key] defined.");
805
		}
806
	}
807
808
809
	/********************************************************************************
810
	 *                                    ACTIONS                                   *
811
	 ********************************************************************************/
812
813
814
	/**
815
	 * Create action
816
	 * @param string     $key
817
	 * @param string     $name
818
	 * @param string     $href
819
	 * @param array|null $params
820
	 * @return Column\Action
821
	 */
822
	public function addAction($key, $name, $href = NULL, array $params = NULL)
823
	{
824
		$this->addActionCheck($key);
825
		$href = $href ?: $key;
826
827
		if (NULL === $params) {
828
			$params = [$this->primary_key];
829
		}
830
831
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
832
	}
833
834
835
	/**
836
	 * Create action callback
837
	 * @param string     $key
838
	 * @param string     $name
839
	 * @return Column\Action
840
	 */
841
	public function addActionCallback($key, $name, $callback = NULL)
842
	{
843
		$this->addActionCheck($key);
844
		$params = ['__id' => $this->primary_key];
845
846
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
847
848
		if ($callback) {
849
			if (!is_callable($callback)) {
850
				throw new DataGridException('ActionCallback callback has to be callable.');
851
			}
852
853
			$action->onClick[] = $callback;
854
		}
855
856
		return $action;
857
	}
858
859
860
	/**
861
	 * Get existing action
862
	 * @param  string       $key
863
	 * @return Column\Action
864
	 * @throws DataGridException
865
	 */
866
	public function getAction($key)
867
	{
868
		if (!isset($this->actions[$key])) {
869
			throw new DataGridException("There is no action at key [$key] defined.");
870
		}
871
872
		return $this->actions[$key];
873
	}
874
875
876
	/**
877
	 * Remove action
878
	 * @param string $key
879
	 * @return void
880
	 */
881
	public function removeAction($key)
882
	{
883
		unset($this->actions[$key]);
884
	}
885
886
887
	/**
888
	 * Check whether given key already exists in $this->filters
889
	 * @param  string $key
890
	 * @throws DataGridException
891
	 */
892
	protected function addActionCheck($key)
893
	{
894
		if (isset($this->actions[$key])) {
895
			throw new DataGridException("There is already action at key [$key] defined.");
896
		}
897
	}
898
899
900
	/********************************************************************************
901
	 *                                    FILTERS                                   *
902
	 ********************************************************************************/
903
904
905
	/**
906
	 * Add filter fot text search
907
	 * @param string       $key
908
	 * @param string       $name
909
	 * @param array|string $columns
910
	 * @return Filter\FilterText
911
	 * @throws DataGridException
912
	 */
913
	public function addFilterText($key, $name, $columns = NULL)
914
	{
915
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
916
917
		if (!is_array($columns)) {
918
			throw new DataGridException("Filter Text can except only array or string.");
919
		}
920
921
		$this->addFilterCheck($key);
922
923
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
924
	}
925
926
927
	/**
928
	 * Add select box filter
929
	 * @param string $key
930
	 * @param string $name
931
	 * @param array  $options
932
	 * @param string $column
933
	 * @return Filter\FilterSelect
934
	 * @throws DataGridException
935
	 */
936 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...
937
	{
938
		$column = $column ?: $key;
939
940
		if (!is_string($column)) {
941
			throw new DataGridException("Filter Select can only filter through one column.");
942
		}
943
944
		$this->addFilterCheck($key);
945
946
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
947
	}
948
949
950
	/**
951
	 * Add datepicker filter
952
	 * @param string $key
953
	 * @param string $name
954
	 * @param string $column
955
	 * @return Filter\FilterDate
956
	 * @throws DataGridException
957
	 */
958
	public function addFilterDate($key, $name, $column = NULL)
959
	{
960
		$column = $column ?: $key;
961
962
		if (!is_string($column)) {
963
			throw new DataGridException("FilterDate can only filter through one column.");
964
		}
965
966
		$this->addFilterCheck($key);
967
968
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
969
	}
970
971
972
	/**
973
	 * Add range filter (from - to)
974
	 * @param string $key
975
	 * @param string $name
976
	 * @param string $column
977
	 * @return Filter\FilterRange
978
	 * @throws DataGridException
979
	 */
980 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...
981
	{
982
		$column = $column ?: $key;
983
984
		if (!is_string($column)) {
985
			throw new DataGridException("FilterRange can only filter through one column.");
986
		}
987
988
		$this->addFilterCheck($key);
989
990
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
991
	}
992
993
994
	/**
995
	 * Add datepicker filter (from - to)
996
	 * @param string $key
997
	 * @param string $name
998
	 * @param string $column
999
	 * @return Filter\FilterDateRange
1000
	 * @throws DataGridException
1001
	 */
1002 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...
1003
	{
1004
		$column = $column ?: $key;
1005
1006
		if (!is_string($column)) {
1007
			throw new DataGridException("FilterDateRange can only filter through one column.");
1008
		}
1009
1010
		$this->addFilterCheck($key);
1011
1012
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1013
	}
1014
1015
1016
	/**
1017
	 * Check whether given key already exists in $this->filters
1018
	 * @param  string $key
1019
	 * @throws DataGridException
1020
	 */
1021
	protected function addFilterCheck($key)
1022
	{
1023
		if (isset($this->filters[$key])) {
1024
			throw new DataGridException("There is already action at key [$key] defined.");
1025
		}
1026
	}
1027
1028
1029
	/**
1030
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1031
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1032
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1033
	 */
1034
	public function assableFilters()
1035
	{
1036
		foreach ($this->filter as $key => $value) {
1037
			if (!isset($this->filters[$key])) {
1038
				$this->deleteSesssionData($key);
1039
1040
				continue;
1041
			}
1042
1043
			if (is_array($value) || $value instanceof \Traversable) {
1044
				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...
1045
					$this->filters[$key]->setValue($value);
1046
				}
1047
			} else {
1048
				if ($value !== '' && $value !== NULL) {
1049
					$this->filters[$key]->setValue($value);
1050
				}
1051
			}
1052
		}
1053
1054
		foreach ($this->columns as $column) {
1055
			if (isset($this->sort[$column->getSortingColumn()])) {
1056
				$column->setSort($this->sort);
1057
			}
1058
		}
1059
1060
		return $this->filters;
1061
	}
1062
1063
1064
	/**
1065
	 * Remove filter
1066
	 * @param string $key
1067
	 * @return void
1068
	 */
1069
	public function removeFilter($key)
1070
	{
1071
		unset($this->filters[$key]);
1072
	}
1073
1074
1075
	/********************************************************************************
1076
	 *                                  FILTERING                                   *
1077
	 ********************************************************************************/
1078
1079
1080
	/**
1081
	 * Is filter active?
1082
	 * @return boolean
1083
	 */
1084
	public function isFilterActive()
1085
	{
1086
		$is_filter = ArraysHelper::testTruthy($this->filter);
1087
1088
		return ($is_filter) || $this->force_filter_active;
1089
	}
1090
1091
1092
	/**
1093
	 * Tell that filter is active from whatever reasons
1094
	 * return static
1095
	 */
1096
	public function setFilterActive()
1097
	{
1098
		$this->force_filter_active = TRUE;
1099
1100
		return $this;
1101
	}
1102
1103
1104
	/**
1105
	 * If we want to sent some initial filter
1106
	 * @param array $filter
1107
	 * @return static
1108
	 */
1109
	public function setFilter(array $filter)
1110
	{
1111
		$this->filter = $filter;
1112
1113
		return $this;
1114
	}
1115
1116
1117
	/**
1118
	 * FilterAndGroupAction form factory
1119
	 * @return Form
1120
	 */
1121
	public function createComponentFilter()
1122
	{
1123
		$form = new Form($this, 'filter');
1124
1125
		$form->setMethod('get');
1126
1127
		$form->setTranslator($this->getTranslator());
1128
1129
		/**
1130
		 * InlineEdit part
1131
		 */
1132
		$inline_edit_container = $form->addContainer('inline_edit');
1133
1134
		if ($this->inlineEdit instanceof InlineEdit) {
1135
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1136
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel');
1137
1138
			$this->inlineEdit->onControlAdd($inline_edit_container);
1139
		}
1140
1141
		/**
1142
		 * Filter part
1143
		 */
1144
		$filter_container = $form->addContainer('filter');
1145
1146
		foreach ($this->filters as $filter) {
1147
			$filter->addToFormContainer($filter_container);
1148
		}
1149
1150
		/**
1151
		 * Group action part
1152
		 */
1153
		$group_action_container = $form->addContainer('group_action');
1154
1155
		if ($this->hasGroupActions()) {
1156
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1157
		}
1158
1159
		$form->setDefaults(['filter' => $this->filter]);
1160
1161
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1162
1163
		return $form;
1164
	}
1165
1166
1167
	/**
1168
	 * Set $this->filter values after filter form submitted
1169
	 * @param  Form $form
1170
	 * @return void
1171
	 */
1172
	public function filterSucceeded(Form $form)
1173
	{
1174
		$values = $form->getValues();
1175
1176
		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...
1177
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1178
				return;
1179
			}
1180
		}
1181
1182
		$inline_edit = $form['inline_edit'];
1183
1184
		if (isset($inline_edit) && isset($inline_edit['submit']) && isset($inline_edit['cancel'])) {
1185
			if ($inline_edit['submit']->isSubmittedBy() || $inline_edit['cancel']->isSubmittedBy()) {
1186
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1187
				$primary_where_column = $form->getHttpData(
1188
					Form::DATA_LINE,
1189
					'inline_edit[_primary_where_column]'
1190
				);
1191
1192
				if ($inline_edit['submit']->isSubmittedBy()) {
1193
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1194
1195
					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...
1196
						$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...
1197
					}
1198
				}
1199
1200
				$this->redrawItem($id, $primary_where_column);
1201
1202
				return;
1203
			}
1204
		}
1205
1206
		$values = $values['filter'];
1207
1208
		foreach ($values as $key => $value) {
1209
			/**
1210
			 * Session stuff
1211
			 */
1212
			$this->saveSessionData($key, $value);
1213
1214
			/**
1215
			 * Other stuff
1216
			 */
1217
			$this->filter[$key] = $value;
1218
		}
1219
1220
		$this->reload();
1221
	}
1222
1223
1224
	/**
1225
	 * Should be datagrid filters rendered separately?
1226
	 * @param boolean $out
1227
	 * @return static
1228
	 */
1229
	public function setOuterFilterRendering($out = TRUE)
1230
	{
1231
		$this->outer_filter_rendering = (bool) $out;
1232
1233
		return $this;
1234
	}
1235
1236
1237
	/**
1238
	 * Are datagrid filters rendered separately?
1239
	 * @return boolean
1240
	 */
1241
	public function hasOuterFilterRendering()
1242
	{
1243
		return $this->outer_filter_rendering;
1244
	}
1245
1246
1247
	/**
1248
	 * Try to restore session stuff
1249
	 * @return void
1250
	 */
1251
	public function findSessionFilters()
1252
	{
1253
		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...
1254
			return;
1255
		}
1256
1257
		if (!$this->remember_state) {
1258
			return;
1259
		}
1260
1261
		if ($page = $this->getSessionData('_grid_page')) {
1262
			$this->page = $page;
1263
		}
1264
1265
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1266
			$this->per_page = $per_page;
1267
		}
1268
1269
		if ($sort = $this->getSessionData('_grid_sort')) {
1270
			$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...
1271
		}
1272
1273
		foreach ($this->getSessionData() as $key => $value) {
1274
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_hidden_columns'])) {
1275
				$this->filter[$key] = $value;
1276
			}
1277
		}
1278
	}
1279
1280
1281
	/********************************************************************************
1282
	 *                                    EXPORTS                                   *
1283
	 ********************************************************************************/
1284
1285
1286
	/**
1287
	 * Add export of type callback
1288
	 * @param string $text
1289
	 * @param callable $callback
1290
	 * @param boolean $filtered
1291
	 * @return Export\Export
1292
	 */
1293
	public function addExportCallback($text, $callback, $filtered = FALSE)
1294
	{
1295
		if (!is_callable($callback)) {
1296
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1297
		}
1298
1299
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1300
	}
1301
1302
1303
	/**
1304
	 * Add already implemented csv export
1305
	 * @param string      $text
1306
	 * @param string      $csv_file_name
1307
	 * @param string|null $output_encoding
1308
	 * @param string|null $delimiter
1309
	 * @return Export\Export
1310
	 */
1311
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1312
	{
1313
		return $this->addToExports(new Export\ExportCsv(
1314
			$text,
1315
			$csv_file_name,
1316
			FALSE,
1317
			$output_encoding,
1318
			$delimiter
1319
		));
1320
	}
1321
1322
1323
	/**
1324
	 * Add already implemented csv export, but for filtered data
1325
	 * @param string      $text
1326
	 * @param string      $csv_file_name
1327
	 * @param string|null $output_encoding
1328
	 * @param string|null $delimiter
1329
	 * @return Export\Export
1330
	 */
1331
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1332
	{
1333
		return $this->addToExports(new Export\ExportCsv(
1334
			$text,
1335
			$csv_file_name,
1336
			TRUE,
1337
			$output_encoding,
1338
			$delimiter
1339
		));
1340
	}
1341
1342
1343
	/**
1344
	 * Add export to array
1345
	 * @param Export\Export $export
1346
	 * @return Export\Export
1347
	 */
1348
	protected function addToExports(Export\Export $export)
1349
	{
1350
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1351
1352
		$export->setLink($this->link('export!', ['id' => $id]));
1353
1354
		return $this->exports[$id] = $export;
1355
	}
1356
1357
1358
	public function resetExportsLinks()
1359
	{
1360
		foreach ($this->exports as $id => $export) {
1361
			$export->setLink($this->link('export!', ['id' => $id]));
1362
		}
1363
	}
1364
1365
1366
	/********************************************************************************
1367
	 *                                 GROUP ACTIONS                                *
1368
	 ********************************************************************************/
1369
1370
1371
	/**
1372
	 * Add group actino
1373
	 * @param string $title
1374
	 * @param array  $options
1375
	 * @return GroupAction\GroupAction
1376
	 *
1377
	 * @deprecated
1378
	 */
1379
	public function addGroupAction($title, $options = [])
1380
	{
1381
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1382
	}
1383
1384
	/**
1385
	 * Add group action (select box)
1386
	 * @param string $title
1387
	 * @param array  $options
1388
	 * @return GroupAction\GroupAction
1389
	 */
1390
	public function addGroupSelectAction($title, $options = [])
1391
	{
1392
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1393
	}
1394
1395
	/**
1396
	 * Add group action (text input)
1397
	 * @param string $title
1398
	 * @return GroupAction\GroupAction
1399
	 */
1400
	public function addGroupTextAction($title)
1401
	{
1402
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1403
	}
1404
1405
	/**
1406
	 * Get collection of all group actions
1407
	 * @return GroupAction\GroupActionCollection
1408
	 */
1409
	public function getGroupActionCollection()
1410
	{
1411
		if (!$this->group_action_collection) {
1412
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1413
		}
1414
1415
		return $this->group_action_collection;
1416
	}
1417
1418
1419
	/**
1420
	 * Has datagrid some group actions?
1421
	 * @return boolean
1422
	 */
1423
	public function hasGroupActions()
1424
	{
1425
		return (bool) $this->group_action_collection;
1426
	}
1427
1428
1429
	/********************************************************************************
1430
	 *                                   HANDLERS                                   *
1431
	 ********************************************************************************/
1432
1433
1434
	/**
1435
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1436
	 * @param  int  $page
1437
	 * @return void
1438
	 */
1439
	public function handlePage($page)
1440
	{
1441
		/**
1442
		 * Session stuff
1443
		 */
1444
		$this->page = $page;
1445
		$this->saveSessionData('_grid_page', $page);
1446
1447
		$this->reload(['table']);
1448
	}
1449
1450
1451
	/**
1452
	 * Handler for sorting
1453
	 * @param array $sort
1454
	 * @return void
1455
	 */
1456
	public function handleSort(array $sort)
1457
	{
1458
		$new_sort = [];
1459
1460
		/**
1461
		 * Find apropirate column
1462
		 */
1463
		foreach ($sort as $key => $value) {
1464
			if (empty($this->columns[$key])) {
1465
				throw new DataGridException("Column <$key> not found");
1466
			}
1467
1468
			$column = $this->columns[$key];
1469
			$new_sort = [$column->getSortingColumn() => $value];
1470
1471
			/**
1472
			 * Pagination may be reseted after sorting
1473
			 */
1474
			if ($column->sortableResetPagination()) {
1475
				$this->page = 1;
1476
				$this->saveSessionData('_grid_page', 1);
1477
			}
1478
1479
			/**
1480
			 * Custom sorting callback may be applied
1481
			 */
1482
			if ($column->getSortableCallback()) {
1483
				$this->sort_callback = $column->getSortableCallback();
1484
			}
1485
		}
1486
1487
		/**
1488
		 * Session stuff
1489
		 */
1490
		$this->sort = $new_sort;
1491
		$this->saveSessionData('_grid_sort', $this->sort);
1492
1493
		$this->reload(['table']);
1494
	}
1495
1496
1497
	/**
1498
	 * handler for reseting the filter
1499
	 * @return void
1500
	 */
1501
	public function handleResetFilter()
1502
	{
1503
		/**
1504
		 * Session stuff
1505
		 */
1506
		$this->deleteSesssionData('_grid_page');
1507
1508
		foreach ($this->getSessionData() as $key => $value) {
1509
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1510
				$this->deleteSesssionData($key);
1511
			}
1512
		}
1513
1514
		$this->filter = [];
1515
1516
		$this->reload(['grid']);
1517
	}
1518
1519
1520
	/**
1521
	 * Handler for export
1522
	 * @param  int $id Key for particular export class in array $this->exports
1523
	 * @return void
1524
	 */
1525
	public function handleExport($id)
1526
	{
1527
		if (!isset($this->exports[$id])) {
1528
			throw new Nette\Application\ForbiddenRequestException;
1529
		}
1530
1531
		if (!empty($this->columns_export_order)) {
1532
			$this->setColumnsOrder($this->columns_export_order);
1533
		}
1534
1535
		$export = $this->exports[$id];
1536
1537
		if ($export->isFiltered()) {
1538
			$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...
1539
			$filter    = $this->assableFilters();
1540
		} else {
1541
			$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...
1542
			$filter    = [];
1543
		}
1544
1545
		if (NULL === $this->dataModel) {
1546
			throw new DataGridException('You have to set a data source first.');
1547
		}
1548
1549
		$rows = [];
1550
1551
		$items = Nette\Utils\Callback::invokeArgs(
1552
			[$this->dataModel, 'filterData'], [
1553
				NULL,
1554
				new Sorting($this->sort, $this->sort_callback),
1555
				$filter
1556
			]
1557
		);
1558
1559
		foreach ($items as $item) {
1560
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1561
		}
1562
1563
		if ($export instanceof Export\ExportCsv) {
1564
			$export->invoke($rows, $this);
1565
		} else {
1566
			$export->invoke($items, $this);
1567
		}
1568
1569
		if ($export->isAjax()) {
1570
			$this->reload();
1571
		}
1572
	}
1573
1574
1575
	/**
1576
	 * Handler for getting children of parent item (e.g. category)
1577
	 * @param  int $parent
1578
	 * @return void
1579
	 */
1580
	public function handleGetChildren($parent)
1581
	{
1582
		$this->setDataSource(
1583
			call_user_func($this->tree_view_children_callback, $parent)
1584
		);
1585
1586
		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...
1587
			$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...
1588
			$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...
1589
1590
			$this->redrawControl('items');
1591
1592
			$this->onRedraw();
1593
		} else {
1594
			$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...
1595
		}
1596
	}
1597
1598
1599
	/**
1600
	 * Handler for getting item detail
1601
	 * @param  mixed $id
1602
	 * @return void
1603
	 */
1604
	public function handleGetItemDetail($id)
1605
	{
1606
		$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...
1607
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1608
1609
		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...
1610
			$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...
1611
			$this->redrawControl('items');
1612
1613
			$this->onRedraw();
1614
		} else {
1615
			$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...
1616
		}
1617
	}
1618
1619
1620
	/**
1621
	 * Handler for inline editing
1622
	 * @param  mixed $id
1623
	 * @param  mixed $key
1624
	 * @return void
1625
	 */
1626
	public function handleEdit($id, $key)
1627
	{
1628
		$column = $this->getColumn($key);
1629
		$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...
1630
1631
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1632
	}
1633
1634
1635
	/**
1636
	 * Redraw $this
1637
	 * @return void
1638
	 */
1639
	public function reload($snippets = [])
1640
	{
1641
		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...
1642
			$this->redrawControl('tbody');
1643
			$this->redrawControl('pagination');
1644
1645
			/**
1646
			 * manualy reset exports links...
1647
			 */
1648
			$this->resetExportsLinks();
1649
			$this->redrawControl('exports');
1650
1651
			foreach ($snippets as $snippet) {
1652
				$this->redrawControl($snippet);
1653
			}
1654
1655
			$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...
1656
1657
			$this->onRedraw();
1658
		} else {
1659
			$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...
1660
		}
1661
	}
1662
1663
1664
	/**
1665
	 * Handler for column status
1666
	 * @param  string $id
1667
	 * @param  string $key
1668
	 * @param  string $value
1669
	 * @return void
1670
	 */
1671
	public function handleChangeStatus($id, $key, $value)
1672
	{
1673
		if (empty($this->columns[$key])) {
1674
			throw new DataGridException("ColumnStatus[$key] does not exist");
1675
		}
1676
1677
		$this->columns[$key]->onChange($id, $value);
1678
	}
1679
1680
1681
	/**
1682
	 * Redraw just one row via ajax
1683
	 * @param  int   $id
1684
	 * @param  mixed $primary_where_column
1685
	 * @return void
1686
	 */
1687
	public function redrawItem($id, $primary_where_column = NULL)
1688
	{
1689
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1690
1691
		$this->redrawControl('items');
1692
		$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...
1693
1694
		$this->onRedraw();
1695
	}
1696
1697
1698
	/**
1699
	 * Tell datagrid to display all columns
1700
	 * @return void
1701
	 */
1702
	public function handleShowAllColumns()
1703
	{
1704
		$this->deleteSesssionData('_grid_hidden_columns');
1705
1706
		$this->redrawControl();
1707
1708
		$this->onRedraw();
1709
	}
1710
1711
1712
	/**
1713
	 * Reveal particular column
1714
	 * @param  string $column
1715
	 * @return void
1716
	 */
1717 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...
1718
	{
1719
		$columns = $this->getSessionData('_grid_hidden_columns');
1720
1721
		if (!empty($columns)) {
1722
			$pos = array_search($column, $columns);
1723
1724
			if ($pos !== FALSE) {
1725
				unset($columns[$pos]);
1726
			}
1727
		}
1728
1729
		$this->saveSessionData('_grid_hidden_columns', $columns);
1730
1731
		$this->redrawControl();
1732
1733
		$this->onRedraw();
1734
	}
1735
1736
1737
	/**
1738
	 * Notice datagrid to not display particular columns
1739
	 * @param  string $column
1740
	 * @return void
1741
	 */
1742 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...
1743
	{
1744
		/**
1745
		 * Store info about hiding a column to session
1746
		 */
1747
		$columns = $this->getSessionData('_grid_hidden_columns');
1748
1749
		if (empty($columns)) {
1750
			$columns = [$column];
1751
		} else if (!in_array($column, $columns)) {
1752
			array_push($columns, $column);
1753
		}
1754
1755
		$this->saveSessionData('_grid_hidden_columns', $columns);
1756
1757
		$this->redrawControl();
1758
1759
		$this->onRedraw();
1760
	}
1761
1762
1763
	public function handleActionCallback($__key, $__id)
1764
	{
1765
		$action = $this->getAction($__key);
1766
1767
		if (!($action instanceof Column\ActionCallback)) {
1768
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1769
		}
1770
1771
		$action->onClick($__id);
1772
	}
1773
1774
1775
	/********************************************************************************
1776
	 *                                  PAGINATION                                  *
1777
	 ********************************************************************************/
1778
1779
1780
	/**
1781
	 * Set options of select "items_per_page"
1782
	 * @param array $items_per_page_list
1783
	 * @return static
1784
	 */
1785
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1786
	{
1787
		$this->items_per_page_list = $items_per_page_list;
1788
1789
		if ($include_all) {
1790
			$this->items_per_page_list[] = 'all';
1791
		}
1792
1793
		return $this;
1794
	}
1795
1796
1797
	/**
1798
	 * Paginator factory
1799
	 * @return Components\DataGridPaginator\DataGridPaginator
1800
	 */
1801
	public function createComponentPaginator()
1802
	{
1803
		/**
1804
		 * Init paginator
1805
		 */
1806
		$component = new Components\DataGridPaginator\DataGridPaginator(
1807
			$this->getTranslator()
1808
		);
1809
		$paginator = $component->getPaginator();
1810
1811
		$paginator->setPage($this->page);
1812
		$paginator->setItemsPerPage($this->getPerPage());
1813
1814
		return $component;
1815
	}
1816
1817
1818
	/**
1819
	 * PerPage form factory
1820
	 * @return Form
1821
	 */
1822
	public function createComponentPerPage()
1823
	{
1824
		$form = new Form;
1825
1826
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1827
			->setValue($this->getPerPage());
1828
1829
		$form->addSubmit('submit', '');
1830
1831
		$saveSessionData = [$this, 'saveSessionData'];
1832
1833
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1834
			/**
1835
			 * Session stuff
1836
			 */
1837
			$saveSessionData('_grid_per_page', $values->per_page);
1838
1839
			/**
1840
			 * Other stuff
1841
			 */
1842
			$this->per_page = $values->per_page;
1843
			$this->reload();
1844
		};
1845
1846
		return $form;
1847
	}
1848
1849
1850
	/**
1851
	 * Get parameter per_page
1852
	 * @return int
1853
	 */
1854
	public function getPerPage()
1855
	{
1856
		$items_per_page_list = $this->getItemsPerPageList();
1857
1858
		$per_page = $this->per_page ?: reset($items_per_page_list);
1859
1860
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1861
			$per_page = reset($items_per_page_list);
1862
		}
1863
1864
		return $per_page;
1865
	}
1866
1867
1868
	/**
1869
	 * Get associative array of items_per_page_list
1870
	 * @return array
1871
	 */
1872
	public function getItemsPerPageList()
1873
	{
1874
		if (empty($this->items_per_page_list)) {
1875
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1876
		}
1877
1878
		$list = array_flip($this->items_per_page_list);
1879
1880
		foreach ($list as $key => $value) {
1881
			$list[$key] = $key;
1882
		}
1883
1884
		if (array_key_exists('all', $list)) {
1885
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1886
		}
1887
1888
		return $list;
1889
	}
1890
1891
1892
	/**
1893
	 * Order Grid to "be paginated"
1894
	 * @param bool $do
1895
	 * @return static
1896
	 */
1897
	public function setPagination($do)
1898
	{
1899
		$this->do_paginate = (bool) $do;
1900
1901
		return $this;
1902
	}
1903
1904
1905
	/**
1906
	 * Tell whether Grid is paginated
1907
	 * @return bool
1908
	 */
1909
	public function isPaginated()
1910
	{
1911
		return $this->do_paginate;
1912
	}
1913
1914
1915
	/**
1916
	 * Return current paginator class
1917
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1918
	 */
1919
	public function getPaginator()
1920
	{
1921
		if ($this->isPaginated() && $this->per_page !== 'all') {
1922
			return $this['paginator'];
1923
		}
1924
1925
		return NULL;
1926
	}
1927
1928
1929
	/********************************************************************************
1930
	 *                                     I18N                                     *
1931
	 ********************************************************************************/
1932
1933
1934
	/**
1935
	 * Set datagrid translator
1936
	 * @param Nette\Localization\ITranslator $translator
1937
	 * @return static
1938
	 */
1939
	public function setTranslator(Nette\Localization\ITranslator $translator)
1940
	{
1941
		$this->translator = $translator;
1942
1943
		return $this;
1944
	}
1945
1946
1947
	/**
1948
	 * Get translator for datagrid
1949
	 * @return Nette\Localization\ITranslator
1950
	 */
1951
	public function getTranslator()
1952
	{
1953
		if (!$this->translator) {
1954
			$this->translator = new Localization\SimpleTranslator;
1955
		}
1956
1957
		return $this->translator;
1958
	}
1959
1960
1961
	/********************************************************************************
1962
	 *                                 COLUMNS ORDER                                *
1963
	 ********************************************************************************/
1964
1965
1966
	/**
1967
	 * Set order of datagrid columns
1968
	 * @param array $order
1969
	 * @return static
1970
	 */
1971
	public function setColumnsOrder($order)
1972
	{
1973
		$new_order = [];
1974
1975
		foreach ($order as $key) {
1976
			if (isset($this->columns[$key])) {
1977
				$new_order[$key] = $this->columns[$key];
1978
			}
1979
		}
1980
1981
		if (sizeof($new_order) === sizeof($this->columns)) {
1982
			$this->columns = $new_order;
1983
		} else {
1984
			throw new DataGridException('When changing columns order, you have to specify all columns');
1985
		}
1986
1987
		return $this;
1988
	}
1989
1990
1991
	/**
1992
	 * Columns order may be different for export and normal grid
1993
	 * @param array $order
1994
	 */
1995
	public function setColumnsExportOrder($order)
1996
	{
1997
		$this->columns_export_order = (array) $order;
1998
	}
1999
2000
2001
	/********************************************************************************
2002
	 *                                SESSION & URL                                 *
2003
	 ********************************************************************************/
2004
2005
2006
	/**
2007
	 * Find some unique session key name
2008
	 * @return string
2009
	 */
2010
	public function getSessionSectionName()
2011
	{
2012
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2013
	}
2014
2015
2016
	/**
2017
	 * Should datagrid remember its filters/pagination/etc using session?
2018
	 * @param bool $remember
2019
	 * @return static
2020
	 */
2021
	public function setRememberState($remember = TRUE)
2022
	{
2023
		$this->remember_state = (bool) $remember;
2024
2025
		return $this;
2026
	}
2027
2028
2029
	/**
2030
	 * Should datagrid refresh url using history API?
2031
	 * @param bool $refresh
2032
	 * @return static
2033
	 */
2034
	public function setRefreshUrl($refresh = TRUE)
2035
	{
2036
		$this->refresh_url = (bool) $refresh;
2037
2038
2039
		return $this;
2040
	}
2041
2042
2043
	/**
2044
	 * Get session data if functionality is enabled
2045
	 * @param  string $key
2046
	 * @return mixed
2047
	 */
2048
	public function getSessionData($key = NULL, $default_value = NULL)
2049
	{
2050
		if (!$this->remember_state) {
2051
			return $key ? $default_value : [];
2052
		}
2053
2054
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2055
	}
2056
2057
2058
	/**
2059
	 * Save session data - just if it is enabled
2060
	 * @param  string $key
2061
	 * @param  mixed  $value
2062
	 * @return void
2063
	 */
2064
	public function saveSessionData($key, $value)
2065
	{
2066
		if ($this->remember_state) {
2067
			$this->grid_session->{$key} = $value;
2068
		}
2069
	}
2070
2071
2072
	/**
2073
	 * Delete session data
2074
	 * @return void
2075
	 */
2076
	public function deleteSesssionData($key)
2077
	{
2078
		unset($this->grid_session->{$key});
2079
	}
2080
2081
2082
	/********************************************************************************
2083
	 *                                  ITEM DETAIL                                 *
2084
	 ********************************************************************************/
2085
2086
2087
	/**
2088
	 * Get items detail parameters
2089
	 * @return array
2090
	 */
2091
	public function getItemsDetail()
2092
	{
2093
		return $this->items_detail;
2094
	}
2095
2096
2097
	/**
2098
	 * Items can have thair detail - toggled
2099
	 * @param mixed $detail callable|string|bool
2100
	 * @param bool|NULL $primary_where_column
2101
	 * @return Column\ItemDetail
2102
	 */
2103
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2104
	{
2105
		if ($this->isSortable()) {
2106
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2107
		}
2108
2109
		$this->items_detail = new Column\ItemDetail(
2110
			$this,
2111
			$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...
2112
		);
2113
2114
		if (is_string($detail)) {
2115
			/**
2116
			 * Item detail will be in separate template
2117
			 */
2118
			$this->items_detail->setType('template');
2119
			$this->items_detail->setTemplate($detail);
2120
2121
		} else if (is_callable($detail)) {
2122
			/**
2123
			 * Item detail will be rendered via custom callback renderer
2124
			 */
2125
			$this->items_detail->setType('renderer');
2126
			$this->items_detail->setRenderer($detail);
2127
2128
		} else if (TRUE === $detail) {
2129
			/**
2130
			 * Item detail will be rendered probably via block #detail
2131
			 */
2132
			$this->items_detail->setType('block');
2133
2134
		} else {
2135
			throw new DataGridException(
2136
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2137
			);
2138
		}
2139
2140
		return $this->items_detail;
2141
	}
2142
2143
2144
	/********************************************************************************
2145
	 *                                ROW PRIVILEGES                                *
2146
	 ********************************************************************************/
2147
2148
2149
	/**
2150
	 * @param  callable $condition
2151
	 * @return void
2152
	 */
2153
	public function allowRowsGroupAction(callable $condition)
2154
	{
2155
		$this->row_conditions['group_action'] = $condition;
2156
	}
2157
2158
2159
	/**
2160
	 * @param  string   $key
2161
	 * @param  callable $condition
2162
	 * @return void
2163
	 */
2164
	public function allowRowsAction($key, callable $condition)
2165
	{
2166
		$this->row_conditions['action'][$key] = $condition;
2167
	}
2168
2169
2170
	/**
2171
	 * @param  string      $name
2172
	 * @param  string|null $key
2173
	 * @return bool|callable
2174
	 */
2175
	public function getRowCondition($name, $key = NULL)
2176
	{
2177
		if (!isset($this->row_conditions[$name])) {
2178
			return FALSE;
2179
		}
2180
2181
		$condition = $this->row_conditions[$name];
2182
2183
		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...
2184
			return $condition;
2185
		}
2186
2187
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2188
	}
2189
2190
2191
	/********************************************************************************
2192
	 *                               COLUMN CALLBACK                                *
2193
	 ********************************************************************************/
2194
2195
2196
	/**
2197
	 * @param  string   $key
2198
	 * @param  callable $callback
2199
	 * @return void
2200
	 */
2201
	public function addColumnCallback($key, callable $callback)
2202
	{
2203
		$this->column_callbacks[$key] = $callback;
2204
	}
2205
2206
2207
	/**
2208
	 * @param  string $key
2209
	 * @return callable|null
2210
	 */
2211
	public function getColumnCallback($key)
2212
	{
2213
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2214
	}
2215
2216
2217
	/********************************************************************************
2218
	 *                                 INLINE EDIT                                  *
2219
	 ********************************************************************************/
2220
2221
2222
	/**
2223
	 * @return InlineEdit
2224
	 */
2225
	public function addInlineEdit($primary_where_column = NULL)
2226
	{
2227
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2228
2229
		return $this->inlineEdit;
2230
	}
2231
2232
2233
	/**
2234
	 * @return InlineEdit|null
2235
	 */
2236
	public function getInlineEdit()
2237
	{
2238
		return $this->inlineEdit;
2239
	}
2240
2241
2242
	public function handleInlineEdit($id)
2243
	{
2244
		if ($this->inlineEdit) {
2245
			$this->inlineEdit->setItemId($id);
2246
2247
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2248
2249
			$this['filter']['inline_edit']->addHidden('_id', $id);
2250
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2251
2252
			$this->redrawItem($id, $primary_where_column);
2253
		}
2254
	}
2255
2256
2257
	/********************************************************************************
2258
	 *                               HIDEABLE COLUMNS                               *
2259
	 ********************************************************************************/
2260
2261
2262
	/**
2263
	 * Can datagrid hide colums?
2264
	 * @return boolean
2265
	 */
2266
	public function canHideColumns()
2267
	{
2268
		return (bool) $this->can_hide_columns;
2269
	}
2270
2271
2272
	/**
2273
	 * Order Grid to set columns hideable.
2274
	 * @return static
2275
	 */
2276
	public function setColumnsHideable()
2277
	{
2278
		$this->can_hide_columns = TRUE;
2279
2280
		return $this;
2281
	}
2282
2283
2284
	/********************************************************************************
2285
	 *                                   INTERNAL                                   *
2286
	 ********************************************************************************/
2287
2288
2289
	/**
2290
	 * Get cont of columns
2291
	 * @return int
2292
	 */
2293
	public function getColumnsCount()
2294
	{
2295
		$count = sizeof($this->getColumns());
2296
2297
		if (!empty($this->actions) || $this->isSortable() || $this->getItemsDetail()) {
2298
			$count++;
2299
		}
2300
2301
		if ($this->hasGroupActions()) {
2302
			$count++;
2303
		}
2304
2305
		return $count;
2306
	}
2307
2308
2309
	/**
2310
	 * Get primary key of datagrid data source
2311
	 * @return string
2312
	 */
2313
	public function getPrimaryKey()
2314
	{
2315
		return $this->primary_key;
2316
	}
2317
2318
2319
	/**
2320
	 * Get set of set columns
2321
	 * @return Column\IColumn[]
2322
	 */
2323
	public function getColumns()
2324
	{
2325
		foreach ($this->getSessionData('_grid_hidden_columns', []) as $column) {
2326
			if (!empty($this->columns[$column])) {
2327
				$this->columns_visibility[$column] = [
2328
					'visible' => FALSE,
2329
					'name' => $this->columns[$column]->getName()
2330
				];
2331
			}
2332
2333
			$this->removeColumn($column);
2334
		}
2335
2336
		return $this->columns;
2337
	}
2338
2339
2340
	/**
2341
	 * @return PresenterComponent
2342
	 */
2343
	public function getParent()
2344
	{
2345
		$parent = parent::getParent();
2346
2347
		if (!($parent instanceof PresenterComponent)) {
2348
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2349
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2350
			);
2351
		}
2352
2353
		return $parent;
2354
	}
2355
2356
}
2357