Completed
Push — master ( 6ac92e...33f9f0 )
by Pavel
02:48
created

DataGrid::addColumnCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
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
	public function addGroupAction($title, $options = [])
1378
	{
1379
		return $this->getGroupActionCollection()->addGroupAction($title, $options);
1380
	}
1381
1382
1383
	/**
1384
	 * Get collection of all group actions
1385
	 * @return GroupAction\GroupActionCollection
1386
	 */
1387
	public function getGroupActionCollection()
1388
	{
1389
		if (!$this->group_action_collection) {
1390
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1391
		}
1392
1393
		return $this->group_action_collection;
1394
	}
1395
1396
1397
	/**
1398
	 * Has datagrid some group actions?
1399
	 * @return boolean
1400
	 */
1401
	public function hasGroupActions()
1402
	{
1403
		return (bool) $this->group_action_collection;
1404
	}
1405
1406
1407
	/********************************************************************************
1408
	 *                                   HANDLERS                                   *
1409
	 ********************************************************************************/
1410
1411
1412
	/**
1413
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1414
	 * @param  int  $page
1415
	 * @return void
1416
	 */
1417
	public function handlePage($page)
1418
	{
1419
		/**
1420
		 * Session stuff
1421
		 */
1422
		$this->page = $page;
1423
		$this->saveSessionData('_grid_page', $page);
1424
1425
		$this->reload(['table']);
1426
	}
1427
1428
1429
	/**
1430
	 * Handler for sorting
1431
	 * @param array $sort
1432
	 * @return void
1433
	 */
1434
	public function handleSort(array $sort)
1435
	{
1436
		$new_sort = [];
1437
1438
		/**
1439
		 * Find apropirate column
1440
		 */
1441
		foreach ($sort as $key => $value) {
1442
			if (empty($this->columns[$key])) {
1443
				throw new DataGridException("Column <$key> not found");
1444
			}
1445
1446
			$column = $this->columns[$key];
1447
			$new_sort = [$column->getSortingColumn() => $value];
1448
1449
			/**
1450
			 * Pagination may be reseted after sorting
1451
			 */
1452
			if ($column->sortableResetPagination()) {
1453
				$this->page = 1;
1454
				$this->saveSessionData('_grid_page', 1);
1455
			}
1456
1457
			/**
1458
			 * Custom sorting callback may be applied
1459
			 */
1460
			if ($column->getSortableCallback()) {
1461
				$this->sort_callback = $column->getSortableCallback();
1462
			}
1463
		}
1464
1465
		/**
1466
		 * Session stuff
1467
		 */
1468
		$this->sort = $new_sort;
1469
		$this->saveSessionData('_grid_sort', $this->sort);
1470
1471
		$this->reload(['table']);
1472
	}
1473
1474
1475
	/**
1476
	 * handler for reseting the filter
1477
	 * @return void
1478
	 */
1479
	public function handleResetFilter()
1480
	{
1481
		/**
1482
		 * Session stuff
1483
		 */
1484
		$this->deleteSesssionData('_grid_page');
1485
1486
		foreach ($this->getSessionData() as $key => $value) {
1487
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1488
				$this->deleteSesssionData($key);
1489
			}
1490
		}
1491
1492
		$this->filter = [];
1493
1494
		$this->reload(['grid']);
1495
	}
1496
1497
1498
	/**
1499
	 * Handler for export
1500
	 * @param  int $id Key for particular export class in array $this->exports
1501
	 * @return void
1502
	 */
1503
	public function handleExport($id)
1504
	{
1505
		if (!isset($this->exports[$id])) {
1506
			throw new Nette\Application\ForbiddenRequestException;
1507
		}
1508
1509
		if (!empty($this->columns_export_order)) {
1510
			$this->setColumnsOrder($this->columns_export_order);
1511
		}
1512
1513
		$export = $this->exports[$id];
1514
1515
		if ($export->isFiltered()) {
1516
			$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...
1517
			$filter    = $this->assableFilters();
1518
		} else {
1519
			$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...
1520
			$filter    = [];
1521
		}
1522
1523
		if (NULL === $this->dataModel) {
1524
			throw new DataGridException('You have to set a data source first.');
1525
		}
1526
1527
		$rows = [];
1528
1529
		$items = Nette\Utils\Callback::invokeArgs(
1530
			[$this->dataModel, 'filterData'], [
1531
				NULL,
1532
				new Sorting($this->sort, $this->sort_callback),
1533
				$filter
1534
			]
1535
		);
1536
1537
		foreach ($items as $item) {
1538
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1539
		}
1540
1541
		if ($export instanceof Export\ExportCsv) {
1542
			$export->invoke($rows, $this);
1543
		} else {
1544
			$export->invoke($items, $this);
1545
		}
1546
1547
		if ($export->isAjax()) {
1548
			$this->reload();
1549
		}
1550
	}
1551
1552
1553
	/**
1554
	 * Handler for getting children of parent item (e.g. category)
1555
	 * @param  int $parent
1556
	 * @return void
1557
	 */
1558
	public function handleGetChildren($parent)
1559
	{
1560
		$this->setDataSource(
1561
			call_user_func($this->tree_view_children_callback, $parent)
1562
		);
1563
1564
		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...
1565
			$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...
1566
			$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...
1567
1568
			$this->redrawControl('items');
1569
1570
			$this->onRedraw();
1571
		} else {
1572
			$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...
1573
		}
1574
	}
1575
1576
1577
	/**
1578
	 * Handler for getting item detail
1579
	 * @param  mixed $id
1580
	 * @return void
1581
	 */
1582
	public function handleGetItemDetail($id)
1583
	{
1584
		$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...
1585
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1586
1587
		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...
1588
			$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...
1589
			$this->redrawControl('items');
1590
1591
			$this->onRedraw();
1592
		} else {
1593
			$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...
1594
		}
1595
	}
1596
1597
1598
	/**
1599
	 * Handler for inline editing
1600
	 * @param  mixed $id
1601
	 * @param  mixed $key
1602
	 * @return void
1603
	 */
1604
	public function handleEdit($id, $key)
1605
	{
1606
		$column = $this->getColumn($key);
1607
		$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...
1608
1609
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1610
	}
1611
1612
1613
	/**
1614
	 * Redraw $this
1615
	 * @return void
1616
	 */
1617
	public function reload($snippets = [])
1618
	{
1619
		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...
1620
			$this->redrawControl('tbody');
1621
			$this->redrawControl('pagination');
1622
1623
			/**
1624
			 * manualy reset exports links...
1625
			 */
1626
			$this->resetExportsLinks();
1627
			$this->redrawControl('exports');
1628
1629
			foreach ($snippets as $snippet) {
1630
				$this->redrawControl($snippet);
1631
			}
1632
1633
			$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...
1634
1635
			$this->onRedraw();
1636
		} else {
1637
			$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...
1638
		}
1639
	}
1640
1641
1642
	/**
1643
	 * Handler for column status
1644
	 * @param  string $id
1645
	 * @param  string $key
1646
	 * @param  string $value
1647
	 * @return void
1648
	 */
1649
	public function handleChangeStatus($id, $key, $value)
1650
	{
1651
		if (empty($this->columns[$key])) {
1652
			throw new DataGridException("ColumnStatus[$key] does not exist");
1653
		}
1654
1655
		$this->columns[$key]->onChange($id, $value);
1656
	}
1657
1658
1659
	/**
1660
	 * Redraw just one row via ajax
1661
	 * @param  int   $id
1662
	 * @param  mixed $primary_where_column
1663
	 * @return void
1664
	 */
1665
	public function redrawItem($id, $primary_where_column = NULL)
1666
	{
1667
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1668
1669
		$this->redrawControl('items');
1670
		$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...
1671
1672
		$this->onRedraw();
1673
	}
1674
1675
1676
	/**
1677
	 * Tell datagrid to display all columns
1678
	 * @return void
1679
	 */
1680
	public function handleShowAllColumns()
1681
	{
1682
		$this->deleteSesssionData('_grid_hidden_columns');
1683
1684
		$this->redrawControl();
1685
1686
		$this->onRedraw();
1687
	}
1688
1689
1690
	/**
1691
	 * Reveal particular column
1692
	 * @param  string $column
1693
	 * @return void
1694
	 */
1695 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...
1696
	{
1697
		$columns = $this->getSessionData('_grid_hidden_columns');
1698
1699
		if (!empty($columns)) {
1700
			$pos = array_search($column, $columns);
1701
1702
			if ($pos !== FALSE) {
1703
				unset($columns[$pos]);
1704
			}
1705
		}
1706
1707
		$this->saveSessionData('_grid_hidden_columns', $columns);
1708
1709
		$this->redrawControl();
1710
1711
		$this->onRedraw();
1712
	}
1713
1714
1715
	/**
1716
	 * Notice datagrid to not display particular columns
1717
	 * @param  string $column
1718
	 * @return void
1719
	 */
1720 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...
1721
	{
1722
		/**
1723
		 * Store info about hiding a column to session
1724
		 */
1725
		$columns = $this->getSessionData('_grid_hidden_columns');
1726
1727
		if (empty($columns)) {
1728
			$columns = [$column];
1729
		} else if (!in_array($column, $columns)) {
1730
			array_push($columns, $column);
1731
		}
1732
1733
		$this->saveSessionData('_grid_hidden_columns', $columns);
1734
1735
		$this->redrawControl();
1736
1737
		$this->onRedraw();
1738
	}
1739
1740
1741
	public function handleActionCallback($__key, $__id)
1742
	{
1743
		$action = $this->getAction($__key);
1744
1745
		if (!($action instanceof Column\ActionCallback)) {
1746
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1747
		}
1748
1749
		$action->onClick($__id);
1750
	}
1751
1752
1753
	/********************************************************************************
1754
	 *                                  PAGINATION                                  *
1755
	 ********************************************************************************/
1756
1757
1758
	/**
1759
	 * Set options of select "items_per_page"
1760
	 * @param array $items_per_page_list
1761
	 * @return static
1762
	 */
1763
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1764
	{
1765
		$this->items_per_page_list = $items_per_page_list;
1766
1767
		if ($include_all) {
1768
			$this->items_per_page_list[] = 'all';
1769
		}
1770
1771
		return $this;
1772
	}
1773
1774
1775
	/**
1776
	 * Paginator factory
1777
	 * @return Components\DataGridPaginator\DataGridPaginator
1778
	 */
1779
	public function createComponentPaginator()
1780
	{
1781
		/**
1782
		 * Init paginator
1783
		 */
1784
		$component = new Components\DataGridPaginator\DataGridPaginator(
1785
			$this->getTranslator()
1786
		);
1787
		$paginator = $component->getPaginator();
1788
1789
		$paginator->setPage($this->page);
1790
		$paginator->setItemsPerPage($this->getPerPage());
1791
1792
		return $component;
1793
	}
1794
1795
1796
	/**
1797
	 * PerPage form factory
1798
	 * @return Form
1799
	 */
1800
	public function createComponentPerPage()
1801
	{
1802
		$form = new Form;
1803
1804
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1805
			->setValue($this->getPerPage());
1806
1807
		$form->addSubmit('submit', '');
1808
1809
		$saveSessionData = [$this, 'saveSessionData'];
1810
1811
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1812
			/**
1813
			 * Session stuff
1814
			 */
1815
			$saveSessionData('_grid_per_page', $values->per_page);
1816
1817
			/**
1818
			 * Other stuff
1819
			 */
1820
			$this->per_page = $values->per_page;
1821
			$this->reload();
1822
		};
1823
1824
		return $form;
1825
	}
1826
1827
1828
	/**
1829
	 * Get parameter per_page
1830
	 * @return int
1831
	 */
1832
	public function getPerPage()
1833
	{
1834
		$items_per_page_list = $this->getItemsPerPageList();
1835
1836
		$per_page = $this->per_page ?: reset($items_per_page_list);
1837
1838
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1839
			$per_page = reset($items_per_page_list);
1840
		}
1841
1842
		return $per_page;
1843
	}
1844
1845
1846
	/**
1847
	 * Get associative array of items_per_page_list
1848
	 * @return array
1849
	 */
1850
	public function getItemsPerPageList()
1851
	{
1852
		if (empty($this->items_per_page_list)) {
1853
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1854
		}
1855
1856
		$list = array_flip($this->items_per_page_list);
1857
1858
		foreach ($list as $key => $value) {
1859
			$list[$key] = $key;
1860
		}
1861
1862
		if (array_key_exists('all', $list)) {
1863
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1864
		}
1865
1866
		return $list;
1867
	}
1868
1869
1870
	/**
1871
	 * Order Grid to "be paginated"
1872
	 * @param bool $do
1873
	 * @return static
1874
	 */
1875
	public function setPagination($do)
1876
	{
1877
		$this->do_paginate = (bool) $do;
1878
1879
		return $this;
1880
	}
1881
1882
1883
	/**
1884
	 * Tell whether Grid is paginated
1885
	 * @return bool
1886
	 */
1887
	public function isPaginated()
1888
	{
1889
		return $this->do_paginate;
1890
	}
1891
1892
1893
	/**
1894
	 * Return current paginator class
1895
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1896
	 */
1897
	public function getPaginator()
1898
	{
1899
		if ($this->isPaginated() && $this->per_page !== 'all') {
1900
			return $this['paginator'];
1901
		}
1902
1903
		return NULL;
1904
	}
1905
1906
1907
	/********************************************************************************
1908
	 *                                     I18N                                     *
1909
	 ********************************************************************************/
1910
1911
1912
	/**
1913
	 * Set datagrid translator
1914
	 * @param Nette\Localization\ITranslator $translator
1915
	 * @return static
1916
	 */
1917
	public function setTranslator(Nette\Localization\ITranslator $translator)
1918
	{
1919
		$this->translator = $translator;
1920
1921
		return $this;
1922
	}
1923
1924
1925
	/**
1926
	 * Get translator for datagrid
1927
	 * @return Nette\Localization\ITranslator
1928
	 */
1929
	public function getTranslator()
1930
	{
1931
		if (!$this->translator) {
1932
			$this->translator = new Localization\SimpleTranslator;
1933
		}
1934
1935
		return $this->translator;
1936
	}
1937
1938
1939
	/********************************************************************************
1940
	 *                                 COLUMNS ORDER                                *
1941
	 ********************************************************************************/
1942
1943
1944
	/**
1945
	 * Set order of datagrid columns
1946
	 * @param array $order
1947
	 * @return static
1948
	 */
1949
	public function setColumnsOrder($order)
1950
	{
1951
		$new_order = [];
1952
1953
		foreach ($order as $key) {
1954
			if (isset($this->columns[$key])) {
1955
				$new_order[$key] = $this->columns[$key];
1956
			}
1957
		}
1958
1959
		if (sizeof($new_order) === sizeof($this->columns)) {
1960
			$this->columns = $new_order;
1961
		} else {
1962
			throw new DataGridException('When changing columns order, you have to specify all columns');
1963
		}
1964
1965
		return $this;
1966
	}
1967
1968
1969
	/**
1970
	 * Columns order may be different for export and normal grid
1971
	 * @param array $order
1972
	 */
1973
	public function setColumnsExportOrder($order)
1974
	{
1975
		$this->columns_export_order = (array) $order;
1976
	}
1977
1978
1979
	/********************************************************************************
1980
	 *                                SESSION & URL                                 *
1981
	 ********************************************************************************/
1982
1983
1984
	/**
1985
	 * Find some unique session key name
1986
	 * @return string
1987
	 */
1988
	public function getSessionSectionName()
1989
	{
1990
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
1991
	}
1992
1993
1994
	/**
1995
	 * Should datagrid remember its filters/pagination/etc using session?
1996
	 * @param bool $remember
1997
	 * @return static
1998
	 */
1999
	public function setRememberState($remember = TRUE)
2000
	{
2001
		$this->remember_state = (bool) $remember;
2002
2003
		return $this;
2004
	}
2005
2006
2007
	/**
2008
	 * Should datagrid refresh url using history API?
2009
	 * @param bool $refresh
2010
	 * @return static
2011
	 */
2012
	public function setRefreshUrl($refresh = TRUE)
2013
	{
2014
		$this->refresh_url = (bool) $refresh;
2015
2016
2017
		return $this;
2018
	}
2019
2020
2021
	/**
2022
	 * Get session data if functionality is enabled
2023
	 * @param  string $key
2024
	 * @return mixed
2025
	 */
2026
	public function getSessionData($key = NULL, $default_value = NULL)
2027
	{
2028
		if (!$this->remember_state) {
2029
			return $key ? $default_value : [];
2030
		}
2031
2032
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2033
	}
2034
2035
2036
	/**
2037
	 * Save session data - just if it is enabled
2038
	 * @param  string $key
2039
	 * @param  mixed  $value
2040
	 * @return void
2041
	 */
2042
	public function saveSessionData($key, $value)
2043
	{
2044
		if ($this->remember_state) {
2045
			$this->grid_session->{$key} = $value;
2046
		}
2047
	}
2048
2049
2050
	/**
2051
	 * Delete session data
2052
	 * @return void
2053
	 */
2054
	public function deleteSesssionData($key)
2055
	{
2056
		unset($this->grid_session->{$key});
2057
	}
2058
2059
2060
	/********************************************************************************
2061
	 *                                  ITEM DETAIL                                 *
2062
	 ********************************************************************************/
2063
2064
2065
	/**
2066
	 * Get items detail parameters
2067
	 * @return array
2068
	 */
2069
	public function getItemsDetail()
2070
	{
2071
		return $this->items_detail;
2072
	}
2073
2074
2075
	/**
2076
	 * Items can have thair detail - toggled
2077
	 * @param mixed $detail callable|string|bool
2078
	 * @param bool|NULL $primary_where_column
2079
	 * @return Column\ItemDetail
2080
	 */
2081
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2082
	{
2083
		if ($this->isSortable()) {
2084
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2085
		}
2086
2087
		$this->items_detail = new Column\ItemDetail(
2088
			$this,
2089
			$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...
2090
		);
2091
2092
		if (is_string($detail)) {
2093
			/**
2094
			 * Item detail will be in separate template
2095
			 */
2096
			$this->items_detail->setType('template');
2097
			$this->items_detail->setTemplate($detail);
2098
2099
		} else if (is_callable($detail)) {
2100
			/**
2101
			 * Item detail will be rendered via custom callback renderer
2102
			 */
2103
			$this->items_detail->setType('renderer');
2104
			$this->items_detail->setRenderer($detail);
2105
2106
		} else if (TRUE === $detail) {
2107
			/**
2108
			 * Item detail will be rendered probably via block #detail
2109
			 */
2110
			$this->items_detail->setType('block');
2111
2112
		} else {
2113
			throw new DataGridException(
2114
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2115
			);
2116
		}
2117
2118
		return $this->items_detail;
2119
	}
2120
2121
2122
	/********************************************************************************
2123
	 *                                ROW PRIVILEGES                                *
2124
	 ********************************************************************************/
2125
2126
2127
	/**
2128
	 * @param  callable $condition
2129
	 * @return void
2130
	 */
2131
	public function allowRowsGroupAction(callable $condition)
2132
	{
2133
		$this->row_conditions['group_action'] = $condition;
2134
	}
2135
2136
2137
	/**
2138
	 * @param  string   $key
2139
	 * @param  callable $condition
2140
	 * @return void
2141
	 */
2142
	public function allowRowsAction($key, callable $condition)
2143
	{
2144
		$this->row_conditions['action'][$key] = $condition;
2145
	}
2146
2147
2148
	/**
2149
	 * @param  string      $name
2150
	 * @param  string|null $key
2151
	 * @return bool|callable
2152
	 */
2153
	public function getRowCondition($name, $key = NULL)
2154
	{
2155
		if (!isset($this->row_conditions[$name])) {
2156
			return FALSE;
2157
		}
2158
2159
		$condition = $this->row_conditions[$name];
2160
2161
		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...
2162
			return $condition;
2163
		}
2164
2165
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2166
	}
2167
2168
2169
	/********************************************************************************
2170
	 *                               COLUMN CALLBACK                                *
2171
	 ********************************************************************************/
2172
2173
2174
	/**
2175
	 * @param  string   $key
2176
	 * @param  callable $callback
2177
	 * @return void
2178
	 */
2179
	public function addColumnCallback($key, callable $callback)
2180
	{
2181
		$this->column_callbacks[$key] = $callback;
2182
	}
2183
2184
2185
	/**
2186
	 * @param  string $key
2187
	 * @return callable|null
2188
	 */
2189
	public function getColumnCallback($key)
2190
	{
2191
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2192
	}
2193
2194
2195
	/********************************************************************************
2196
	 *                                 INLINE EDIT                                  *
2197
	 ********************************************************************************/
2198
2199
2200
	/**
2201
	 * @return InlineEdit
2202
	 */
2203
	public function addInlineEdit($primary_where_column = NULL)
2204
	{
2205
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2206
2207
		return $this->inlineEdit;
2208
	}
2209
2210
2211
	/**
2212
	 * @return InlineEdit|null
2213
	 */
2214
	public function getInlineEdit()
2215
	{
2216
		return $this->inlineEdit;
2217
	}
2218
2219
2220
	public function handleInlineEdit($id)
2221
	{
2222
		if ($this->inlineEdit) {
2223
			$this->inlineEdit->setItemId($id);
2224
2225
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2226
2227
			$this['filter']['inline_edit']->addHidden('_id', $id);
2228
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2229
2230
			$this->redrawItem($id, $primary_where_column);
2231
		}
2232
	}
2233
2234
2235
	/********************************************************************************
2236
	 *                               HIDEABLE COLUMNS                               *
2237
	 ********************************************************************************/
2238
2239
2240
	/**
2241
	 * Can datagrid hide colums?
2242
	 * @return boolean
2243
	 */
2244
	public function canHideColumns()
2245
	{
2246
		return (bool) $this->can_hide_columns;
2247
	}
2248
2249
2250
	/**
2251
	 * Order Grid to set columns hideable.
2252
	 * @return static
2253
	 */
2254
	public function setColumnsHideable()
2255
	{
2256
		$this->can_hide_columns = TRUE;
2257
2258
		return $this;
2259
	}
2260
2261
2262
	/********************************************************************************
2263
	 *                                   INTERNAL                                   *
2264
	 ********************************************************************************/
2265
2266
2267
	/**
2268
	 * Get cont of columns
2269
	 * @return int
2270
	 */
2271
	public function getColumnsCount()
2272
	{
2273
		$count = sizeof($this->getColumns());
2274
2275
		if (!empty($this->actions) || $this->isSortable() || $this->getItemsDetail()) {
2276
			$count++;
2277
		}
2278
2279
		if ($this->hasGroupActions()) {
2280
			$count++;
2281
		}
2282
2283
		return $count;
2284
	}
2285
2286
2287
	/**
2288
	 * Get primary key of datagrid data source
2289
	 * @return string
2290
	 */
2291
	public function getPrimaryKey()
2292
	{
2293
		return $this->primary_key;
2294
	}
2295
2296
2297
	/**
2298
	 * Get set of set columns
2299
	 * @return Column\IColumn[]
2300
	 */
2301
	public function getColumns()
2302
	{
2303
		foreach ($this->getSessionData('_grid_hidden_columns', []) as $column) {
2304
			if (!empty($this->columns[$column])) {
2305
				$this->columns_visibility[$column] = [
2306
					'visible' => FALSE,
2307
					'name' => $this->columns[$column]->getName()
2308
				];
2309
			}
2310
2311
			$this->removeColumn($column);
2312
		}
2313
2314
		return $this->columns;
2315
	}
2316
2317
2318
	/**
2319
	 * @return PresenterComponent
2320
	 */
2321
	public function getParent()
2322
	{
2323
		$parent = parent::getParent();
2324
2325
		if (!($parent instanceof PresenterComponent)) {
2326
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2327
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2328
			);
2329
		}
2330
2331
		return $parent;
2332
	}
2333
2334
}
2335