DocLister::uniformPrepare()   F
last analyzed

Complexity

Conditions 14
Paths 432

Size

Total Lines 79
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 52
dl 0
loc 79
ccs 0
cts 55
cp 0
rs 2.8887
c 1
b 0
f 0
cc 14
nc 432
nop 2
crap 210

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * DocLister class
4
 *
5
 * @license GNU General Public License (GPL), http://www.gnu.org/copyleft/gpl.html
6
 * @author Agel_Nash <[email protected]>
7
 */
8 1
include_once MODX_BASE_PATH . 'assets/lib/APIHelpers.class.php';
9 1
include_once MODX_BASE_PATH . 'assets/lib/Helpers/FS.php';
10 1
include_once MODX_BASE_PATH . 'assets/lib/Helpers/Config.php';
11 1
include_once MODX_BASE_PATH . 'assets/lib/Helpers/Lexicon.php';
12 1
include_once MODX_BASE_PATH . 'assets/lib/Helpers/LexiconHandlers/AbstractLexiconHandler.php';
13 1
include_once MODX_BASE_PATH . 'assets/lib/Helpers/LexiconHandlers/EvoBabelLexiconHandler.php';
14 1
include_once MODX_BASE_PATH . 'assets/snippets/DocLister/lib/jsonHelper.class.php';
15 1
include_once MODX_BASE_PATH . 'assets/snippets/DocLister/lib/sqlHelper.class.php';
16
include_once MODX_BASE_PATH . 'assets/snippets/DocLister/lib/DLTemplate.class.php';
17
include_once MODX_BASE_PATH . 'assets/snippets/DocLister/lib/DLCollection.class.php';
18
include_once MODX_BASE_PATH . 'assets/snippets/DocLister/lib/xnop.class.php';
19
20
/**
21
 * Class DocLister
22
 */
23
abstract class DocLister
24
{
25
    /**
26
     * Ключ в массиве $_REQUEST в котором находится алиас запрашиваемого документа
27
     */
28
    const AliasRequest = 'q';
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected ALIASREQUEST).
Loading history...
29
    /**
30
     * Массив документов полученный в результате выборки из базы
31
     * @var array
32
     * @access protected
33
     */
34
    protected $_docs = [];
35
36
    /**
37
     * Массив документов self::$_docs собранный в виде дерева
38
     * @var array
39
     * @access protected
40
     */
41
    protected $_tree = [];
42
43
    /**
44
     * @var
45
     * @access protected
46
     */
47
    protected $IDs = 0;
48
49
    /**
50
     * Объект DocumentParser - основной класс MODX'а
51
     * @var DocumentParser
52
     * @access protected
53
     */
54
    protected $modx;
55
56
    /**
57
     * Шаблонизатор чанков
58
     * @var DLTemplate
59
     * @access protected
60
     */
61
    protected $DLTemplate;
62
63
    /**
64
     * Массив загруженных экстендеров
65
     * @var array
66
     * @access protected
67
     */
68
    protected $extender = [];
69
70
    /**
71
     * Массив плейсхолдеров доступных в шаблоне
72
     * @var array
73
     * @access protected
74
     */
75
    protected $_plh = [];
76
77
    /**
78
     * Список таблиц уже с префиксами MODX
79
     * @var array
80
     * @access private
81
     */
82
    private $_table = [];
83
84
    /**
85
     * PrimaryKey основной таблицы
86
     * @var string
87
     * @access protected
88
     */
89
    protected $idField = 'id';
90
91
    /**
92
     * Parent Key основной таблицы
93
     * @var string
94
     * @access protected
95
     */
96
    protected $parentField = 'parent';
97
98
    /**
99
     * Дополнительные условия для SQL запросов
100
     * @var array
101
     * @access protected
102
     */
103
    protected $_filters = ['where' => '', 'join' => ''];
104
105
    /**
106
     * Список доступных логических операторов для фильтрации
107
     * @var array
108
     * @access protected
109
     */
110
    protected $_logic_ops = ['AND' => ' AND ', 'OR' => ' OR ']; // logic operators currently supported
111
112
    /**
113
     * Режим отладки
114
     * @var int
115
     * @access private
116
     */
117
    private $_debugMode = 0;
118
119
    /**
120
     * Отладчик
121
     *
122
     * @var DLdebug|xNop
123
     * @access public
124
     */
125
    public $debug;
126
127
    /**
128
     * Массив дополнительно подключаемых таблиц с псевдонимами
129
     * @var array
130
     */
131
    public $AddTable = [];
132
133
    /**
134
     * Время запуска сниппета
135
     * @var int
136
     */
137
    private $_timeStart = 0;
138
139
    /**
140
     * Номер фильтра в общем списке фильтров
141
     * @var int
142
     * @access protected
143
     */
144
    protected $totalFilters = 0;
145
146
    /** @var string имя шаблона для вывода записи */
147
    public $renderTPL = '';
148
149
    /** @var string имя шаблона обертки для записей */
150
    public $ownerTPL = '';
151
152
    /** @var \Helpers\FS */
153
    public $FS;
154
    /** @var string результатирующая строка которая была последний раз сгенирирована
155
     *               вызовами методов DocLister::render и DocLister::getJSON
156
     */
157
    protected $outData = '';
158
159
    /** @var int Число документов, которые были отфильтрованы через prepare при выводе */
160
    public $skippedDocs = 0;
161
162
    /** @var string Имя таблицы */
163
    protected $table = '';
164
    /** @var string alias таблицы */
165
    protected $alias = '';
166
167
    /** @var null|paginate_DL_Extender */
168
    protected $extPaginate;
169
170
    /** @var null|Helpers\Config */
171
    public $config;
172
173
    /**
174
     * @var cache_DL_Extender
175
     */
176
    protected $extCache;
177
178
    protected $lexicon;
179
180
    /**
181
     * Конструктор контроллеров DocLister
182
     *
183
     * @param  DocumentParser  $modx  объект DocumentParser - основной класс MODX
184
     * @param  mixed  $cfg  массив параметров сниппета
185
     * @param  int  $startTime  время запуска сниппета
186
     * @throws Exception
187
     */
188
    public function __construct($modx, $cfg = [], $startTime = null)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (4) exceeds 3; consider refactoring the function
Loading history...
189
    {
190
        $this->setTimeStart($startTime);
191
192
        if (extension_loaded('mbstring')) {
193
            mb_internal_encoding("UTF-8");
194
        } else {
195
            throw new Exception('Not found php extension mbstring');
196
        }
197 58
198
        if ($modx instanceof DocumentParser) {
0 ignored issues
show
introduced by
$modx is always a sub-type of DocumentParser.
Loading history...
199 58
            $this->modx = $modx;
200
            $this->setDebug(1);
201 58
202 58
            if (!is_array($cfg) || empty($cfg)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
203 58
                $cfg = $this->modx->Event->params;
0 ignored issues
show
Bug introduced by
The property Event does not seem to exist on DocumentParser.
Loading history...
204
            }
205
        } else {
206
            throw new Exception('MODX var is not instaceof DocumentParser');
207 58
        }
208 58
209 58
        $this->FS = \Helpers\FS::getInstance();
210
        $this->config = new \Helpers\Config($cfg);
211 58
212
        if (isset($cfg['config'])) {
213
            $this->config->setPath(dirname(__DIR__))->loadConfig($cfg['config']);
214 58
        }
215
216
        if ($this->config->setConfig($cfg) === false) {
217
            throw new Exception('no parameters to run DocLister');
218 58
        }
219 58
        $this->lexicon = new \Helpers\Lexicon($this->modx, [
220
            'langDir' => 'assets/snippets/DocLister/core/lang/',
221 58
            'lang' => $this->getCFGDef('lang', $this->modx->getLocale()),
0 ignored issues
show
Bug introduced by
The method getLocale() does not exist on DocumentParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
            'lang' => $this->getCFGDef('lang', $this->modx->/** @scrutinizer ignore-call */ getLocale()),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222
            'handler' => $this->getCFGDef('lexiconHandler', '\\Helpers\\Lexicon\\EvoBabelLexiconHandler'),
223
        ]);
224
225 58
        $this->loadLang(['core', 'json']);
226
        $this->setDebug($this->getCFGDef('debug', 0));
227
228
        if ($this->checkDL()) {
229 58
            $cfg = [];
230 58
            $idType = $this->getCFGDef('idType', '');
231
            if (empty($idType) && $this->getCFGDef('documents', '') != '') {
232 58
                $idType = 'documents';
233 58
            }
234 58
            switch ($idType) {
235 58
                case 'documents':
236
                    $IDs = $this->getCFGDef('documents');
237
                    $cfg['idType'] = "documents";
238
                    break;
239 58
                case 'parents':
240
                default:
241
                    $cfg['idType'] = "parents";
242
                    if (($IDs = $this->getCFGDef('parents', '')) === '') {
243 58
                        $IDs = $cfg['parents'] = $this->getCurrentMODXPageID();
244 58
                    }
245 58
                    break;
246 58
            }
247 58
            $this->config->setConfig($cfg);
248 58
            $this->alias = empty($this->alias) ? $this->getCFGDef(
249 58
                'tableAlias',
250 58
                'c'
251 58
            ) : $this->alias;
252 58
            $this->table = $this->getTable(empty($this->table) ? $this->getCFGDef(
253 58
                'table',
254
                'site_content'
255 58
            ) : $this->table, $this->alias);
256 58
257 58
            $this->idField = $this->getCFGDef('idField', $this->idField);
258
            $this->parentField = $this->getCFGDef('parentField', $this->parentField);
259 58
            $this->extCache = $this->getExtender('cache', true);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getExtender('cache', true) can also be of type xNop. However, the property $extCache is declared as type cache_DL_Extender. 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...
260
            $this->extCache->init($this, [
0 ignored issues
show
Bug introduced by
The method init() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

260
            $this->extCache->/** @scrutinizer ignore-call */ 
261
                             init($this, [
Loading history...
Bug introduced by
The method init() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

260
            $this->extCache->/** @scrutinizer ignore-call */ 
261
                             init($this, [

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
261 58
                'cache' => $this->getCFGDef('cache', 1),
262 58
                'cacheKey' => $this->getCFGDef('cacheKey'),
263 58
                'cacheLifetime' => $this->getCFGDef('cacheLifetime', 0),
264 58
                'cacheStrategy' => $this->getCFGDef('cacheStrategy'),
265 58
            ]);
266 58
            $this->setIDs($IDs);
267 58
        }
268 58
269 58
        $this->setLocate();
270 58
271 58
        $this->getCustomLang($this->getCFGDef('customLang', $this->getCFGDef('lexicon', '')));
272
273 58
        $this->loadExtender($this->getCFGDef("extender", ""));
274
275 58
        if ($this->checkExtender('request')) {
276
            $this->extender['request']->init($this, $this->getCFGDef("requestActive", ""));
277
        }
278 58
        $this->_filters = $this->getFilters($this->getCFGDef('filters', ''));
279
        $this->ownerTPL = $this->getCFGDef('ownerTpl', $this->getCFGDef('ownerTPL', ''));
280 58
281
        $DLTemplate = DLTemplate::getInstance($modx);
282
        if ($path = $this->getCFGDef('templatePath')) {
283 58
            $DLTemplate->setTemplatePath($path);
284 58
        }
285 58
        if ($ext = $this->getCFGDef('templateExtension')) {
286 58
            $DLTemplate->setTemplateExtension($ext);
287
        }
288
        $this->DLTemplate = $DLTemplate->setTemplateData(['DocLister' => $this]);
289 58
    }
290
291
    /**
292 58
     * Разбиение фильтра на субфильтры с учётом вложенности
293 58
     * @param  string  $str  строка с фильтром
294
     * @return array массив субфильтров
295
     */
296
    public function smartSplit($str)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (4) exceeds 3; consider refactoring the function
Loading history...
297
    {
298
        $res = [];
299
        $cur = '';
300 10
        $open = 0;
301
        $strlen = mb_strlen($str, 'UTF-8');
302 10
        for ($i = 0; $i <= $strlen; $i++) {
303 10
            $e = mb_substr($str, $i, 1, 'UTF-8');
304 10
            switch ($e) {
305 10
                case '\\':
306 10
                    $cur .= $e;
307 10
                    $cur .= mb_substr($str, ++$i, 1, 'UTF-8');
308
                    break;
309 10
                case ')':
310
                    $open--;
311
                    if ($open === 0) {
312
                        $res[] = $cur . ')';
313 10
                        $cur = '';
314 10
                    } else {
315 10
                        $cur .= $e;
316 4
                    }
317 4
                    break;
318 4
                case '(':
319 10
                    $open++;
320
                    $cur .= $e;
321 10
                    break;
322 10
                case ';':
323 4
                    if ($open === 0) {
324 4
                        $res[] = $cur;
325 4
                        $cur = '';
326 10
                    } else {
327 6
                        $cur .= $e;
328 5
                    }
329 5
                    break;
330 5
                default:
331 1
                    $cur .= $e;
332
            }
333 6
        }
334 10
        $cur = preg_replace("/(\))$/u", '', $cur);
335 10
        if ($cur !== '') {
336 10
            $res[] = $cur;
337 10
        }
338 10
339 10
        return array_reverse($res);
340 10
    }
341 10
342
    /**
343 10
     * Трансформация объекта в строку
344
     * @return string последний ответ от DocLister'а
345
     */
346
    public function __toString()
347
    {
348
        return $this->outData;
349
    }
350
351
    /**
352
     * Установить время запуска сниппета
353
     * @param  float|null  $time
354
     */
355
    public function setTimeStart($time = null)
356
    {
357
        $this->_timeStart = is_null($time) ? microtime(true) : $time;
0 ignored issues
show
Documentation Bug introduced by
It seems like is_null($time) ? microtime(true) : $time can also be of type string. However, the property $_timeStart is declared as type integer. 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...
358
    }
359 58
360
    /**
361 58
     * Время запуска сниппета
362 58
     *
363
     * @return int
364
     */
365
    public function getTimeStart()
366
    {
367
        return $this->_timeStart;
368
    }
369 38
370
    /**
371 38
     * Установка режима отладки
372
     * @param  int  $flag  режим отладки
373
     */
374
    public function setDebug($flag = 0)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (4) exceeds 3; consider refactoring the function
Loading history...
375
    {
376
        $flag = abs((int) $flag);
377
        if ($this->_debugMode != $flag) {
378 58
            $this->_debugMode = $flag;
379
            $this->debug = null;
380 58
            if ($this->_debugMode > 0) {
381 58
                if (isset($_SESSION['usertype']) && $_SESSION['usertype'] == 'manager') {
382 58
                    error_reporting(E_ALL ^ E_NOTICE);
383 58
                    ini_set('display_errors', 1);
384 58
                }
385 58
                $dir = dirname(dirname(__FILE__));
386
                if (file_exists($dir . "/lib/DLdebug.class.php")) {
387
                    include_once $dir . "/lib/DLdebug.class.php";
388
                    if (class_exists("DLdebug", false)) {
389 58
                        $this->debug = new DLdebug($this);
390 58
                    }
391 58
                }
392 58
            }
393 58
394 58
            if (is_null($this->debug)) {
395 58
                $this->debug = new xNop();
396 58
                $this->_debugMode = 0;
397
            }
398 58
        }
399 58
    }
400 58
401 58
    /**
402 58
     * Информация о режиме отладки
403 58
     */
404 58
    public function getDebug()
405 58
    {
406
        return $this->_debugMode;
407
    }
408
409
    /**
410 58
     * Генерация имени таблицы с префиксом и алиасом
411
     *
412 58
     * @param  string  $name  имя таблицы
413
     * @param  string  $alias  желаемый алиас таблицы
414
     * @return string имя таблицы с префиксом и алиасом
415
     */
416
    public function getTable($name, $alias = '')
417
    {
418
        if (!isset($this->_table[$name])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
419
            $this->_table[$name] = $this->modx->getFullTableName($name);
420
        }
421
        $table = $this->_table[$name];
422 58
        if (!empty($alias) && is_scalar($alias)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
423
            $table .= " as `" . $alias . "`";
424 58
        }
425 58
426 58
        return $table;
427 58
    }
428 58
429 58
    /**
430 58
     * @param $name
431
     * @param $table
432 58
     * @param $alias
433
     * @return mixed
434
     */
435
    public function TableAlias($name, $table, $alias)
0 ignored issues
show
Coding Style introduced by
Method name "DocLister::TableAlias" is not in camel caps format
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
436
    {
437
        if (!$this->checkTableAlias($name, $table)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
438
            $this->AddTable[$table][$name] = $alias;
439
        }
440
441 26
        return $this->AddTable[$table][$name];
442
    }
443 26
444 26
    /**
445 26
     * @param $name
446
     * @param $table
447 26
     * @return bool
448
     */
449
    public function checkTableAlias($name, $table)
450
    {
451
        return isset($this->AddTable[$table][$name]);
452
    }
453
454
    /**
455 26
     * Разбор JSON строки при помощи json_decode
456
     *
457 26
     * @param $json string строка c JSON
458
     * @param  array  $config  ассоциативный массив с настройками для json_decode
459
     * @param  bool  $nop  создавать ли пустой объект запрашиваемого типа
460
     * @return array|mixed|xNop
461
     */
462
    public function jsonDecode($json, $config = [], $nop = false)
463
    {
464
        $this->debug->debug(
0 ignored issues
show
Bug introduced by
The method debug() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

464
        $this->debug->/** @scrutinizer ignore-call */ 
465
                      debug(
Loading history...
465
            'Decode JSON: ' . $this->debug->dumpData($json) . "\r\nwith config: " . $this->debug->dumpData($config),
0 ignored issues
show
Bug introduced by
The method dumpData() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

465
            'Decode JSON: ' . $this->debug->/** @scrutinizer ignore-call */ dumpData($json) . "\r\nwith config: " . $this->debug->dumpData($config),
Loading history...
466
            'jsonDecode',
467
            2
468
        );
469
        $config = jsonHelper::jsonDecode($json, $config, $nop);
470
        $this->isErrorJSON($json);
471
        $this->debug->debugEnd("jsonDecode");
0 ignored issues
show
Bug introduced by
The method debugEnd() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

471
        $this->debug->/** @scrutinizer ignore-call */ 
472
                      debugEnd("jsonDecode");
Loading history...
472
473
        return $config;
474
    }
475
476
    /**
477
     * Были ли ошибки во время работы с JSON
478
     *
479
     * @param $json string строка с JSON для записи в лог при отладке
480
     * @return bool|string
481
     */
482
    public function isErrorJSON($json)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
483
    {
484
        $error = jsonHelper::json_last_error_msg();
485
        if (!in_array($error, ['error_none', 'other'])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
486
            $this->debug->error($this->getMsg('json.' . $error) . ": " . $this->debug->dumpData($json, 'code'), 'JSON');
0 ignored issues
show
Bug introduced by
The method error() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

486
            $this->debug->/** @scrutinizer ignore-call */ 
487
                          error($this->getMsg('json.' . $error) . ": " . $this->debug->dumpData($json, 'code'), 'JSON');
Loading history...
487
            $error = true;
488
        }
489
490
        return $error;
491
    }
492
493
    /**
494
     * Проверка параметров и загрузка необходимых экстендеров
495
     * return boolean статус загрузки
496
     */
497
    public function checkDL()
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
498
    {
499
        $this->debug->debug('Check DocLister parameters', 'checkDL', 2);
500
        $flag = true;
501
        $extenders = $this->getCFGDef('extender', '');
502
        $extenders = explode(",", $extenders);
503 58
        $tmp = $this->getCFGDef('requestActive', '') != '' || in_array('request', $extenders);
504
        if ($tmp && !$this->_loadExtender('request')) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
505 58
            //OR request in extender's parameter
506 58
            throw new Exception('Error load request extender');
507 58
        }
508 58
509 58
        $tmp = $this->getCFGDef('summary', '') != '' || in_array('summary', $extenders);
510 58
        if ($tmp && !$this->_loadExtender('summary')) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
511
            //OR summary in extender's parameter
512
            throw new Exception('Error load summary extender');
513
        }
514
515 58
        if ((int) $this->getCFGDef('display', 0) > 0 && ( //OR paginate in extender's parameter
516 58
            in_array('paginate', $extenders) || $this->getCFGDef('paginate', '') != '' ||
517
            $this->getCFGDef('TplPrevP', '') != '' || $this->getCFGDef('TplPage', '') != '' ||
518
            $this->getCFGDef('TplCurrentPage', '') != '' || $this->getCFGDef('TplWrapPaginate', '') != '' ||
519
            $this->getCFGDef('pageLimit', '') != '' || $this->getCFGDef('pageAdjacents', '') != '' ||
520
            $this->getCFGDef('PaginateClass', '') != '' || $this->getCFGDef('TplNextP', '') != ''
521 58
        ) && !$this->_loadExtender('paginate')
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
522 27
        ) {
523
            throw new Exception('Error load paginate extender');
524
        } else {
525
            if ((int) $this->getCFGDef('display', 0) == 0) {
526
                $extenders = $this->unsetArrayVal($extenders, 'paginate');
527 58
            }
528 58
        }
529
530
        if ($this->getCFGDef('prepare', '') != '' || $this->getCFGDef('prepareWrap') != '') {
531 58
            $this->_loadExtender('prepare');
532 58
        }
533 58
534
        $this->config->setConfig(['extender' => implode(",", $extenders)]);
0 ignored issues
show
Bug introduced by
The method setConfig() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

534
        $this->config->/** @scrutinizer ignore-call */ 
535
                       setConfig(['extender' => implode(",", $extenders)]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
535
        $this->debug->debugEnd("checkDL");
536 58
537
        return $flag;
538
    }
539
540 58
    /**
541 58
     * Удаление определенных данных из массива
542
     *
543 58
     * @param  array  $data  массив с данными
544
     * @param  mixed  $val  значение которые необходимо удалить из массива
545
     * @return array отчищеный массив с данными
546
     */
547
    private function unsetArrayVal($data, $val)
548
    {
549
        $out = [];
550
        if (is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
551
            foreach ($data as $item) {
552
                if ($item != $val) {
553 58
                    $out[] = $item;
554
                } else {
555 58
                    continue;
556 58
                }
557 58
            }
558 58
        }
559 58
560 58
        return $out;
561
    }
562
563 58
    /**
564 58
     * Генерация URL страницы
565
     *
566 58
     * @param  int  $id  уникальный идентификатор страницы
567
     * @return string URL страницы
568
     */
569
    public function getUrl($id = 0)
570
    {
571
        $id = ((int) $id > 0) ? (int) $id : $this->getCurrentMODXPageID();
572
573
        $link = $this->checkExtender('request') ? $this->extender['request']->getLink() : $this->getRequest();
574
        if ($id == $this->modx->config['site_start']) {
575
            $url = $this->modx->config['site_url'] . ($link != '' ? "?{$link}" : "");
576
        } else {
577
            $url = $this->modx->makeUrl($id, '', $link, $this->getCFGDef('urlScheme', ''));
0 ignored issues
show
Bug introduced by
The method makeUrl() does not exist on DocumentParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

577
            /** @scrutinizer ignore-call */ 
578
            $url = $this->modx->makeUrl($id, '', $link, $this->getCFGDef('urlScheme', ''));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
578
        }
579
580
        return $url;
581
    }
582
583
    /**
584
     * Получение массива документов из базы
585
     * @param  mixed  $tvlist  дополнительные параметры выборки
586
     * @return array Массив документов выбранных из базы
587
     */
588
    abstract public function getDocs($tvlist = '');
589
590
    /**
591
     * Подготовка результатов к отображению.
592
     *
593
     * @param  string  $tpl  шаблон
594
     * @return mixed подготовленный к отображению результат выборки
595
     */
596
    abstract public function _render($tpl = '');
597
598
    /**
599
     * Подготовка результатов к отображению в соответствии с настройками
600
     *
601
     * @param  string  $tpl  шаблон
602
     * @return string
603
     */
604
    public function render($tpl = '')
605
    {
606
        if ($this->extPaginate === null) {
607
            $this->toPlaceholders(count($this->_docs), 1, 'count');
608
        }
609
610
        $this->debug->debug(['Render data with template ' => $tpl], 'render', 2, ['html']);
611
        $out = '';
612
        if (1 == $this->getCFGDef('tree', '0')) {
613
            foreach ($this->_tree as $item) {
614
                $out .= $this->renderTree($item);
615
            }
616
            $out = $this->renderWrap($out);
617
        } else {
618
            $out = $this->_render($tpl);
619
        }
620
621
        if ($out) {
622
            $this->outData = $this->parseSource($out);
623
        }
624
        $this->debug->debugEnd('render');
625
626
        return $this->outData;
627
    }
628
629
    /**
630
     * @param  string  $out
631
     * @return string
632
     */
633
    public function parseSource($out = '')
634
    {
635
        if ($this->getCFGDef('parseDocumentSource', 1)) {
636
            $out = DLTemplate::getInstance($this->modx)->parseDocumentSource($out);
637
        }
638
639
        return $out;
640
    }
641
642
    /***************************************************
643
     ****************** CORE Block *********************
644
     ***************************************************/
645
646
    /**
647
     * Определение ID страницы открытой во фронте
648
     *
649
     * @return int
650
     */
651
    public function getCurrentMODXPageID()
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
652
    {
653
        $id = isset($this->modx->documentIdentifier) ? (int) $this->modx->documentIdentifier : 0;
654
        $docData = isset($this->modx->documentObject) ? $this->modx->documentObject : [];
655
656
        return empty($id)?\APIHelpers::getkey($docData, 'id', 0) : $id;
657 58
    }
658
659 58
    /**
660 58
     * Display and save error information
661
     *
662 58
     * @param  string  $message  error message
663
     * @param  integer  $code  error number
664
     * @param  string  $file  error on file
665
     * @param  integer  $line  error on line
666
     * @param  array  $trace  stack trace
667
     */
668
    public function ErrorLogger($message, $code, $file, $line, $trace)
0 ignored issues
show
Coding Style introduced by
Method name "DocLister::ErrorLogger" is not in camel caps format
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
669
    {
670
        if (abs($this->getCFGDef('debug', '0')) == '1') {
671
            $out = "CODE #" . $code . "<br />";
672
            $out .= "on file: " . $file . ":" . $line . "<br />";
673
            $out .= "<pre>";
674
            $out .= print_r($trace, 1);
675
            $out .= "</pre>";
676
677
            $message = $out . $message;
678
        }
679
        die($message);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
680
    }
681
682
    /**
683
     * Получение объекта DocumentParser
684
     *
685
     * @return DocumentParser
686
     */
687
    public function getMODX()
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
688
    {
689
        return $this->modx;
690
    }
691
692
    /**
693 58
     * load extenders
694
     *
695 58
     * @param  string  $ext  name extender separated by ,
696
     * @return boolean status load extenders
697
     * @throws Exception
698
     */
699
    public function loadExtender($ext = '')
700
    {
701
        $out = true;
702
        if ($ext != '') {
703
            $ext = explode(",", $ext);
704
            foreach ($ext as $item) {
705 58
                if ($item != '' && !$this->_loadExtender($item)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
706
                    throw new Exception('Error load ' . APIHelpers::e($item) . ' extender');
707 58
                }
708 58
            }
709
        }
710
711
        return $out;
712
    }
713
714
    /**
715
     * Получение информации из конфига
716
     *
717 58
     * @param  string  $name  имя параметра в конфиге
718
     * @param  mixed  $def  значение по умолчанию, если в конфиге нет искомого параметра
719
     * @return mixed значение из конфига
720
     */
721
    public function getCFGDef($name, $def = null)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
722
    {
723
        $out = $this->config->getCFGDef($name, $def);
724
        if ($name == 'dateFormat' && !empty($out) && is_scalar($out) && strpos($out, '%') !== false) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
725
            $replace = [
726
                '%d' => 'd', '%a' => 'D', '%e' => 'j', '%A' => 'l', '%u' => 'N', '%w' => 'w', '%j' => 'z',
727 58
                '%V' => 'W',
728
                '%B' => 'F', '%m' => 'm', '%b' => 'M',
729 58
                '%G' => 'o', '%Y' => 'Y', '%y' => 'y',
730
                '%P' => 'a', '%p' => 'A', '%l' => 'g', '%I' => 'h', '%H' => 'H', '%M' => 'i', '%S' => 's',
731
                '%z' => 'O', '%Z' => 'T',
732
                '%s' => 'U',
733
            ];
734
            $out = strtr($out, $replace);
735
        }
736
737
        return $out;
738
    }
739
740
    /**
741
     * Сохранение данных в массив плейсхолдеров
742
     *
743
     * @param  mixed  $data  данные
744
     * @param  int  $set  устанавливать ли глобальнй плейсхолдер MODX
745
     * @param  string  $key  ключ локального плейсхолдера
746
     * @return string
747
     */
748
    public function toPlaceholders($data, $set = 0, $key = 'contentPlaceholder')
749
    {
750
        $this->debug->debug(null, 'toPlaceholders', 2);
751
        if ($set == 0) {
752
            $set = $this->getCFGDef('contentPlaceholder', 0);
753
        }
754
        $this->_plh[$key] = $data;
755
        $id = $this->getCFGDef('id', '');
756
        if ($id != '') {
757
            $id .= ".";
758
        }
759
        $out = DLTemplate::getInstance($this->getMODX())->toPlaceholders($data, $set, $key, $id);
760
761
        $this->debug->debugEnd(
762
            "toPlaceholders",
763
            [$key . " placeholder" => $data],
764
            ['html']
765
        );
766
767
        return $out;
768
    }
769
770
    /**
771
     * Предварительная обработка данных перед вставкой в SQL запрос вида IN
772 28
     * Если данные в виде строки, то происходит попытка сформировать массив из этой строки по разделителю $sep
773
     * Точно по тому, по которому потом данные будут собраны обратно
774 28
     *
775 1
     * @param  integer|string|array  $data  данные для обработки
776 1
     * @param  string  $sep  разделитель
777 28
     * @param  boolean  $quote  заключать ли данные на выходе в кавычки
778
     * @return string обработанная строка
779
     */
780
    public function sanitarIn($data, $sep = ',', $quote = true)
781 28
    {
782 28
        return APIhelpers::sanitarIn($data, $sep, $quote);
783 28
    }
784 28
785 28
    /**
786 28
     * Загрузка кастомного лексикона
787 28
     *
788 28
     * В файле с кастомным лексиконом ключи в массиве дожны быть полные
789
     * Например:
790 28
     *      - core.bla-bla
791
     *      - paginate.next
792
     *
793
     * @param  string  $lang  имя языкового пакета
794
     * @return array
795
     */
796
    public function getCustomLang($lang = '')
797
    {
798
        $langDir = $this->getCFGDef('langDir', 'assets/snippets/DocLister/lang/');
799
800
        return $this->lexicon->fromFile($lang, '', $langDir);
801
    }
802
803
    /**
804
     * Загрузка языкового пакета
805
     *
806
     * @param  array|string  $name  ключ языкового пакета
807
     * @param  string  $lang  имя языкового пакета
808
     * @return array массив с лексиконом
809
     */
810
    public function loadLang($name = 'core', $lang = '')
811
    {
812
        $this->debug->debug(
813
            'Load language ' . $this->debug->dumpData($name) . "." . $this->debug->dumpData($lang),
814
            'loadlang',
815
            2
816
        );
817
        $this->debug->debugEnd("loadlang");
818
819
        return $this->lexicon->fromFile($name, $lang);
820
    }
821
822
    /**
823
     * Получение строки из языкового пакета
824
     *
825 58
     * @param  string  $name  имя записи в языковом пакете
826
     * @param  string  $def  Строка по умолчанию, если запись в языковом пакете не будет обнаружена
827 58
     * @return string строка в соответствии с текущими языковыми настройками
828 58
     */
829 58
    public function getMsg($name, $def = '')
830
    {
831 58
        return $this->lexicon->get($name, $def);
832 58
    }
833 58
834
    /**
835 58
     * Переменовывание элементов массива
836 58
     *
837 27
     * @param  array  $data  массив с данными
838 27
     * @param  string  $prefix  префикс ключей
839 58
     * @param  string  $suffix  суффикс ключей
840 58
     * @param  string  $sep  разделитель суффиксов, префиксов и ключей массива
841 58
     * @return array массив с переименованными ключами
842 58
     */
843
    public function renameKeyArr($data, $prefix = '', $suffix = '', $sep = '.')
844
    {
845
        return \APIHelpers::renameKeyArr($data, $prefix, $suffix, $sep);
846 58
    }
847 58
848 58
    /**
849 58
     * Установка локали
850 58
     *
851 58
     * @param  string  $locale  локаль
852 58
     * @return string имя установленной локали
853 58
     */
854
    public function setLocate($locale = '')
855 58
    {
856
        if ('' == $locale) {
857
            $locale = $this->getCFGDef('locale', '');
858
        }
859
        if ('' != $locale) {
860
            setlocale(LC_ALL, $locale);
861
        }
862
863
        return $locale;
864
    }
865
866
    /**
867
     * Шаблонизация дерева.
868
     * Перевод из массива в HTML в соответствии с указанным шаблоном
869
     *
870
     * @param  array  $data  массив сформированный как дерево
871
     * @return string строка для отображения пользователю
872
     */
873
    protected function renderTree($data)
874
    {
875
        $out = '';
876
        if (!empty($data['#childNodes'])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
877
            foreach ($data['#childNodes'] as $item) {
878
                $out .= $this->renderTree($item);
879
            }
880
        }
881
882
        $data[$this->getCFGDef("sysKey", "dl") . ".wrap"] = $this->renderWrap($out);
883
        $out = $this->parseChunk($this->getCFGDef('tpl', ''), $data);
884
885 58
        return $out;
886
    }
887 58
888
    /**
889
     * refactor $modx->getChunk();
890
     *
891
     * @param  string  $name  Template: chunk name || @CODE: template || @FILE: file with template
892
     * @return string html template with placeholders without data
893
     */
894
    private function _getChunk($name)
895
    {
896 58
        $this->debug->debug(['Get chunk by name' => $name], "getChunk", 2, ['html']);
897
        //without trim
898 58
        $tpl = DLTemplate::getInstance($this->getMODX())->getChunk($name);
899 58
        $tpl = $this->parseLang($tpl);
900 58
901 58
        $this->debug->debugEnd("getChunk");
902
903
        return $tpl;
904
    }
905 58
906
    /**
907
     * Замена в шаблоне фраз из лексикона
908
     *
909
     * @param  string  $tpl  HTML шаблон
910
     * @return string
911
     */
912
    public function parseLang($tpl)
913
    {
914
        $this->debug->debug(["parseLang" => $tpl], "parseLang", 2, ['html']);
915
        $tpl = $this->lexicon->parse($tpl);
916
        $this->debug->debugEnd("parseLang");
917
918
        return $tpl;
919
    }
920
921
    /**
922
     * refactor $modx->parseChunk();
923
     *
924
     * @param  string  $name  Template: chunk name || @CODE: template || @FILE: file with template
925
     * @param  array  $data  paceholder
926
     * @param  bool  $parseDocumentSource  render html template via DocumentParser::parseDocumentSource()
927
     * @return string html template with data without placeholders
928
     */
929
    public function parseChunk($name, $data = [], $parseDocumentSource = false)
930
    {
931
        $this->debug->debug(
932
            ["parseChunk" => $name, "With data" => print_r($data, 1)],
933
            "parseChunk",
934
            2,
935
            ['html', null]
936
        );
937
        $disablePHx = $this->getCFGDef('disablePHx', 0);
938
        $out = $this->DLTemplate->parseChunk($name, $data, $parseDocumentSource, (bool) $disablePHx);
939
        $out = $this->parseLang($out);
940
        if (empty($out)) {
941
            $this->debug->debug("Empty chunk: " . $this->debug->dumpData($name), '', 2);
942
        }
943
        $this->debug->debugEnd("parseChunk");
944
945
        return $out;
946
    }
947
948
    /**
949
     * Get full template from parameter name
950
     *
951
     * @param  string  $name  param name
952
     * @param  string  $val  default value
953
     *
954
     * @return string html template from parameter
955
     */
956
    public function getChunkByParam($name, $val = '')
957
    {
958
        $data = $this->getCFGDef($name, $val);
959
        $data = $this->_getChunk($data);
960
961
        return $data;
962
    }
963
964
    /**
965
     * Помещение html кода в какой-то блок обертку
966
     *
967
     * @param  string  $data  html код который нужно обернуть в ownerTPL
968
     * @return string результатирующий html код
969
     */
970
    public function renderWrap($data)
971
    {
972
        $out = $data;
973
        $docs = count($this->_docs) - $this->skippedDocs;
974
        $wrap = $this->getCFGDef('prepareWrap');
975
        if ((($this->getCFGDef("noneWrapOuter",
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->getCFGDef('noneW...TPL)) || ! empty($wrap), Probably Intended Meaning: $this->getCFGDef('noneWr...TPL) || ! empty($wrap))
Loading history...
976
            "1") && $docs == 0) || $docs > 0) && !empty($this->ownerTPL) || !empty($wrap)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
977
            $this->debug->debug("", "renderWrapTPL", 2);
978
            $parse = true;
979
            $plh = [$this->getCFGDef("sysKey", "dl") . ".wrap" => $data];
980
            /**
981
             * @var $extPrepare prepare_DL_Extender
982
             */
983
            $extPrepare = $this->getExtender('prepare');
984
            if ($extPrepare) {
0 ignored issues
show
introduced by
$extPrepare is of type prepare_DL_Extender, thus it always evaluated to true.
Loading history...
985
                $params = $extPrepare->init($this, [
986
                    'data' => [
987
                        'docs' => $this->_docs,
988
                        'placeholders' => $plh,
989
                    ],
990
                    'nameParam' => 'prepareWrap',
991
                    'return' => 'placeholders',
992
                ]);
993
                if ($params === false) {
994
                    $out = $data;
995
                    $parse = false;
996
                }
997
                $plh = $params;
998
            }
999
            if ($parse && !empty($this->ownerTPL)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1000
                $this->debug->updateMessage(
0 ignored issues
show
Bug introduced by
The method updateMessage() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1000
                $this->debug->/** @scrutinizer ignore-call */ 
1001
                              updateMessage(
Loading history...
1001
                    ["render ownerTPL" => $this->ownerTPL, "With data" => print_r($plh, 1)],
1002
                    "renderWrapTPL",
1003
                    ['html', null]
1004
                );
1005
                $out = $this->parseChunk($this->ownerTPL, $plh);
1006
            }
1007
            if (empty($this->ownerTPL)) {
1008
                $this->debug->updateMessage("empty ownerTPL", "renderWrapTPL");
1009
            }
1010
            $this->debug->debugEnd("renderWrapTPL");
1011
        }
1012
1013
        return $out;
1014
    }
1015
1016
    /**
1017
     * Единые обработки массива с данными о документе для всех контроллеров
1018
     *
1019
     * @param  array  $data  массив с данными о текущем документе
1020
     * @param  int  $i  номер итерации в цикле
1021
     * @return array массив с данными которые можно использовать в цикле render метода
1022
     */
1023
    protected function uniformPrepare(&$data, $i = 0)
1024
    {
1025
        $class = [];
1026
1027
        $iterationName = ($i % 2 == 1) ? 'Odd' : 'Even';
1028
        $tmp = strtolower($iterationName);
1029
        $class[] = $this->getCFGDef($tmp . 'Class', $tmp);
1030
1031
        $this->renderTPL = $this->getCFGDef('tplId' . $i, $this->renderTPL);
1032
        $this->renderTPL = $this->getCFGDef('tpl' . $iterationName, $this->renderTPL);
1033
        $iteration = $i;
1034
1035
        if ($this->extPaginate) {
1036
            $offset = $this->getCFGDef(
1037
                'reversePagination',
1038
                0
1039
            ) && $this->extPaginate->currentPage() > 1 ? $this->extPaginate->totalPage() * $this->getCFGDef(
1040
                'display',
1041
                0
1042
            ) - $this->extPaginate->totalDocs() : 0;
1043
            if ($this->getCFGDef('maxDocs', 0) && !$this->getCFGDef(
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1044
                'reversePagination',
1045
                0
1046
            ) && $this->extPaginate->currentPage() == $this->extPaginate->totalPage()
1047
            ) {
1048
                $iteration += $this->getCFGDef('display', 0);
1049
            }
1050
            $iteration += $this->getCFGDef(
1051
                'display',
1052
                0
1053
            ) * ($this->extPaginate->currentPage() - 1) - $offset;
1054
        }
1055
1056
        $data[$this->getCFGDef(
1057
            "sysKey",
1058
            "dl"
1059
        ) . '.full_iteration'] = $iteration;
1060
1061
        if ($i == 1) {
1062
            $this->renderTPL = $this->getCFGDef('tplFirst', $this->renderTPL);
1063
            $class[] = $this->getCFGDef('firstClass', 'first');
1064
            $data[$this->getCFGDef("sysKey", "dl") . '.is_first'] = 1;
1065
        } else {
1066
            $data[$this->getCFGDef("sysKey", "dl") . '.is_first'] = 0;
1067
        }
1068
1069
        if ($i == (count($this->_docs) - $this->skippedDocs)) {
1070
            $this->renderTPL = $this->getCFGDef('tplLast', $this->renderTPL);
1071
            $class[] = $this->getCFGDef('lastClass', 'last');
1072
            $data[$this->getCFGDef("sysKey", "dl") . '.is_last'] = 1;
1073
        } else {
1074
            $data[$this->getCFGDef("sysKey", "dl") . '.is_last'] = 0;
1075
        }
1076
1077
        if (array_key_exists('id', $data) && $this->modx->documentIdentifier == $data['id']) {
1078
            $this->renderTPL = $this->getCFGDef('tplCurrent', $this->renderTPL);
1079
            $data[$this->getCFGDef(
1080
                "sysKey",
1081
                "dl"
1082
            ) . '.active'] = 1; //[+active+] - 1 if $modx->documentIdentifer equal ID this element
1083
            $class[] = $this->getCFGDef('currentClass', 'current');
1084
        } else {
1085
            $data[$this->getCFGDef("sysKey", "dl") . '.active'] = 0;
1086
        }
1087
1088
        $class = implode(" ", $class);
1089
        $data[$this->getCFGDef("sysKey", "dl") . '.class'] = $class;
1090
1091
        /**
1092
         * @var $extE e_DL_Extender
1093
         */
1094
        $extE = $this->getExtender('e', true, true);
1095
        if ($out = $extE->init($this, compact('data'))) {
1096
            if (is_array($out)) {
1097
                $data = $out;
1098
            }
1099
        }
1100
1101
        return compact('class', 'iterationName');
1102
    }
1103
1104
    /**
1105
     * Формирование JSON ответа
1106
     *
1107
     * @param  array  $data  массив данных которые подготавливаются к выводу в JSON
1108
     * @param  mixed  $fields  список полей учавствующих в JSON ответе. может быть либо массив, либо строка с разделителем , (запятая)
1109
     * @param  array  $array  данные которые необходимо примешать к ответу на каждой записи $data
1110
     * @return string JSON строка
1111
     */
1112
    public function getJSON($data, $fields, $array = [])
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1113
    {
1114
        $out = [];
1115
        $fields = is_array($fields) ? $fields : explode(",", $fields);
1116
        if (is_array($array) && count($array) > 0) {
1117
            $tmp = [];
1118
            foreach ($data as $i => $v) { //array_merge not valid work with integer index key
1119
                $tmp[$i] = (isset($array[$i]) ? array_merge($v, $array[$i]) : $v);
1120
            }
1121
            $data = $tmp;
1122
        }
1123
1124
        foreach ($data as $num => $doc) {
1125
            $tmp = [];
1126
            foreach ($doc as $name => $value) {
1127
                if (in_array($name, $fields) || (isset($fields[0]) && $fields[0] == '1')) {
1128
                    $tmp[str_replace(".", "_", $name)] = $value; //JSON element name without dot
1129
                }
1130
            }
1131
            $out[$num] = $tmp;
1132
        }
1133
1134
        $format = $this->getCFGDef('JSONformat', 'old');
1135
        switch ($format) {
1136
            case 'new':
1137
                $return['rows'] = array_values($out);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = array(); before regardless.
Loading history...
1138
                $return['total'] = $this->getChildrenCount();
1139
                break;
1140
            case 'simple':
1141
                $return = array_values($out);
1142
                break;
1143
            default:
1144
                $return = $out;
1145
                break;
1146
        }
1147
        $this->outData = json_encode($return);
1148
        $this->isErrorJSON($return);
1149
1150
        return $this->getCFGDef('debug') ? jsonHelper::json_format($this->outData) : $this->outData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getCFGDef(...tData) : $this->outData could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1151
    }
1152
1153
    /**
1154
     * @param  array  $item
1155
     * @param  null  $extSummary
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $extSummary is correct as it would always require null to be passed?
Loading history...
1156
     * @param  string  $introField
1157
     * @param  string  $contentField
1158
     * @return mixed|string
1159
     */
1160
    protected function getSummary(array $item = [], $extSummary = null, $introField = '', $contentField = '')
1161
    {
1162
        $out = '';
1163
1164
        if (is_null($extSummary)) {
0 ignored issues
show
introduced by
The condition is_null($extSummary) is always true.
Loading history...
1165
            /**
1166
             * @var $extSummary summary_DL_Extender
1167
             */
1168
            $extSummary = $this->getExtender('summary');
1169
        }
1170
        $introField = $this->getCFGDef("introField", $introField);
1171
        $contentField = $this->getCFGDef("contentField", $contentField);
1172
1173
        if (!empty($introField) && !empty($item[$introField]) && mb_strlen($item[$introField], 'UTF-8') > 0) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1174
            $out = $item[$introField];
1175
        } else {
1176
            if (!empty($contentField) && !empty($item[$contentField]) && mb_strlen($item[$contentField], 'UTF-8') > 0) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1177
                $out = $extSummary->init($this, [
1178
                    "content" => $item[$contentField],
1179
                    "action" => $this->getCFGDef("summary", ""),
1180
                    "cutSummary" => $this->getCFGDef('cutSummary'),
1181
                    "dotSummary" => $this->getCFGDef('dotSummary'),
1182
                    'breakSummary' => $this->getCFGDef('breakSummary'),
1183
                ]);
1184
            }
1185
        }
1186
1187
        return $out;
1188
    }
1189
1190
    /**
1191
     * @param  string  $name  extender name
1192
     * @return boolean status extender load
1193
     */
1194
    public function checkExtender($name)
1195
    {
1196
        return (isset($this->extender[$name]) && $this->extender[$name] instanceof $name . "_DL_Extender");
1197
    }
1198
1199
    /**
1200
     * @param $name
1201
     * @param $obj
1202
     */
1203
    public function setExtender($name, $obj)
1204
    {
1205
        $this->extender[$name] = $obj;
1206
    }
1207
1208
    /**
1209
     * Вытащить экземпляр класса экстендера из общего массива экстендеров
1210
     *
1211
     * @param  string  $name  имя экстендера
1212
     * @param  bool  $autoload  Если экстендер не загружен, то пытаться ли его загрузить
1213
     * @param  bool  $nop  если экстендер не загружен, то загружать ли xNop
1214
     * @return null|xNop
1215
     */
1216
    public function getExtender($name, $autoload = false, $nop = false)
1217
    {
1218
        $out = null;
1219
        if ((is_scalar($name) && $this->checkExtender($name)) || ($autoload && $this->_loadExtender($name))) {
1220
            $out = $this->extender[$name];
1221
        }
1222
        if ($nop && is_null($out)) {
1223
            $out = new xNop();
1224
        }
1225
1226
        return $out;
1227
    }
1228
1229
    /**
1230
     * load extender
1231
     *
1232
     * @param  string  $name  name extender
1233
     * @return boolean $flag status load extender
1234
     */
1235
    protected function _loadExtender($name)
1236
    {
1237
        $this->debug->debug('Load Extender ' . $this->debug->dumpData($name), 'LoadExtender', 2);
1238
        $flag = false;
1239
1240
        $classname = ($name != '') ? $name . "_DL_Extender" : "";
1241
        if ($classname != '' && isset($this->extender[$name]) && $this->extender[$name] instanceof $classname) {
1242
            $flag = true;
1243
        } else {
1244
            if (!class_exists($classname, false) && $classname != '') {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1245
                if (file_exists(dirname(__FILE__) . "/extender/" . $name . ".extender.inc")) {
1246
                    include_once dirname(__FILE__) . "/extender/" . $name . ".extender.inc";
1247
                }
1248 58
            }
1249
            if (class_exists($classname, false) && $classname != '') {
1250 58
                $this->extender[$name] = new $classname($this, $name);
1251
                $flag = true;
1252
            }
1253
        }
1254
        if (!$flag) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1255
            $this->debug->debug("Error load Extender " . $this->debug->dumpData($name));
1256
        }
1257
        $this->debug->debugEnd('LoadExtender');
1258
1259
        return $flag;
1260
    }
1261
1262
    /*************************************************
1263
     ****************** IDs BLOCK ********************
1264
     ************************************************/
1265
1266
    /**
1267
     * Очистка массива $IDs по которому потом будет производиться выборка документов
1268
     *
1269
     * @param  mixed  $IDs  список id документов по которым необходима выборка
1270 58
     * @return array очищенный массив
1271
     */
1272 58
    public function setIDs($IDs)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (4) exceeds 3; consider refactoring the function
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1273 58
    {
1274 58
        $this->debug->debug('set ID list ' . $this->debug->dumpData($IDs), 'setIDs', 2);
1275 58
        $IDs = $this->cleanIDs($IDs);
1276 58
        $type = $this->getCFGDef('idType', 'parents');
1277
        $depth = $this->getCFGDef('depth', '');
1278
        if ($type == 'parents' && $depth > 0) {
1279
            $out = $this->extCache->load('children');
1280 58
            if ($out === false) {
0 ignored issues
show
introduced by
The condition $out === false is always false.
Loading history...
1281
                $tmp = $IDs;
1282
                do {
1283
                    if (count($tmp) > 0) {
1284
                        $tmp = $this->getChildrenFolder($tmp);
1285
                        $IDs = array_merge($IDs, $tmp);
1286
                    }
1287
                } while ((--$depth) > 0);
1288
                $this->extCache->save($IDs, 'children');
1289 58
            } else {
1290
                $IDs = $out;
1291 58
            }
1292 58
        }
1293
        $this->debug->debugEnd("setIDs");
1294 58
1295 58
        return ($this->IDs = $IDs);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->IDs = $IDs also could return the type string which is incompatible with the documented return type array.
Loading history...
1296
    }
1297
1298 58
    /**
1299 2
     * @return int
1300 2
     */
1301 2
    public function getIDs()
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1302 2
    {
1303 58
        return $this->IDs;
1304 58
    }
1305 58
1306 58
    /**
1307
     * Очистка данных и уникализация списка цифр.
1308 58
     * Если был $IDs был передан как строка, то эта строка будет преобразована в массив по разделителю $sep
1309
     * @param  mixed  $IDs  данные для обработки
1310
     * @param  string  $sep  разделитель
1311 58
     * @return array очищенный массив с данными
1312
     */
1313 58
    public function cleanIDs($IDs, $sep = ',')
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1314
    {
1315
        $this->debug->debug(
1316
            'clean IDs ' . $this->debug->dumpData($IDs) . ' with separator ' . $this->debug->dumpData($sep),
1317
            'cleanIDs',
1318
            2
1319
        );
1320
        
1321
        return APIHelpers::cleanIDs($IDs, $sep);
1322
    }
1323
1324
    /**
1325
     * Проверка массива с id-шниками документов для выборки
1326 58
     * @return boolean пригодны ли данные для дальнейшего использования
1327
     */
1328 58
    protected function checkIDs()
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1329 58
    {
1330 58
        return (is_array($this->IDs) && count($this->IDs) > 0) ? true : false;
0 ignored issues
show
introduced by
The condition is_array($this->IDs) is always false.
Loading history...
1331 58
    }
1332 58
1333
    /**
1334
     * Get all field values from array documents
1335
     *
1336
     * @param  string  $userField  field name
1337
     * @param  boolean  $uniq  Only unique values
1338
     * @return array all field values
1339
     * @global array $_docs all documents
1340
     */
1341
    public function getOneField($userField, $uniq = false)
1342
    {
1343
        $out = [];
1344
        foreach ($this->_docs as $doc => $val) {
1345
            if (isset($val[$userField]) && (($uniq && !in_array($val[$userField], $out)) || !$uniq)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1346
                $out[$doc] = $val[$userField];
1347 58
            }
1348
        }
1349 58
1350
        return $out;
1351
    }
1352
1353
    /**
1354
     * @return DLCollection
1355
     */
1356
    public function docsCollection()
1357
    {
1358
        return new DLCollection($this->modx, $this->_docs);
1359
    }
1360
1361
    /**********************************************************
1362
     ********************** SQL BLOCK *************************
1363
     *********************************************************/
1364
1365
    /**
1366
     * Подсчет документов удовлетворящиюх выборке
1367 58
     *
1368
     * @return int Число дочерних документов
1369 58
     */
1370 58
    abstract public function getChildrenCount();
1371 58
1372
    /**
1373 58
     * Выборка документов которые являются дочерними относительно $id документа и в тоже время
1374 58
     * являются родителями для каких-нибудь других документов
1375 58
     *
1376 58
     * @param  string|array  $id  значение PrimaryKey родителя
1377 58
     * @return array массив документов
1378 58
     */
1379 58
    abstract public function getChildrenFolder($id);
1380 58
1381 58
    /**
1382 58
     * @param  string  $group
1383 58
     * @return string
1384 58
     */
1385 58
    protected function getGroupSQL($group = '')
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1386
    {
1387 58
        $out = '';
1388
        if ($group != '') {
1389
            $out = 'GROUP BY ' . $group;
1390
        }
1391
1392
        return $out;
1393
    }
1394
1395
    /**
1396
     *    Sorting method in SQL queries
1397
     *
1398
     * @param  string  $sortName  default sort field
1399
     * @param  string  $orderDef  default order (ASC|DESC)
1400
     *
1401
     * @return string Order by for SQL
1402
     * @global string $order
1403
     * @global string $orderBy
1404
     * @global string $sortBy
1405
     *
1406
     */
1407
    protected function SortOrderSQL($sortName, $orderDef = 'DESC')
0 ignored issues
show
Coding Style introduced by
Function's nesting level (5) exceeds 3; consider refactoring the function
Loading history...
Coding Style introduced by
Method name "DocLister::SortOrderSQL" is not in camel caps format
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1408
    {
1409
        $this->debug->debug('', 'sortORDER', 2);
1410
1411
        $sort = '';
1412
        switch ($this->getCFGDef('sortType', '')) {
1413
            case 'none':
1414
                break;
1415
            case 'doclist':
1416
                $idList = $this->sanitarIn($this->IDs, ',', false);
1417
                $out = ['orderBy' => "FIND_IN_SET({$this->getCFGDef('sortBy', $this->getPK())}, '{$idList}')"];
1418
                $this->config->setConfig($out); //reload config;
1419
                $sort = "ORDER BY " . $out['orderBy'];
1420
                break;
1421
            default:
1422
                $out = ['orderBy' => '', 'order' => '', 'sortBy' => ''];
1423
                if (($tmp = $this->getCFGDef('orderBy', '')) != '') {
1424
                    $out['orderBy'] = $tmp;
1425
                } else {
1426
                    switch (true) {
1427
                        case ('' != ($tmp = $this->getCFGDef('sortDir', ''))): //higher priority than order
1428
                            $out['order'] = $tmp;
1429
                        // no break
1430
                        case ('' != ($tmp = $this->getCFGDef('order', ''))):
1431
                            $out['order'] = $tmp;
1432
                            // no break
1433
                    }
1434
                    if ('' == $out['order'] || !in_array(strtoupper($out['order']), ['ASC', 'DESC'])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1435
                        $out['order'] = $orderDef; //Default
1436
                    }
1437
1438
                    $out['sortBy'] = (($tmp = $this->getCFGDef('sortBy', '')) != '') ? $tmp : $sortName;
1439
                    $out['orderBy'] = $out['sortBy'] . " " . $out['order'];
1440
                }
1441
                $this->config->setConfig($out); //reload config;
1442
                $sort = "ORDER BY " . $out['orderBy'];
1443
                break;
1444
        }
1445
        $this->debug->debugEnd("sortORDER", 'Get sort order for SQL: ' . $this->debug->dumpData($sort));
1446
1447
        return $sort;
1448
    }
1449
1450
    /**
1451 27
     * Получение LIMIT вставки в SQL запрос
1452
     *
1453 27
     * @return string LIMIT вставка в SQL запрос
1454 27
     */
1455 27
    protected function LimitSQL($limit = 0, $offset = 0)
0 ignored issues
show
Coding Style introduced by
Method name "DocLister::LimitSQL" is not in camel caps format
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1456 27
    {
1457
        $this->debug->debug('', 'limitSQL', 2);
1458 27
        $ret = '';
1459
        if ($limit == 0) {
1460
            $limit = $this->getCFGDef('display', 0);
1461
        }
1462
        $maxDocs = $this->getCFGDef('maxDocs', 0);
1463
        if ($maxDocs > 0 && $limit > $maxDocs) {
1464
            $limit = $maxDocs;
1465
        }
1466
        if ($offset == 0) {
1467
            $offset = $this->getCFGDef('offset', 0);
1468
        }
1469
        $offset += $this->getCFGDef('start', 0);
1470
        $total = $this->getCFGDef('total', 0);
1471
        if ($limit < ($total - $limit)) {
1472
            $limit = $total - $offset;
1473 27
        }
1474
1475 27
        if ($limit != 0) {
1476
            $ret = "LIMIT " . (int) $offset . "," . (int) $limit;
1477 27
        } else {
1478 27
            if ($offset != 0) {
1479 27
                /**
1480
                 * To retrieve all rows from a certain offset up to the end of the result set, you can use some large number for the second parameter
1481 27
                 * @see http://dev.mysql.com/doc/refman/5.0/en/select.html
1482
                 */
1483
                $ret = "LIMIT " . (int) $offset . ",18446744073709551615";
1484
            }
1485
        }
1486
        $this->debug->debugEnd("limitSQL", "Get limit for SQL: " . $this->debug->dumpData($ret));
1487 27
1488 27
        return $ret;
1489 27
    }
1490
1491
    /**
1492
     * Clean up the modx and html tags
1493 27
     *
1494
     * @param  string  $data  String for cleaning
1495
     * @param  string  $charset
1496 27
     * @return string Clear string
1497
     */
1498
    public function sanitarData($data, $charset = 'UTF-8')
1499
    {
1500 27
        return APIHelpers::sanitarTag($data, $charset);
1501 27
    }
1502 27
1503
    /**
1504 27
     * run tree build
1505 27
     *
1506
     * @param  string  $idField  default name id field
1507 27
     * @param  string  $parentField  default name parent field
1508 27
     * @return array
1509 27
     */
1510 27
    public function treeBuild($idField = 'id', $parentField = 'parent')
1511 27
    {
1512
        return $this->_treeBuild(
1513 27
            $this->_docs,
1514
            $this->getCFGDef('idField', $idField),
1515
            $this->getCFGDef('parentField', $parentField)
1516
        );
1517
    }
1518
1519
    /**
1520
     * @see: https://github.com/DmitryKoterov/DbSimple/blob/master/lib/DbSimple/Generic.php#L986
1521 27
     *
1522
     * @param  array  $data  Associative data array
1523 27
     * @param  string  $idName  name ID field in associative data array
1524 27
     * @param  string  $pidName  name parent field in associative data array
1525 27
     * @return array
1526 27
     */
1527 27
    private function _treeBuild($data, $idName, $pidName)
1528 27
    {
1529 27
        $children = []; // children of each ID
1530
        $ids = [];
1531
        foreach ($data as $i => $r) {
1532 27
            $row = &$data[$i];
1533 27
            $id = $row[$idName];
1534 27
            $pid = $row[$pidName];
1535 27
            $children[$pid][$id] = &$row;
1536 27
            if (!isset($children[$id])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1537 27
                $children[$id] = [];
1538
            }
1539
            $row['#childNodes'] = &$children[$id];
1540
            $ids[$row[$idName]] = true;
1541 27
        }
1542 27
        // Root elements are elements with non-found PIDs.
1543 27
        $this->_tree = [];
1544
        foreach ($data as $i => $r) {
1545
            $row = &$data[$i];
1546
            if (!isset($ids[$row[$pidName]])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1547
                $this->_tree[$row[$idName]] = $row;
1548
            }
1549
        }
1550
1551
        return $this->_tree;
1552 27
    }
1553
1554 27
    /**
1555
     * Получение PrimaryKey основной таблицы.
1556
     * По умолчанию это id. Переопределить можно в контроллере присвоив другое значение переменной idField
1557
     * @param  bool  $full  если true то возвращается значение для подстановки в запрос
1558
     * @return string PrimaryKey основной таблицы
1559
     */
1560
    public function getPK($full = true)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1561
    {
1562
        $idField = isset($this->idField) ? $this->idField : 'id';
1563
        if ($full) {
1564 58
            $idField = '`' . $idField . '`';
1565
            if (!empty($this->alias)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1566 58
                $idField = '`' . $this->alias . '`.' . $idField;
1567
            }
1568
        }
1569
1570
        return $idField;
1571
    }
1572
1573
    /**
1574
     * Получение Parent key
1575
     * По умолчанию это parent. Переопределить можно в контроллере присвоив другое значение переменной parentField
1576
     * @param  bool  $full  если true то возвращается значение для подстановки в запрос
1577
     * @return string Parent Key основной таблицы
1578
     */
1579
    public function getParentField($full = true)
1580
    {
1581
        $parentField = isset($this->parentField) ? $this->parentField : '';
1582
        if ($full && !empty($parentField)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1583
            $parentField = '`' . $parentField . '`';
1584
            if (!empty($this->alias)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1585
                $parentField = '`' . $this->alias . '`.' . $parentField;
1586
            }
1587
        }
1588
1589
        return $parentField;
1590
    }
1591
1592
    /**
1593
     * Разбор фильтров
1594
     * OR(AND(filter:field:operator:value;filter2:field:oerpator:value);(...)), etc.
1595
     *
1596
     * @param  string  $filter_string  строка со всеми фильтрами
1597
     * @return mixed результат разбора фильтров
1598
     */
1599
    protected function getFilters($filter_string)
0 ignored issues
show
Coding Style introduced by
Function's nesting level (5) exceeds 3; consider refactoring the function
Loading history...
1600
    {
1601
        $this->debug->debug("getFilters: " . $this->debug->dumpData($filter_string), 'getFilter', 1);
1602
        // the filter parameter tells us, which filters can be used in this query
1603
        $filter_string = ltrim(trim($filter_string, ';'));
1604
        $output = ['join' => '', 'where' => ''];
1605
        if (!$filter_string) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1606
            return $output;
1607
        }
1608
        $logic_op_found = false;
1609
        $joins = $wheres = [];
1610
        foreach ($this->_logic_ops as $op => $sql) {
1611
            if (strpos($filter_string, $op) === 0) {
1612
                $logic_op_found = true;
1613
                $subfilters = mb_substr($filter_string, strlen($op) + 1, mb_strlen($filter_string, "UTF-8"), "UTF-8");
1614
                $subfilters = $this->smartSplit($subfilters);
1615
                $lastFilter = '';
1616
                foreach ($subfilters as $filter) {
1617
                    /**
1618
                     * С правой стороны не выполняется trim, т.к. там находятся значения. А они могу быть чувствительны к пробелам
1619
                     */
1620
                    $subfilter = $this->getFilters(ltrim($filter) . ltrim($lastFilter));
1621
                    if (!$subfilter) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1622
                        $lastFilter = explode(';', $filter, 2);
1623
                        $subfilter = isset($lastFilter[1]) ? $this->getFilters($lastFilter[1]) : '';
1624
                        $lastFilter = $lastFilter[0];
1625
                        if (!$subfilter) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1626 27
                            continue;
1627
                        }
1628 27
                    }
1629 27
                    if ($subfilter['join']) {
1630 27
                        $joins[] = $subfilter['join'];
1631 27
                    }
1632 27
                    if ($subfilter['where']) {
1633 27
                        $wheres[] = $subfilter['where'];
1634 27
                    }
1635
                }
1636 27
                $output['join'] = !empty($joins) ? implode(' ', array_reverse($joins)) : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1637
                $output['where'] = !empty($wheres) ? '(' . implode($sql, array_reverse($wheres)) . ')' : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1638
            }
1639
        }
1640
1641
        if (!$logic_op_found) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1642
            $filter = $this->loadFilter($filter_string);
1643
            if (!$filter) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1644
                $this->debug->warning('Error while loading DocLister filter "' . $this->debug->dumpData($filter_string) . '": check syntax!');
0 ignored issues
show
Bug introduced by
The method warning() does not exist on xNop. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1644
                $this->debug->/** @scrutinizer ignore-call */ 
1645
                              warning('Error while loading DocLister filter "' . $this->debug->dumpData($filter_string) . '": check syntax!');
Loading history...
1645 9
                $output = false;
1646
            } else {
1647 9
                $output['join'] = $filter->get_join();
1648 9
                $output['where'] = $filter->get_where();
1649 9
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
1650 9
            }
1651 9
        }
1652 9
        $this->debug->debug('getFilter');
1653 9
1654
        return $output;
1655 9
    }
1656
1657
    /**
1658
     * @return mixed
1659
     */
1660
    public function filtersWhere()
1661
    {
1662
        return APIHelpers::getkey($this->_filters, 'where', '');
1663
    }
1664
1665 58
    /**
1666
     * @return mixed
1667 58
     */
1668
    public function filtersJoin()
1669 58
    {
1670 58
        return APIHelpers::getkey($this->_filters, 'join', '');
1671 58
    }
1672
1673 19
    /**
1674 19
     * @param  string  $join
1675 19
     * @return $this
1676 19
     */
1677 19
    public function setFiltersJoin($join = '')
1678 10
    {
1679 10
        if (!empty($join)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1680 10
            if (!empty($this->_filters['join'])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1681 10
                $this->_filters['join'] .= ' ' . $join;
1682 10
            } else {
1683
                $this->_filters['join'] = $join;
1684
            }
1685
        }
1686 10
1687 10
        return $this;
1688 6
    }
1689 6
1690 6
    /**
1691 6
     * Приведение типа поля
1692 5
     *
1693
     * @param $field string имя поля
1694 1
     * @param $type string тип фильтрации
1695 10
     * @return string имя поля с учетом приведения типа
1696 10
     */
1697 10
    public function changeSortType($field, $type)
1698 10
    {
1699 10
        $type = trim($type);
1700 10
        switch (strtoupper($type)) {
1701 10
            case 'DECIMAL':
1702 10
                $field = 'CAST(' . $field . ' as DECIMAL(10,2))';
1703 10
                break;
1704 10
            case 'UNSIGNED':
1705 19
                $field = 'CAST(' . $field . ' as UNSIGNED)';
1706
                break;
1707 19
            case 'BINARY':
1708 19
                $field = 'CAST(' . $field . ' as BINARY)';
1709 19
                break;
1710 5
            case 'DATETIME':
1711 5
                $field = 'CAST(' . $field . ' as DATETIME)';
1712 5
                break;
1713 18
            case 'SIGNED':
1714 18
                $field = 'CAST(' . $field . ' as SIGNED)';
1715
                break;
1716
        }
1717 19
1718 19
        return $field;
1719
    }
1720 19
1721
    /**
1722
     * Загрузка фильтра
1723
     * @param  string  $filter  срока с параметрами фильтрации
1724
     * @return bool
1725
     */
1726
    protected function loadFilter($filter)
1727
    {
1728
        $this->debug->debug('Load filter ' . $this->debug->dumpData($filter), 'loadFilter', 2);
1729
        $out = false;
1730
        $fltr_params = explode(':', $filter, 2);
1731
        $fltr = APIHelpers::getkey($fltr_params, 0, null);
1732
        /**
1733
         * @var tv_DL_filter|content_DL_filter $fltr_class
1734
         */
1735
        $fltr_class = $fltr . '_DL_filter';
1736
        // check if the filter is implemented
1737
        if (!is_null($fltr)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1738
            if (!class_exists($fltr_class) && file_exists(__DIR__ . '/filter/' . $fltr . '.filter.php')) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
Bug introduced by
$fltr_class of type content_DL_filter|tv_DL_filter is incompatible with the type string expected by parameter $class of class_exists(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1738
            if (!class_exists(/** @scrutinizer ignore-type */ $fltr_class) && file_exists(__DIR__ . '/filter/' . $fltr . '.filter.php')) {
Loading history...
1739
                require_once dirname(__FILE__) . '/filter/' . $fltr . '.filter.php';
1740
            }
1741
            if (class_exists($fltr_class)) {
1742
                $this->totalFilters++;
1743
                $fltr_obj = new $fltr_class();
1744
                if ($fltr_obj->init($this, $filter)) {
1745
                    $out = $fltr_obj;
1746
                } else {
1747
                    $this->debug->error("Wrong filter parameter: '{$this->debug->dumpData($filter)}'", 'Filter');
1748
                }
1749
            }
1750
        }
1751
        if (!$out) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after NOT operator; 0 found
Loading history...
1752
            $this->debug->error("Error load Filter: '{$this->debug->dumpData($filter)}'", 'Filter');
1753
        }
1754
1755
        $this->debug->debugEnd("loadFilter");
1756
1757
        return $out;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $out also could return the type object which is incompatible with the documented return type boolean.
Loading history...
1758
    }
1759
1760
    /**
1761
     * Общее число фильтров
1762
     * @return int
1763 8
     */
1764
    public function getCountFilters()
1765 8
    {
1766 8
        return (int) $this->totalFilters;
1767 8
    }
1768 1
1769 1
    /**
1770 8
     * Выполнить SQL запрос
1771 1
     * @param  string  $q  SQL запрос
1772 1
     */
1773 8
    public function dbQuery($q)
1774 1
    {
1775 1
        $this->debug->debug($q, "query", 1, 'sql');
1776 7
        $out = $this->modx->db->query($q);
1777 1
        $this->debug->debugEnd("query");
1778 1
1779 6
        return $out;
1780
    }
1781
1782 8
    /**
1783
     * Экранирование строки в SQL запросе LIKE
1784 8
     * @see: http://stackoverflow.com/a/3683868/2323306
1785
     *
1786
     * @param  string  $field  поле по которому осуществляется поиск
1787
     * @param  string  $value  искомое значение
1788
     * @param  string  $escape  экранирующий символ
1789
     * @param  string  $tpl  шаблон подстановки значения в SQL запрос
1790
     * @return string строка для подстановки в SQL запрос
1791
     */
1792 19
    public function LikeEscape($field, $value, $escape = '=', $tpl = '%[+value+]%')
0 ignored issues
show
Coding Style introduced by
Method name "DocLister::LikeEscape" is not in camel caps format
Loading history...
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
1793
    {
1794 19
        return sqlHelper::LikeEscape($this->modx, $field, $value, $escape, $tpl);
1795 19
    }
1796 19
1797 19
    /**
1798
     * Получение REQUEST_URI без GET-ключа с
1799
     * @return string
1800
     */
1801 19
    public function getRequest()
1802
    {
1803 19
        $query = parse_url(MODX_SITE_URL . $_SERVER['REQUEST_URI'], PHP_URL_QUERY) ?? '';
1804 19
        parse_str($query, $url);
1805
1806
        return http_build_query(array_merge($url, [DocLister::AliasRequest => null]));
1807 19
    }
1808
}
1809