Completed
Push — master ( 699359...3c40fd )
by Petr
06:54
created

Grid::render()   C

Complexity

Conditions 7
Paths 33

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7.0052

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 31
ccs 20
cts 21
cp 0.9524
rs 6.7272
cc 7
eloc 19
nc 33
nop 0
crap 7.0052
1
<?php
2
3
/**
4
 * This file is part of the Grido (http://grido.bugyik.cz)
5
 *
6
 * Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz)
7
 *
8
 * For the full copyright and license information, please view
9
 * the file LICENSE.md that was distributed with this source code.
10
 */
11
12
namespace Grido;
13
14
use Grido\Exception;
15
use Grido\Components\Button;
16
use Grido\Components\Paginator;
17
use Grido\Components\Columns\Column;
18
use Grido\Components\Filters\Filter;
19
use Grido\Components\Actions\Action;
20
21
use Symfony\Component\PropertyAccess\PropertyAccessor;
22
23
/**
24
 * Grido - DataGrid for Nette Framework.
25
 *
26
 * @package     Grido
27
 * @author      Petr Bugyík
28
 *
29 1
 * @property-read int $count
30
 * @property-read mixed $data
31
 * @property-read \Nette\Utils\Html $tablePrototype
32
 * @property-read PropertyAccessor $propertyAccessor
33
 * @property-read Customization $customization
34
 * @property-write string $templateFile
35
 * @property bool $rememberState
36
 * @property array $defaultPerPage
37
 * @property array $defaultFilter
38
 * @property array $defaultSort
39
 * @property array $perPageList
40 1
 * @property \Nette\Localization\ITranslator $translator
41
 * @property Paginator $paginator
42 1
 * @property string $primaryKey
43
 * @property string $filterRenderType
44 1
 * @property DataSources\IDataSource $model
45
 * @property callback $rowCallback
46 1
 * @property bool $strictMode
47
 * @method void onRegistered(Grid $grid)
48
 * @method void onRender(Grid $grid)
49
 * @method void onFetchData(Grid $grid)
50 1
 */
51
class Grid extends Components\Container
52 1
{
53
    /***** DEFAULTS ****/
54
    const BUTTONS = 'buttons';
55
56
    const CLIENT_SIDE_OPTIONS = 'grido-options';
57
58
    /** @var int @persistent */
59
    public $page = 1;
60
61
    /** @var int @persistent */
62
    public $perPage;
63
64
    /** @var array @persistent */
65
    public $sort = [];
66 1
67
    /** @var array @persistent */
68
    public $filter = [];
69
70
    /** @var array event on all grid's components registered */
71
    public $onRegistered;
72
73
    /** @var array event on render */
74
    public $onRender;
75
76
    /** @var array event for modifying data */
77
    public $onFetchData;
78
79
    /** @var callback returns tr html element; function($row, Html $tr) */
80
    protected $rowCallback;
81
82
    /** @var \Nette\Utils\Html */
83
    protected $tablePrototype;
84
85
    /** @var bool */
86
    protected $rememberState = FALSE;
87
88
    /** @var string */
89
    protected $rememberStateSectionName;
90
91
    /** @var string */
92
    protected $primaryKey = 'id';
93
94
    /** @var string */
95
    protected $filterRenderType;
96
97
    /** @var array */
98
    protected $perPageList = [10, 20, 30, 50, 100];
99
100
    /** @var int */
101
    protected $defaultPerPage = 20;
102
103
    /** @var array */
104
    protected $defaultFilter = [];
105
106
    /** @var array */
107
    protected $defaultSort = [];
108
109
    /** @var DataSources\IDataSource */
110
    protected $model;
111
112
    /** @var int total count of items */
113
    protected $count;
114
115
    /** @var mixed */
116
    protected $data;
117
118
    /** @var Paginator */
119 1
    protected $paginator;
120
121
    /** @var \Nette\Localization\ITranslator */
122
    protected $translator;
123
124
    /** @var PropertyAccessor */
125
    protected $propertyAccessor;
126
127
    /** @var bool */
128
    protected $strictMode = TRUE;
129
130
    /** @var array */
131
    protected $options = [
132
        self::CLIENT_SIDE_OPTIONS => []
133
    ];
134
135
    /** @var Customization */
136
    protected $customization;
137
138
    /**
139
     * Sets a model that implements the interface Grido\DataSources\IDataSource or data-source object.
140
     * @param mixed $model
141 1
     * @param bool $forceWrapper
142
     * @throws Exception
143
     * @return Grid
144
     */
145
    public function setModel($model, $forceWrapper = FALSE)
146
    {
147 1
        $this->model = $model instanceof DataSources\IDataSource && $forceWrapper === FALSE
148 1
            ? $model
149 1
            : new DataSources\Model($model);
150 1
151 1
        return $this;
152
    }
153
154
    /**
155
     * Sets the default number of items per page.
156
     * @param int $perPage
157
     * @return Grid
158
     */
159
    public function setDefaultPerPage($perPage)
160 1
    {
161 1
        $perPage = (int) $perPage;
162 1
        $this->defaultPerPage = $perPage;
163
164 1
        if (!in_array($perPage, $this->perPageList)) {
165 1
            $this->perPageList[] = $perPage;
166 1
            sort($this->perPageList);
167 1
        }
168 1
169 1
        return $this;
170
    }
171
172
    /**
173
     * Sets default filtering.
174
     * @param array $filter
175
     * @return Grid
176
     */
177
    public function setDefaultFilter(array $filter)
178
    {
179 1
        $this->defaultFilter = array_merge($this->defaultFilter, $filter);
180 1
        return $this;
181
    }
182
183
    /**
184
     * Sets default sorting.
185
     * @param array $sort
186
     * @return Grid
187 1
     * @throws Exception
188
     */
189
    public function setDefaultSort(array $sort)
190
    {
191 1
        static $replace = ['asc' => Column::ORDER_ASC, 'desc' => Column::ORDER_DESC];
192
193 1
        foreach ($sort as $column => $dir) {
194 1
            $dir = strtr(strtolower($dir), $replace);
195 1
            if (!in_array($dir, $replace)) {
196 1
                throw new Exception("Dir '$dir' for column '$column' is not allowed.");
197
            }
198
199 1
            $this->defaultSort[$column] = $dir;
200 1
        }
201
202 1
        return $this;
203
    }
204
205
    /**
206
     * Sets items to per-page select.
207
     * @param array $perPageList
208 1
     * @return Grid
209
     */
210
    public function setPerPageList(array $perPageList)
211
    {
212 1
        $this->perPageList = $perPageList;
213
214 1
        if ($this->hasFilters(FALSE) || $this->hasOperation(FALSE)) {
215 1
            $this['form']['count']->setItems($this->getItemsForCountSelect());
216 1
        }
217
218 1
        return $this;
219
    }
220 1
221
    /**
222
     * Sets translator.
223
     * @param \Nette\Localization\ITranslator $translator
224
     * @return Grid
225
     */
226
    public function setTranslator(\Nette\Localization\ITranslator $translator)
227
    {
228 1
        $this->translator = $translator;
229 1
        return $this;
230
    }
231
232
    /**
233
     * Sets type of filter rendering.
234
     * Defaults inner (Filter::RENDER_INNER) if column does not exist then outer filter (Filter::RENDER_OUTER).
235
     * @param string $type
236 1
     * @throws Exception
237
     * @return Grid
238
     */
239
    public function setFilterRenderType($type)
240
    {
241 1
        $type = strtolower($type);
242 1
        if (!in_array($type, [Filter::RENDER_INNER, Filter::RENDER_OUTER])) {
243 1
            throw new Exception('Type must be Filter::RENDER_INNER or Filter::RENDER_OUTER.');
244
        }
245
246 1
        $this->filterRenderType = $type;
247 1
        return $this;
248
    }
249
250
    /**
251
     * Sets custom paginator.
252
     * @param Paginator $paginator
253
     * @return Grid
254
     */
255
    public function setPaginator(Paginator $paginator)
256
    {
257 1
        $this->paginator = $paginator;
258 1
        return $this;
259
    }
260
261
    /**
262
     * Sets grid primary key.
263
     * Defaults is "id".
264
     * @param string $key
265
     * @return Grid
266
     */
267
    public function setPrimaryKey($key)
268
    {
269 1
        $this->primaryKey = $key;
270 1
        return $this;
271
    }
272
273
    /**
274
     * Sets file name of custom template.
275
     * @param string $file
276
     * @return Grid
277
     */
278
    public function setTemplateFile($file)
279
    {
280 1
        $this->onRender[] = function() use ($file) {
281 1
            $this->template->gridoTemplate = $this->getTemplate()->getFile();
0 ignored issues
show
Bug introduced by
Accessing gridoTemplate on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
282 1
            $this->getTemplate()->setFile($file);
283 1
        };
284
285 1
        return $this;
286
    }
287
288
    /**
289
     * Sets saving state to session.
290
     * @param bool $state
291
     * @param string $sectionName
292
     * @return Grid
293
     */
294
    public function setRememberState($state = TRUE, $sectionName = NULL)
295
    {
296 1
        $this->getPresenter(); //component must be attached to presenter
297 1
        $this->getRememberSession(TRUE); //start session if not
298 1
        $this->rememberState = (bool) $state;
299 1
        $this->rememberStateSectionName = $sectionName;
300
301 1
        return $this;
302
    }
303
304
    /**
305
     * Sets callback for customizing tr html object.
306
     * Callback returns tr html element; function($row, Html $tr).
307
     * @param $callback
308
     * @return Grid
309
     */
310
    public function setRowCallback($callback)
311
    {
312 1
        $this->rowCallback = $callback;
313 1
        return $this;
314
    }
315
316
    /**
317
     * Sets client-side options.
318
     * @param array $options
319
     * @return Grid
320
     */
321
    public function setClientSideOptions(array $options)
322
    {
323 1
        $this->options[self::CLIENT_SIDE_OPTIONS] = $options;
324 1
        return $this;
325
    }
326
327
    /**
328
     * Determines whether any user error will cause a notice.
329
     * @param bool $mode
330
     * @return \Grido\Grid
331
     */
332
    public function setStrictMode($mode)
333
    {
334 1
        $this->strictMode = (bool) $mode;
335 1
        return $this;
336
    }
337
338
    /**
339
     * @param \Grido\Customization $customization
340
     */
341
    public function setCustomization(Customization $customization)
342
    {
343 1
        $this->customization = $customization;
344 1
    }
345
346
    /**********************************************************************************************/
347
348
    /**
349
     * Returns total count of data.
350
     * @return int
351
     */
352
    public function getCount()
353
    {
354 1
        if ($this->count === NULL) {
355 1
            $this->count = $this->getModel()->getCount();
356 1
        }
357
358 1
        return $this->count;
359
    }
360
361
    /**
362
     * Returns default per page.
363
     * @return int
364
     */
365
    public function getDefaultPerPage()
366 1
    {
367 1
        if (!in_array($this->defaultPerPage, $this->perPageList)) {
368 1
            $this->defaultPerPage = $this->perPageList[0];
369 1
        }
370
371 1
        return $this->defaultPerPage;
372
    }
373
374
    /**
375
     * Returns default filter.
376
     * @return array
377
     */
378
    public function getDefaultFilter()
379
    {
380 1
        return $this->defaultFilter;
381
    }
382
383
    /**
384
     * Returns default sort.
385
     * @return array
386
     */
387
    public function getDefaultSort()
388
    {
389 1
        return $this->defaultSort;
390
    }
391
392
    /**
393
     * Returns list of possible items per page.
394
     * @return array
395
     */
396 1
    public function getPerPageList()
397
    {
398 1
        return $this->perPageList;
399
    }
400
401
    /**
402
     * Returns primary key.
403
     * @return string
404
     */
405
    public function getPrimaryKey()
406
    {
407 1
        return $this->primaryKey;
408
    }
409
410
    /**
411
     * Returns remember state.
412
     * @return bool
413
     */
414
    public function getRememberState()
415
    {
416 1
        return $this->rememberState;
417
    }
418
419
    /**
420
     * Returns row callback.
421
     * @return callback
422
     */
423
    public function getRowCallback()
424
    {
425 1
        return $this->rowCallback;
426
    }
427
428
    /**
429
     * Returns items per page.
430
     * @return int
431
     */
432
    public function getPerPage()
433
    {
434 1
        return $this->perPage === NULL
435 1
            ? $this->getDefaultPerPage()
436 1
            : $this->perPage;
437
    }
438
439
    /**
440
     * Returns actual filter values.
441
     * @param string $key
442
     * @return mixed
443
     */
444
    public function getActualFilter($key = NULL)
445
    {
446 1
        $filter = $this->filter ? $this->filter : $this->defaultFilter;
447 1
        return $key !== NULL && isset($filter[$key]) ? $filter[$key] : $filter;
448
    }
449
450
    /**
451
     * Returns fetched data.
452
     * @param bool $applyPaging
453
     * @param bool $useCache
454
     * @param bool $fetch
455
     * @throws Exception
456
     * @return array|DataSources\IDataSource|\Nette\Database\Table\Selection
457
     */
458
    public function getData($applyPaging = TRUE, $useCache = TRUE, $fetch = TRUE)
459
    {
460 1
        if ($this->getModel() === NULL) {
461 1
            throw new Exception('Model cannot be empty, please use method $grid->setModel().');
462
        }
463
464 1
        $data = $this->data;
465 1
        if ($data === NULL || $useCache === FALSE) {
466 1
            $this->applyFiltering();
467 1
            $this->applySorting();
468
469 1
            if ($applyPaging) {
470 1
                $this->applyPaging();
471 1
            }
472
473 1
            if ($fetch === FALSE) {
474 1
                return $this->getModel();
475
            }
476
477 1
            $data = $this->getModel()->getData();
478
479 1
            if ($useCache === TRUE) {
480 1
                $this->data = $data;
481 1
            }
482
483 1
            if ($applyPaging && !empty($data) && !in_array($this->page, range(1, $this->getPaginator()->pageCount))) {
484 1
                $this->__triggerUserNotice("Page is out of range.");
485 1
                $this->page = 1;
486 1
            }
487
488 1
            if (!empty($this->onFetchData)) {
489
                $this->onFetchData($this);
490
            }
491 1
        }
492
493 1
        return $data;
494
    }
495
496
    /**
497
     * Returns translator.
498
     * @return Translations\FileTranslator
499
     */
500
    public function getTranslator()
501
    {
502 1
        if ($this->translator === NULL) {
503 1
            $this->setTranslator(new Translations\FileTranslator);
504 1
        }
505
506 1
        return $this->translator;
507
    }
508
509
    /**
510
     * Returns remember session for set expiration, etc.
511
     * @param bool $forceStart - if TRUE, session will be started if not
512
     * @return \Nette\Http\SessionSection|NULL
513
     */
514
    public function getRememberSession($forceStart = FALSE)
515
    {
516 1
        $presenter = $this->getPresenter();
517 1
        $session = $presenter->getSession();
518
519 1
        if (!$session->isStarted() && $forceStart) {
520 1
            $session->start();
521 1
        }
522
523 1
        return $session->isStarted()
524 1
            ? ($session->getSection($this->rememberStateSectionName ?: ($presenter->name . ':' . $this->getUniqueId())))
0 ignored issues
show
Bug introduced by
Accessing name 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...
525 1
            : NULL;
526
    }
527
528
    /**
529
     * Returns table html element of grid.
530
     * @return \Nette\Utils\Html
531
     */
532
    public function getTablePrototype()
533
    {
534 1
        if ($this->tablePrototype === NULL) {
535 1
            $this->tablePrototype = \Nette\Utils\Html::el('table');
536 1
            $this->tablePrototype->id($this->getName());
537 1
        }
538
539 1
        return $this->tablePrototype;
540
    }
541
542
    /**
543
     * @return string
544
     * @internal
545
     */
546
    public function getFilterRenderType()
547
    {
548 1
        if ($this->filterRenderType !== NULL) {
549 1
            return $this->filterRenderType;
550
        }
551
552 1
        $this->filterRenderType = Filter::RENDER_OUTER;
553 1
        if ($this->hasColumns() && $this->hasFilters() && $this->hasActions()) {
554 1
            $this->filterRenderType = Filter::RENDER_INNER;
555
556 1
            $filters = $this[Filter::ID]->getComponents();
557 1
            foreach ($filters as $filter) {
558 1
                if (!$this[Column::ID]->getComponent($filter->name, FALSE)) {
559 1
                    $this->filterRenderType = Filter::RENDER_OUTER;
560 1
                    break;
561
                }
562 1
            }
563 1
        }
564
565 1
        return $this->filterRenderType;
566
    }
567
568
    /**
569
     * @return DataSources\IDataSource
570
     */
571
    public function getModel()
572
    {
573 1
        return $this->model;
574
    }
575
576
    /**
577
     * @return Paginator
578
     * @internal
579
     */
580
    public function getPaginator()
581
    {
582 1
        if ($this->paginator === NULL) {
583 1
            $this->paginator = new Paginator;
584 1
            $this->paginator->setItemsPerPage($this->getPerPage())
585 1
                ->setGrid($this);
586 1
        }
587
588 1
        return $this->paginator;
589
    }
590
591
    /**
592
     * A simple wrapper around symfony/property-access with Nette Database dot notation support.
593
     * @param array|object $object
594
     * @param string $name
595
     * @return mixed
596
     * @internal
597
     */
598
    public function getProperty($object, $name)
599
    {
600 1
        if ($object instanceof \Nette\Database\Table\IRow && \Nette\Utils\Strings::contains($name, '.')) {
601 1
            $parts = explode('.', $name);
602 1
            foreach ($parts as $item) {
603 1
                if (is_object($object)) {
604 1
                    $object = $object->$item;
605 1
                }
606 1
            }
607
608 1
            return $object;
609
        }
610
611 1
        if (is_array($object)) {
612 1
            $name = "[$name]";
613 1
        }
614
615 1
        return $this->getPropertyAccessor()->getValue($object, $name);
616
    }
617
618
    /**
619
     * @return PropertyAccessor
620
     * @internal
621
     */
622
    public function getPropertyAccessor()
623
    {
624 1
        if ($this->propertyAccessor === NULL) {
625 1
            $this->propertyAccessor = new PropertyAccessor(TRUE, TRUE);
626 1
        }
627
628 1
        return $this->propertyAccessor;
629
    }
630
631
    /**
632
     * @param mixed $row item from db
633
     * @return \Nette\Utils\Html
634
     * @internal
635
     */
636
    public function getRowPrototype($row)
637
    {
638
        try {
639 1
            $primaryValue = $this->getProperty($row, $this->getPrimaryKey());
640 1
        } catch (\Exception $e) {
641 1
            $primaryValue = NULL;
642
        }
643
644 1
        $tr = \Nette\Utils\Html::el('tr');
645 1
        $primaryValue ? $tr->class[] = "grid-row-$primaryValue" : NULL;
646
647 1
        if ($this->rowCallback) {
648 1
            $tr = call_user_func_array($this->rowCallback, [$row, $tr]);
649 1
        }
650
651 1
        return $tr;
652
    }
653
654
    /**
655
     * Returns client-side options.
656
     * @return array
657
     */
658
    public function getClientSideOptions()
659
    {
660 1
        return (array) $this->options[self::CLIENT_SIDE_OPTIONS];
661
    }
662
663
    /**
664
     * @return bool
665
     */
666
    public function isStrictMode()
667
    {
668 1
        return $this->strictMode;
669
    }
670
671
    /**
672
     * @return Customization
673
     */
674
    public function getCustomization()
675
    {
676 1
        if ($this->customization === NULL) {
677 1
            $this->customization = new Customization($this);
678 1
        }
679
680 1
        return $this->customization;
681
    }
682
683
    /**********************************************************************************************/
684
685
    /**
686
     * Loads state informations.
687
     * @param array $params
688
     * @internal
689
     */
690
    public function loadState(array $params)
691
    {
692
        //loads state from session
693 1
        $session = $this->getRememberSession();
694 1
        if ($session && $this->getPresenter()->isSignalReceiver($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 isSignalReceiver() does only exist in the following implementations of said interface: KdybyModule\CliPresenter, 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...
695 1
            $session->remove();
696 1
        } elseif ($session && empty($params) && $session->params) {
697 1
            $params = (array) $session->params;
698 1
        }
699
700 1
        parent::loadState($params);
701 1
    }
702
703
    /**
704
     * Saves state informations for next request.
705
     * @param array $params
706
     * @param \Nette\Application\UI\PresenterComponentReflection $reflection (internal, used by Presenter)
707
     * @internal
708
     */
709
    public function saveState(array &$params, $reflection = NULL)
710
    {
711 1
        !empty($this->onRegistered) && $this->onRegistered($this);
712 1
        return parent::saveState($params, $reflection);
713
    }
714
715
    /**
716
     * Ajax method.
717
     * @internal
718
     */
719
    public function handleRefresh()
720
    {
721
        $this->reload();
722
    }
723
724
    /**
725
     * @param int $page
726
     * @internal
727
     */
728
    public function handlePage($page)
0 ignored issues
show
Unused Code introduced by
The parameter $page is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
729
    {
730 1
        $this->reload();
731
    }
732
733
    /**
734
     * @param array $sort
735
     * @internal
736
     */
737
    public function handleSort(array $sort)
0 ignored issues
show
Unused Code introduced by
The parameter $sort is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
738
    {
739 1
        $this->page = 1;
740 1
        $this->reload();
741
    }
742
743
    /**
744
     * @param \Nette\Forms\Controls\SubmitButton $button
745
     * @internal
746
     */
747
    public function handleFilter(\Nette\Forms\Controls\SubmitButton $button)
748
    {
749 1
        $values = $button->form->values[Filter::ID];
750 1
        $session = $this->rememberState //session filter
751 1
            ? isset($this->getRememberSession(TRUE)->params['filter'])
752 1
                ? $this->getRememberSession(TRUE)->params['filter']
753 1
                : []
754 1
            : [];
755
756 1
        foreach ($values as $name => $value) {
757 1
            if (is_numeric($value) || !empty($value) || isset($this->defaultFilter[$name]) || isset($session[$name])) {
758 1
                $this->filter[$name] = $this->getFilter($name)->changeValue($value);
759 1
            } elseif (isset($this->filter[$name])) {
760
                unset($this->filter[$name]);
761
            }
762 1
        }
763
764 1
        $this->page = 1;
765 1
        $this->reload();
766
    }
767
768
    /**
769
     * @param \Nette\Forms\Controls\SubmitButton $button
770
     * @internal
771
     */
772
    public function handleReset(\Nette\Forms\Controls\SubmitButton $button)
773
    {
774 1
        $this->sort = [];
775 1
        $this->filter = [];
776 1
        $this->perPage = NULL;
777
778 1
        if ($session = $this->getRememberSession()) {
779 1
            $session->remove();
780 1
        }
781
782 1
        $button->form->setValues([Filter::ID => $this->defaultFilter], TRUE);
783
784 1
        $this->page = 1;
785 1
        $this->reload();
786
    }
787
788
    /**
789
     * @param \Nette\Forms\Controls\SubmitButton $button
790
     * @internal
791
     */
792
    public function handlePerPage(\Nette\Forms\Controls\SubmitButton $button)
793
    {
794 1
        $perPage = (int) $button->form['count']->value;
795 1
        $this->perPage = $perPage == $this->defaultPerPage
796 1
            ? NULL
797
            : $perPage;
798
799 1
        $this->page = 1;
800 1
        $this->reload();
801
    }
802
803
    /**
804
     * Refresh wrapper.
805
     * @return void
806
     * @internal
807
     */
808
    public function reload()
809
    {
810 1
        if ($this->presenter->isAjax()) {
811
            $this->presenter->payload->grido = TRUE;
812
            $this->redrawControl();
813
        } else {
814 1
            $this->redirect('this');
815
        }
816
    }
817
818
    /**********************************************************************************************/
819
820
    /**
821
     * @return \Nette\Templating\FileTemplate
822
     * @internal
823
     */
824
    public function createTemplate()
825
    {
826 1
        $template = parent::createTemplate();
827 1
        $template->setFile($this->getCustomization()->getTemplateFiles()[Customization::TEMPLATE_DEFAULT]);
828 1
        $template->registerHelper('translate', [$this->getTranslator(), 'translate']);
829
830 1
        return $template;
831
    }
832
833
    /**
834
     * @internal
835
     * @throws Exception
836
     */
837
    public function render()
838
    {
839 1
        if (!$this->hasColumns()) {
840
            throw new Exception('Grid must have defined a column, please use method $grid->addColumn*().');
841
        }
842
843 1
        $this->saveRememberState();
844 1
        $data = $this->getData();
845
846 1
        if (!empty($this->onRender)) {
847 1
            $this->onRender($this);
848 1
        }
849
850 1
        $this->template->data = $data;
851 1
        $this->template->form = $form = $this['form'];
852 1
        $this->template->paginator = $this->getPaginator();
0 ignored issues
show
Bug introduced by
Accessing paginator on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
853
854 1
        $this->template->columns = $this->getComponent(Column::ID)->getComponents();
0 ignored issues
show
Bug introduced by
Accessing columns on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getComponents() does only exist in the following implementations of said interface: Grido\Components\Actions\Action, Grido\Components\Actions\Event, Grido\Components\Actions\Href, Grido\Components\Button, Grido\Components\Columns\Column, Grido\Components\Columns\Date, Grido\Components\Columns\Editable, Grido\Components\Columns\Email, Grido\Components\Columns\Link, Grido\Components\Columns\Number, Grido\Components\Columns\Text, Grido\Components\Component, Grido\Components\Container, Grido\Components\Export, Grido\Components\Filters\Check, Grido\Components\Filters\Custom, Grido\Components\Filters\Date, Grido\Components\Filters\DateRange, Grido\Components\Filters\Filter, Grido\Components\Filters\Number, Grido\Components\Filters\Select, Grido\Components\Filters\Text, Grido\Components\Operation, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Nette\ComponentModel\Container, Nette\Forms\Container, Nette\Forms\Form.

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...
855 1
        $this->template->actions = $this->hasActions() ? $this->getComponent(Action::ID)->getComponents() : [];
0 ignored issues
show
Bug introduced by
Accessing actions on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getComponents() does only exist in the following implementations of said interface: Grido\Components\Actions\Action, Grido\Components\Actions\Event, Grido\Components\Actions\Href, Grido\Components\Button, Grido\Components\Columns\Column, Grido\Components\Columns\Date, Grido\Components\Columns\Editable, Grido\Components\Columns\Email, Grido\Components\Columns\Link, Grido\Components\Columns\Number, Grido\Components\Columns\Text, Grido\Components\Component, Grido\Components\Container, Grido\Components\Export, Grido\Components\Filters\Check, Grido\Components\Filters\Custom, Grido\Components\Filters\Date, Grido\Components\Filters\DateRange, Grido\Components\Filters\Filter, Grido\Components\Filters\Number, Grido\Components\Filters\Select, Grido\Components\Filters\Text, Grido\Components\Operation, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Nette\ComponentModel\Container, Nette\Forms\Container, Nette\Forms\Form.

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...
856 1
        $this->template->buttons = $this->hasButtons() ? $this->getComponent(Button::ID)->getComponents() : [];
0 ignored issues
show
Bug introduced by
Accessing buttons on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getComponents() does only exist in the following implementations of said interface: Grido\Components\Actions\Action, Grido\Components\Actions\Event, Grido\Components\Actions\Href, Grido\Components\Button, Grido\Components\Columns\Column, Grido\Components\Columns\Date, Grido\Components\Columns\Editable, Grido\Components\Columns\Email, Grido\Components\Columns\Link, Grido\Components\Columns\Number, Grido\Components\Columns\Text, Grido\Components\Component, Grido\Components\Container, Grido\Components\Export, Grido\Components\Filters\Check, Grido\Components\Filters\Custom, Grido\Components\Filters\Date, Grido\Components\Filters\DateRange, Grido\Components\Filters\Filter, Grido\Components\Filters\Number, Grido\Components\Filters\Select, Grido\Components\Filters\Text, Grido\Components\Operation, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Nette\ComponentModel\Container, Nette\Forms\Container, Nette\Forms\Form.

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...
857 1
        $this->template->formFilters = $this->hasFilters() ? $form->getComponent(Filter::ID)->getComponents() : [];
0 ignored issues
show
Bug introduced by
Accessing formFilters on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
858 1
        $this->template->customization = $this->getCustomization();
0 ignored issues
show
Bug introduced by
Accessing customization on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
859
860 1
        $form['count']->setValue($this->getPerPage());
861
862 1
        if ($options = $this->options[self::CLIENT_SIDE_OPTIONS]) {
863 1
            $this->getTablePrototype()->data[self::CLIENT_SIDE_OPTIONS] = json_encode($options);
864 1
        }
865
866 1
        $this->template->render();
867 1
    }
868
869
    protected function saveRememberState()
870
    {
871 1
        if ($this->rememberState) {
872 1
            $session = $this->getRememberSession(TRUE);
873 1
            $params = array_keys($this->getReflection()->getPersistentParams());
874 1
            foreach ($params as $param) {
875 1
                $session->params[$param] = $this->$param;
876 1
            }
877 1
        }
878 1
    }
879
880
    protected function applyFiltering()
881
    {
882 1
        $conditions = $this->__getConditions($this->getActualFilter());
883 1
        $this->getModel()->filter($conditions);
884 1
    }
885
886
    /**
887
     * @param array $filter
888
     * @return array
889
     * @internal
890
     */
891
    public function __getConditions(array $filter)
892
    {
893 1
        $conditions = [];
894 1
        if (!empty($filter)) {
895
            try {
896 1
                $this['form']->setDefaults([Filter::ID => $filter]);
897 1
            } catch (\Nette\InvalidArgumentException $e) {
898
                $this->__triggerUserNotice($e->getMessage());
899
                $filter = [];
900
                if ($session = $this->getRememberSession()) {
901
                    $session->remove();
902
                }
903
            }
904
905 1
            foreach ($filter as $column => $value) {
906 1
                if ($component = $this->getFilter($column, FALSE)) {
907 1
                    if ($condition = $component->__getCondition($value)) {
908 1
                        $conditions[] = $condition;
909 1
                    }
910 1
                } else {
911 1
                    $this->__triggerUserNotice("Filter with name '$column' does not exist.");
912
                }
913 1
            }
914 1
        }
915
916 1
        return $conditions;
917
    }
918
919
    protected function applySorting()
920
    {
921 1
        $sort = [];
922 1
        $this->sort = $this->sort ? $this->sort : $this->defaultSort;
923
924 1
        foreach ($this->sort as $column => $dir) {
925 1
            $component = $this->getColumn($column, FALSE);
926 1
            if (!$component) {
927 1
                if (!isset($this->defaultSort[$column])) {
928 1
                    $this->__triggerUserNotice("Column with name '$column' does not exist.");
929 1
                    break;
930
                }
931
932 1
            } elseif (!$component->isSortable()) {
933 1
                if (isset($this->defaultSort[$column])) {
934 1
                    $component->setSortable();
935 1
                } else {
936 1
                    $this->__triggerUserNotice("Column with name '$column' is not sortable.");
937 1
                    break;
938
                }
939 1
            }
940
941 1
            if (!in_array($dir, [Column::ORDER_ASC, Column::ORDER_DESC])) {
942 1
                if ($dir == '' && isset($this->defaultSort[$column])) {
943
                    unset($this->sort[$column]);
944
                    break;
945
                }
946
947 1
                $this->__triggerUserNotice("Dir '$dir' is not allowed.");
948 1
                break;
949
            }
950
951 1
            $sort[$component ? $component->column : $column] = $dir == Column::ORDER_ASC ? 'ASC' : 'DESC';
0 ignored issues
show
Documentation introduced by
The property $column is declared protected in Grido\Components\Columns\Column. 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...
952 1
        }
953
954 1
        if (!empty($sort)) {
955 1
            $this->getModel()->sort($sort);
956 1
        }
957 1
    }
958
959
    protected function applyPaging()
960
    {
961 1
        $paginator = $this->getPaginator()
962 1
            ->setItemCount($this->getCount())
963 1
            ->setPage($this->page);
964
965 1
        $perPage = $this->getPerPage();
966 1
        if ($perPage !== NULL && !in_array($perPage, $this->perPageList)) {
967 1
            $this->__triggerUserNotice("The number '$perPage' of items per page is out of range.");
968 1
        }
969
970 1
        $this->getModel()->limit($paginator->getOffset(), $paginator->getLength());
971 1
    }
972
973
    protected function createComponentForm($name)
974
    {
975 1
        $form = new \Nette\Application\UI\Form($this, $name);
976 1
        $form->setTranslator($this->getTranslator());
977 1
        $form->setMethod($form::GET);
978
979 1
        $buttons = $form->addContainer(self::BUTTONS);
980 1
        $buttons->addSubmit('search', 'Grido.Search')
981 1
            ->onClick[] = [$this, 'handleFilter'];
982 1
        $buttons->addSubmit('reset', 'Grido.Reset')
983 1
            ->onClick[] = [$this, 'handleReset'];
984 1
        $buttons->addSubmit('perPage', 'Grido.ItemsPerPage')
985 1
            ->onClick[] = [$this, 'handlePerPage'];
986
987 1
        $form->addSelect('count', 'Count', $this->getItemsForCountSelect())
988 1
            ->setTranslator(NULL)
989 1
            ->controlPrototype->attrs['title'] = $this->getTranslator()->translate('Grido.ItemsPerPage');
990 1
    }
991
992
    /**
993
     * @return array
994
     */
995
    protected function getItemsForCountSelect()
996
    {
997 1
        return array_combine($this->perPageList, $this->perPageList);
998
    }
999
1000
    /**
1001
     * @internal
1002
     * @param string $message
1003
     */
1004
    public function __triggerUserNotice($message)
1005
    {
1006 1
        if ($this->getPresenter(FALSE) && $session = $this->getRememberSession()) {
1007 1
            $session->remove();
1008 1
        }
1009
1010 1
        $this->strictMode && trigger_error($message, E_USER_NOTICE);
1011 1
    }
1012
}
1013