Completed
Push — master ( 579af5...b29473 )
by Oleg
07:53
created

GridViewWidget::getPager()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %
Metric Value
dl 11
loc 11
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
1
<?php /** MicroGridViewWidget */
2
3
namespace Micro\Widget;
4
5
use Micro\Base\Exception;
6
use Micro\File\Type;
7
use Micro\Mvc\Models\IModel;
8
use Micro\Mvc\Models\IQuery;
9
use Micro\Mvc\Models\Query;
10
use Micro\Mvc\Widget;
11
use Micro\Web\Html;
12
13
/**
14
 * GridViewWidget class file.
15
 *
16
 * @author Oleg Lunegov <[email protected]>
17
 * @link https://github.com/lugnsk/micro
18
 * @copyright Copyright &copy; 2013 Oleg Lunegov
19
 * @license /LICENSE
20
 * @package Micro
21
 * @subpackage Widget
22
 * @version 1.0
23
 * @since 1.0
24
 */
25
class GridViewWidget extends Widget
26
{
27
    /** @var int $page Current page on table */
28
    public $page = 0;
29
    /** @var int $limit Limit current rows */
30
    public $limit = 10;
31
    /** @var bool $filters Usage filters */
32
    public $filters = true;
33
    /** @var string $template Template render */
34
    public $template = '{counter}{table}{pager}';
35
    /** @var string $templateTable Template table render */
36
    public $templateTable = '{headers}{filters}{rows}';
37
    /** @var string $textCounter text for before counter */
38
    public $counterText = 'Sum: ';
39
    /** @var string $emptyText text to render if rows not found */
40
    public $emptyText = 'Elements not found';
41
    /** @var array $attributesEmpty Attributes for empty text */
42
    public $attributesEmpty = [];
43
    /** @var array $attributes attributes for table */
44
    public $attributes = [];
45
    /** @var array $attributesCounter attributes for counter */
46
    public $attributesCounter = [];
47
    /** @var array $attributesHeading attributes for heading */
48
    public $attributesHeading = [];
49
    /** @var array $attributesFilter attributes for filter row */
50
    public $attributesFilter = [];
51
    /** @var array $attributesFilterForm attributes for filter form */
52
    public $attributesFilterForm = [];
53
    /** @var array $tableConfig table configuration */
54
    public $tableConfig = [];
55
    /** @var array $paginationConfig parameters for PaginationWidget */
56
    public $paginationConfig = [];
57
58
    /** @var array $rows Rows from data */
59
    protected $rows;
60
    /** @var array $fields Fields of data */
61
    protected $fields = [];
62
    /** @var int $rowsCount Count rows */
63
    protected $rowsCount = 0;
64
    /** @var int $totalCount Total count data */
65
    protected $totalCount = 0;
66
    /** @var string $filterPrefix prefix for filter name */
67
    protected $filterPrefix;
68
69
70
    /**
71
     * Re-declare widget constructor
72
     *
73
     * @access public
74
     *
75
     * @param array $args arguments
76
     *
77
     * @result void
78
     * @throws Exception
79
     */
80
    public function __construct(array $args = [])
81
    {
82
        parent::__construct($args);
83
84
        if (empty($args['data'])) {
85
            throw new Exception('Argument "data" not initialized into GridViewWidget');
86
        }
87
88
        $this->limit = ($this->limit < 10) ? 10 : $this->limit;
89
        $this->page = ($this->page < 0) ? 0 : $this->page;
90
91
        if ($args['data'] instanceof IQuery) {
92 View Code Duplication
            if ($args['data']->objectName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Bug introduced by
Accessing objectName on the interface Micro\Mvc\Models\IQuery 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...
93
                /** @var IModel $cls */
94
                $cls = $args['data']->objectName;
0 ignored issues
show
Bug introduced by
Accessing objectName on the interface Micro\Mvc\Models\IQuery 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...
95
                $args['data']->table = $cls::tableName();
0 ignored issues
show
Bug introduced by
Accessing table on the interface Micro\Mvc\Models\IQuery 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...
96
            } elseif (!$args['data']->table) {
0 ignored issues
show
Bug introduced by
Accessing table on the interface Micro\Mvc\Models\IQuery 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...
97
                throw new Exception('Data query not set table or objectName');
98
            }
99
100
            if ($args['data']->having || $args['data']->group) {
0 ignored issues
show
Bug introduced by
Accessing having on the interface Micro\Mvc\Models\IQuery 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
Accessing group on the interface Micro\Mvc\Models\IQuery 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...
101
                $res = new Query($this->container->db);
0 ignored issues
show
Bug introduced by
Accessing db on the interface Micro\Base\IContainer 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...
102
                $res->select = 'COUNT(*)';
103
                $res->table = '(' . $args['data']->getQuery() . ') micro_count';
104
                $res->single = true;
105
            } else {
106
                /** @var Query $res */
107
                $res = clone $args['data'];
108
                $res->objectName = null;
109
                $res->select = 'COUNT(*)';
110
                $res->single = true;
111
            }
112
113
            /** @var array $a */
114
            $this->totalCount = ($a = $res->run()) ? $a[0] : 0;
115
            $this->filterPrefix = $args['data']->table;
0 ignored issues
show
Bug introduced by
Accessing table on the interface Micro\Mvc\Models\IQuery 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...
116
117
            $args['data']->ofset = $this->page * $this->limit;
0 ignored issues
show
Bug introduced by
Accessing ofset on the interface Micro\Mvc\Models\IQuery 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...
118
            $args['data']->limit = $this->limit;
0 ignored issues
show
Bug introduced by
Accessing limit on the interface Micro\Mvc\Models\IQuery 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...
119
            $args['data'] = $args['data']->run($args['data']->objectName ? \PDO::FETCH_CLASS : \PDO::FETCH_ASSOC);
0 ignored issues
show
Bug introduced by
Accessing objectName on the interface Micro\Mvc\Models\IQuery 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...
120
        } else { // array
121
            $this->totalCount = count($args['data']);
122
            $args['data'] = array_slice($args['data'], $this->page * $this->limit, $this->limit);
123
        }
124
125 View Code Duplication
        foreach ($args['data'] AS $model) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
            $this->rows[] = is_subclass_of($model, 'Micro\Mvc\Models\Model') ? $model : (object)$model;
127
        }
128
    }
129
130
    /**
131
     * Initialize widget
132
     *
133
     * @access public
134
     *
135
     * @result void
136
     */
137
    public function init()
138
    {
139
        $this->filterPrefix = ucfirst($this->filterPrefix ?: 'data' . $this->totalCount);
140
        $this->fields = (null !== $this->rows) ? array_keys(Type::getVars($this->rows[0])) : [];
141
        $this->rowsCount = count($this->rows);
142
        $this->paginationConfig['countRows'] = $this->totalCount;
143
        $this->paginationConfig['limit'] = $this->limit;
144
        $this->paginationConfig['currentPage'] = $this->page;
145
        $this->tableConfig = $this->tableConfig ?: $this->fields;
146
147
        foreach ($this->tableConfig AS $key => $conf) {
148
            unset($this->tableConfig[$key]);
149
150
            $this->tableConfig[is_string($conf) ? $conf : $key] = array_merge([
151
                'attributesHeader' => !empty($conf['attributesHeader']) ? $conf['attributesHeader'] : [],
152
                'attributesFilter' => !empty($conf['attributesFilter']) ? $conf['attributesFilter'] : [],
153
                'attributes' => !empty($conf['attributes']) ? $conf['attributes'] : []
154
            ], is_array($conf) ? $conf : []);
155
        }
156
    }
157
158
    /**
159
     * Running widget
160
     *
161
     * @access public
162
     *
163
     * @return string
164
     */
165
    public function run()
166
    {
167 View Code Duplication
        if (!$this->rows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rows 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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
168
            return Html::openTag('div', $this->attributesEmpty) . $this->emptyText . Html::closeTag('div');
169
        }
170
171
        ob_start();
172
        echo str_replace(
173
            ['{counter}', '{pager}', '{table}'],
174
            [$this->getCounter(), $this->getPager(), $this->getTable()],
175
            $this->template
176
        );
177
178
        return ob_get_clean();
179
    }
180
181
    /**
182
     * Get counter
183
     *
184
     * @access protected
185
     *
186
     * @return string
187
     */
188
    protected function getCounter()
189
    {
190
        return Html::openTag('div', $this->attributesCounter) .
191
        $this->counterText . $this->totalCount . Html::closeTag('div');
192
    }
193
194
    /**
195
     * Get pager
196
     *
197
     * @access protected
198
     *
199
     * @return string
200
     */
201 View Code Duplication
    protected function getPager()
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...
202
    {
203
        if (!$this->rows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rows 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...
204
            return '';
205
        }
206
207
        $pager = new PaginationWidget($this->paginationConfig);
208
        $pager->init();
209
210
        return $pager->run();
211
    }
212
213
    /**
214
     * Get table
215
     *
216
     * @access protected
217
     *
218
     * @return string
219
     */
220
    protected function getTable()
221
    {
222
        $table = str_replace(
223
            ['{headers}', '{filters}', '{rows}'],
224
            [$this->renderHeading(), $this->renderFilters(), $this->renderRows()],
225
            $this->templateTable
226
        );
227
228
        return Html::openTag('table', $this->attributes) . $table . Html::closeTag('table');
229
    }
230
231
    /**
232
     * Render heading
233
     *
234
     * @access protected
235
     *
236
     * @return string
237
     */
238
    protected function renderHeading()
239
    {
240
        $result = Html::openTag('tr', $this->attributesHeading);
241
        foreach ($this->tableConfig AS $key => $row) {
242
            $result .= Html::openTag('th', $row['attributesHeader']);
243
            if (!empty($row['header'])) {
244
                $result .= $row['header'];
245
            } else {
246
                if (is_string($key)) {
247
                    /** @noinspection PhpUndefinedMethodInspection */
248
                    $result .= is_subclass_of($this->rows[0],
249
                        'Micro\\Mvc\\Models\\Model') ? $this->rows[0]->getLabel($key) : ucfirst($key);
250
                }
251
            }
252
            $result .= Html::closeTag('th');
253
        }
254
255
        return $result . Html::closeTag('tr');
256
    }
257
258
    /**
259
     * Render filters
260
     *
261
     * @access protected
262
     *
263
     * @return null|string
264
     */
265
    protected function renderFilters()
266
    {
267
        if (!$this->filters) {
268
            return null;
269
        }
270
        /** @var array $filtersData */
271
        $filtersData = $this->container->request->query($this->filterPrefix);
0 ignored issues
show
Bug introduced by
Accessing request on the interface Micro\Base\IContainer 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...
272
273
        $result = Html::beginForm(null, 'get', $this->attributesFilterForm);
274
        $result .= Html::openTag('tr', $this->attributesFilter);
275
276
        foreach ($this->tableConfig AS $key => $row) {
277
            $result .= Html::openTag('td', $row['attributesFilter']);
278
            if (array_key_exists('filter', $row) && $row['filter'] === false) {
279
                continue;
280
            }
281
            if (!empty($row['filter'])) {
282
                $result .= $row['filter'];
283
            } else {
284
                $buffer = is_array($row) ? $key : $row;
285
                $fieldName = $this->filterPrefix . '[' . $buffer . ']';
286
                $fieldId = $this->filterPrefix . '_' . $buffer;
287
                $val = !empty($filtersData[$buffer]) ? $filtersData[$buffer] : '';
288
                $result .= Html::textField($fieldName, $val, ['id' => $fieldId]);
289
            }
290
            $result .= Html::closeTag('td');
291
        }
292
293
        return $result . Html::closeTag('tr') . Html::endForm();
294
    }
295
296
    /**
297
     * Render rows
298
     *
299
     * @access protected
300
     *
301
     * @return null|string
302
     */
303
    protected function renderRows()
304
    {
305
        $result = null;
306
307
        if (0 === count($this->rows)) {
308
            return Html::openTag('tr') .
309
            Html::openTag('td', ['cols' => count($this->fields)]) . $this->emptyText . Html::closeTag('td') .
310
            Html::closeTag('tr');
311
        }
312
313
        foreach ($this->rows AS $data) {
314
            $result .= Html::openTag('tr');
315
316
            foreach ($this->tableConfig AS $key => $row) {
317
                $result .= Html::openTag('td', $row['attributes']);
318
319
                if (!empty($row['class']) && is_subclass_of($row['class'], 'Micro\\Widget\\GridColumn')) {
320
                    $primaryKey = $data->{!empty($row['key']) ? $row['key'] : 'id'};
321
                    $result .= (string)(new $row['class'](
322
                        $row + ['str' => (null === $data) ?: $data, 'pKey' => $primaryKey]
323
                    ));
324
                } elseif (!empty($row['value'])) {
325
                    $result .= eval('return ' . $row['value'] . ';');
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
326
                } else {
327
                    $result .= property_exists($data, $key) ? $data->$key : null;
328
                }
329
                $result .= Html::closeTag('td');
330
            }
331
            $result .= Html::closeTag('tr');
332
        }
333
334
        return $result;
335
    }
336
}
337