Test Failed
Branch master (5aadec)
by Agel_Nash
04:00
created

DocLister::getPK()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
dl 0
loc 11
c 2
b 2
f 0
rs 9.2
cc 4
eloc 6
nc 6
nop 1
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
include_once(MODX_BASE_PATH . 'assets/lib/APIHelpers.class.php');
1 ignored issue
show
Bug introduced by
The constant MODX_BASE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
9
include_once(MODX_BASE_PATH . 'assets/lib/Helpers/FS.php');
10
include_once(MODX_BASE_PATH . 'assets/lib/Helpers/Config.php');
11
require_once(dirname(dirname(__FILE__)) . "/lib/jsonHelper.class.php");
12
require_once(dirname(dirname(__FILE__)) . "/lib/sqlHelper.class.php");
13
require_once(dirname(dirname(__FILE__)) . "/lib/DLTemplate.class.php");
14
require_once(dirname(dirname(__FILE__)) . "/lib/DLCollection.class.php");
15
require_once(dirname(dirname(__FILE__)) . "/lib/xnop.class.php");
16
17
/**
18
 * Class DocLister
19
 */
20
abstract class DocLister
21
{
22
    /**
23
     * Ключ в массиве $_REQUEST в котором находится алиас запрашиваемого документа
24
     */
25
    const AliasRequest = 'q';
26
    /**
27
     * Массив документов полученный в результате выборки из базы
28
     * @var array
29
     * @access protected
30
     */
31
    protected $_docs = array();
32
33
    /**
34
     * Массив документов self::$_docs собранный в виде дерева
35
     * @var array
36
     * @access protected
37
     */
38
    protected $_tree = array();
39
40
    /**
41
     * @var
42
     * @access protected
43
     */
44
    protected $IDs = 0;
45
46
    /**
47
     * Объект DocumentParser - основной класс MODX'а
1 ignored issue
show
Bug introduced by
The type DocumentParser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
48
     * @var DocumentParser
49
     * @access protected
50
     */
51
    protected $modx = null;
52
53
    /**
54
     * Шаблонизатор чанков
55
     * @var DLTemplate
56
     * @access protected
57
     */
58
    protected $DLTemplate = null;
59
60
    /**
61
     * Массив загруженных экстендеров
62
     * @var array
63
     * @access protected
64
     */
65
    protected $extender = array();
66
67
    /**
68
     * Массив плейсхолдеров доступных в шаблоне
69
     * @var array
70
     * @access protected
71
     */
72
    protected $_plh = array();
73
74
    /**
75
     * Языковой пакет
76
     * @var array
77
     * @access protected
78
     */
79
    protected $_lang = array();
80
81
    /**
82
     * Пользовательский языковой пакет
83
     * @var array
84
     * @access protected
85
     */
86
    protected $_customLang = array();
87
88
    /**
89
     * Список таблиц уже с префиксами MODX
90
     * @var array
91
     * @access private
92
     */
93
    private $_table = array();
94
95
    /**
96
     * PrimaryKey основной таблицы
97
     * @var string
98
     * @access protected
99
     */
100
    protected $idField = 'id';
101
102
    /**
103
     * Parent Key основной таблицы
104
     * @var string
105
     * @access protected
106
     */
107
    protected $parentField = 'parent';
108
109
    /**
110
     * Дополнительные условия для SQL запросов
111
     * @var array
112
     * @access protected
113
     */
114
    protected $_filters = array('where' => '', 'join' => '');
115
116
    /**
117
     * Список доступных логических операторов для фильтрации
118
     * @var array
119
     * @access protected
120
     */
121
    protected $_logic_ops = array('AND' => ' AND ', 'OR' => ' OR '); // logic operators currently supported
122
123
    /**
124
     * Режим отладки
125
     * @var int
126
     * @access private
127
     */
128
    private $_debugMode = 0;
129
130
    /**
131
     * Отладчик
132
     *
133
     * @var DLdebug|xNop
134
     * @access public
135
     */
136
    public $debug = null;
137
138
    /**
139
     * Массив дополнительно подключаемых таблиц с псевдонимами
140
     * @var array
141
     */
142
    public $AddTable = array();
143
144
    /**
145
     * Время запуска сниппета
146
     * @var int
147
     */
148
    private $_timeStart = 0;
149
150
    /**
151
     * Номер фильтра в общем списке фильтров
152
     * @var int
153
     * @access protected
154
     */
155
    protected $totalFilters = 0;
156
157
    /** @var string имя шаблона для вывода записи */
158
    public $renderTPL = '';
159
160
    /** @var string имя шаблона обертки для записей */
161
    public $ownerTPL = '';
162
163
    public $FS = null;
164
    /** @var string результатирующая строка которая была последний раз сгенирирована
165
     *               вызовами методов DocLister::render и DocLister::getJSON
166
     */
167
    protected $outData = '';
168
169
    /** @var int Число документов, которые были отфильтрованы через prepare при выводе */
170
    public $skippedDocs = 0;
171
172
    /** @var string Имя таблицы */
173
    protected $table = '';
174
    /** @var string alias таблицы */
175
    protected $alias = '';
176
177
    /** @var null|paginate_DL_Extender */
178
    protected $extPaginate = null;
179
180
    /** @var null|Helpers\Config */
181
    public $config = null;
182
183
    /**
184
     * Конструктор контроллеров DocLister
185
     *
186
     * @param DocumentParser $modx объект DocumentParser - основной класс MODX
187
     * @param array $cfg массив параметров сниппета
188
     * @param int $startTime время запуска сниппета
189
     * @throws Exception
190
     */
191
    public function __construct($modx, $cfg = array(), $startTime = null)
192
    {
193
        $this->setTimeStart($startTime);
194
195
        if (extension_loaded('mbstring')) {
196
            mb_internal_encoding("UTF-8");
197
        } else {
198
            throw new Exception('Not found php extension mbstring');
199
        }
200
201
        if ($modx instanceof DocumentParser) {
202
            $this->modx = $modx;
203
            $this->setDebug(1);
204
205
            if (!is_array($cfg) || empty($cfg)) {
206
                $cfg = $this->modx->Event->params;
207
            }
208
        } else {
209
            throw new Exception('MODX var is not instaceof DocumentParser');
210
        }
211
212
        $this->FS = \Helpers\FS::getInstance();
213
        $this->config = new \Helpers\Config($cfg);
214
215
        if (isset($cfg['config'])) {
216
            $this->config->setPath(dirname(__DIR__))->loadConfig($cfg['config']);
217
        }
218
219
        if ($this->config->setConfig($cfg) === false) {
0 ignored issues
show
introduced by
The condition $this->config->setConfig($cfg) === false can never be true.
Loading history...
220
            throw new Exception('no parameters to run DocLister');
221
        }
222
223
        $this->loadLang(array('core', 'json'));
224
        $this->setDebug($this->getCFGDef('debug', 0));
225
226
        if ($this->checkDL()) {
227
            $cfg = array();
228
            $idType = $this->getCFGDef('idType', '');
229
            if (empty($idType) && $this->getCFGDef('documents', '') != '') {
230
                $idType = 'documents';
231
            }
232
            switch ($idType) {
233
                case 'documents':
234
                    $IDs = $this->getCFGDef('documents');
235
                    $cfg['idType'] = "documents";
236
                    break;
237
                case 'parents':
238
                default:
239
                    $cfg['idType'] = "parents";
240
                    if (($IDs = $this->getCFGDef('parents', '')) === '') {
241
                        $IDs = $this->getCurrentMODXPageID();
242
                    }
243
                    break;
244
            }
245
            $this->config->setConfig($cfg);
246
            $this->alias = empty($this->alias) ? $this->getCFGDef('tableAlias',
247
                'c') : $this->alias;
248
            $this->table = $this->getTable(empty($this->table) ? $this->getCFGDef('table',
249
                'site_content') : $this->table, $this->alias);
250
251
            $this->idField = $this->getCFGDef('idField', 'id');
252
            $this->parentField = $this->getCFGDef('parentField', 'parent');
253
254
            $this->setIDs($IDs);
255
        }
256
257
        $this->setLocate();
258
259
        if ($this->getCFGDef("customLang")) {
260
            $this->getCustomLang();
261
        }
262
        $this->loadExtender($this->getCFGDef("extender", ""));
263
264
        if ($this->checkExtender('request')) {
265
            $this->extender['request']->init($this, $this->getCFGDef("requestActive", ""));
266
        }
267
        $this->_filters = $this->getFilters($this->getCFGDef('filters', ''));
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getFilters($this-...tCFGDef('filters', '')) can also be of type false. However, the property $_filters is declared as type array. 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...
268
        $this->ownerTPL = $this->getCFGDef("ownerTPL", "");
269
        $DLTemplate = DLTemplate::getInstance($modx);
270
        if ($path = $this->getCFGDef('templatePath')) {
271
            $DLTemplate->setTemplatePath($path);
272
        }
273
        if ($ext = $this->getCFGDef('templateExtension')) {
274
            $DLTemplate->setTemplateExtension($ext);
275
        }
276
        $DLTemplate->setTwigTemplateVars(array('DocLister' => $this));
277
        $this->DLTemplate = $DLTemplate;
278
    }
279
280
    /**
281
     * Разбиение фильтра на субфильтры с учётом вложенности
282
     * @param string $str строка с фильтром
283
     * @return array массив субфильтров
284
     */
285
    public function smartSplit($str)
286
    {
287
        $res = array();
288
        $cur = '';
289
        $open = 0;
290
        $strlen = mb_strlen($str, 'UTF-8');
291
        for ($i = 0; $i <= $strlen; $i++) {
292
            $e = mb_substr($str, $i, 1, 'UTF-8');
293
            switch ($e) {
294
                case '\\':
295
                    $cur .= $e;
296
                    $cur .= mb_substr($str, ++$i, 1, 'UTF-8');
297
                    break;
298
                case ')':
299
                    $open--;
300
                    if ($open == 0) {
301
                        $res[] = $cur . ')';
302
                        $cur = '';
303
                    } else {
304
                        $cur .= $e;
305
                    }
306
                    break;
307
                case '(':
308
                    $open++;
309
                    $cur .= $e;
310
                    break;
311
                case ';':
312
                    if ($open == 0) {
313
                        $res[] = $cur;
314
                        $cur = '';
315
                    } else {
316
                        $cur .= $e;
317
                    }
318
                    break;
319
                default:
320
                    $cur .= $e;
321
            }
322
        }
323
        $cur = preg_replace("/(\))$/u", '', $cur);
324
        if ($cur != '') {
325
            $res[] = $cur;
326
        }
327
328
        return $res;
329
    }
330
331
    /**
332
     * Трансформация объекта в строку
333
     * @return string последний ответ от DocLister'а
334
     */
335
    public function __toString()
336
    {
337
        return $this->outData;
338
    }
339
340
    /**
341
     * Установить время запуска сниппета
342
     * @param float|null $time
343
     */
344
    public function setTimeStart($time = null)
345
    {
346
        $this->_timeStart = is_null($time) ? microtime(true) : $time;
347
    }
348
349
    /**
350
     * Время запуска сниппета
351
     *
352
     * @return int
353
     */
354
    public function getTimeStart()
355
    {
356
        return $this->_timeStart;
357
    }
358
359
    /**
360
     * Установка режима отладки
361
     * @param int $flag режим отладки
362
     */
363
    public function setDebug($flag = 0)
364
    {
365
        $flag = abs((int)$flag);
366
        if ($this->_debugMode != $flag) {
367
            $this->_debugMode = $flag;
368
            $this->debug = null;
369
            if ($this->_debugMode > 0) {
370
                if (isset($_SESSION['usertype']) && $_SESSION['usertype'] == 'manager') {
371
                    error_reporting(E_ALL ^ E_NOTICE);
372
                    ini_set('display_errors', 1);
373
                }
374
                $dir = dirname(dirname(__FILE__));
375
                if (file_exists($dir . "/lib/DLdebug.class.php")) {
376
                    include_once($dir . "/lib/DLdebug.class.php");
377
                    if (class_exists("DLdebug", false)) {
378
                        $this->debug = new DLdebug($this);
379
                    }
380
                }
381
            }
382
383
            if (is_null($this->debug)) {
384
                $this->debug = new xNop();
385
                $this->_debugMode = 0;
386
                error_reporting(0);
387
                ini_set('display_errors', 0);
388
            }
389
        }
390
    }
391
392
    /**
393
     * Информация о режиме отладки
394
     */
395
    public function getDebug()
396
    {
397
        return $this->_debugMode;
398
    }
399
400
    /**
401
     * Генерация имени таблицы с префиксом и алиасом
402
     *
403
     * @param string $name имя таблицы
404
     * @param string $alias желаемый алиас таблицы
405
     * @return string имя таблицы с префиксом и алиасом
406
     */
407
    public function getTable($name, $alias = '')
408
    {
409
        if (!isset($this->_table[$name])) {
410
            $this->_table[$name] = $this->modx->getFullTableName($name);
411
        }
412
        $table = $this->_table[$name];
413
        if (!empty($alias) && is_scalar($alias)) {
414
            $table .= " as `" . $alias . "`";
415
        }
416
417
        return $table;
418
    }
419
420
    /**
421
     * @param $name
422
     * @param $table
423
     * @param $alias
424
     * @return mixed
425
     */
426
    public function TableAlias($name, $table, $alias)
427
    {
428
        if (!$this->checkTableAlias($name, $table)) {
429
            $this->AddTable[$table][$name] = $alias;
430
        }
431
432
        return $this->AddTable[$table][$name];
433
    }
434
435
    /**
436
     * @param $name
437
     * @param $table
438
     * @return bool
439
     */
440
    public function checkTableAlias($name, $table)
441
    {
442
        return isset($this->AddTable[$table][$name]);
443
    }
444
445
    /**
446
     * Разбор JSON строки при помощи json_decode
447
     *
448
     * @param $json string строка c JSON
449
     * @param array $config ассоциативный массив с настройками для json_decode
450
     * @param bool $nop создавать ли пустой объект запрашиваемого типа
451
     * @return array|mixed|xNop
452
     */
453
    public function jsonDecode($json, $config = array(), $nop = false)
454
    {
455
        $this->debug->debug('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

455
        $this->debug->debug('Decode JSON: ' . $this->debug->/** @scrutinizer ignore-call */ dumpData($json) . "\r\nwith config: " . $this->debug->dumpData($config),
Loading history...
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

455
        $this->debug->/** @scrutinizer ignore-call */ 
456
                      debug('Decode JSON: ' . $this->debug->dumpData($json) . "\r\nwith config: " . $this->debug->dumpData($config),
Loading history...
456
            'jsonDecode', 2);
457
        $config = jsonHelper::jsonDecode($json, $config, $nop);
458
        $this->isErrorJSON($json);
459
        $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

459
        $this->debug->/** @scrutinizer ignore-call */ 
460
                      debugEnd("jsonDecode");
Loading history...
460
461
        return $config;
462
    }
463
464
    /**
465
     * Были ли ошибки во время работы с JSON
466
     *
467
     * @param $json string строка с JSON для записи в лог при отладке
468
     * @return bool|string
469
     */
470
    public function isErrorJSON($json)
471
    {
472
        $error = jsonHelper::json_last_error_msg();
473
        if (!in_array($error, array('error_none', 'other'))) {
474
            $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

474
            $this->debug->/** @scrutinizer ignore-call */ 
475
                          error($this->getMsg('json.' . $error) . ": " . $this->debug->dumpData($json, 'code'), 'JSON');
Loading history...
475
            $error = true;
476
        }
477
478
        return $error;
479
    }
480
481
    /**
482
     * Проверка параметров и загрузка необходимых экстендеров
483
     * return boolean статус загрузки
484
     */
485
    public function checkDL()
486
    {
487
        $this->debug->debug('Check DocLister parameters', 'checkDL', 2);
488
        $flag = true;
489
        $extenders = $this->getCFGDef('extender', '');
490
        $extenders = explode(",", $extenders);
491
        $tmp = $this->getCFGDef('requestActive', '') != '' || in_array('request', $extenders);
492
        if ($tmp && !$this->_loadExtender('request')) {
493
            //OR request in extender's parameter
494
            throw new Exception('Error load request extender');
495
        }
496
497
        $tmp = $this->getCFGDef('summary', '') != '' || in_array('summary', $extenders);
498
        if ($tmp && !$this->_loadExtender('summary')) {
499
            //OR summary in extender's parameter
500
            throw new Exception('Error load summary extender');
501
        }
502
503
        if (
504
            (int)$this->getCFGDef('display', 0) > 0 && ( //OR paginate in extender's parameter
505
                in_array('paginate', $extenders) || $this->getCFGDef('paginate', '') != '' ||
506
                $this->getCFGDef('TplPrevP', '') != '' || $this->getCFGDef('TplPage', '') != '' ||
507
                $this->getCFGDef('TplCurrentPage', '') != '' || $this->getCFGDef('TplWrapPaginate', '') != '' ||
508
                $this->getCFGDef('pageLimit', '') != '' || $this->getCFGDef('pageAdjacents', '') != '' ||
509
                $this->getCFGDef('PaginateClass', '') != '' || $this->getCFGDef('TplNextP', '') != ''
510
            ) && !$this->_loadExtender('paginate')
511
        ) {
512
            throw new Exception('Error load paginate extender');
513
        } else {
514
            if ((int)$this->getCFGDef('display', 0) == 0) {
515
                $extenders = $this->unsetArrayVal($extenders, 'paginate');
516
            }
517
        }
518
519
        if ($this->getCFGDef('prepare', '') != '' || $this->getCFGDef('prepareWrap') != '') {
520
            $this->_loadExtender('prepare');
521
        }
522
523
        $this->config->setConfig(array('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

523
        $this->config->/** @scrutinizer ignore-call */ 
524
                       setConfig(array('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...
524
        $this->debug->debugEnd("checkDL");
525
526
        return $flag;
527
    }
528
529
    /**
530
     * Удаление определенных данных из массива
531
     *
532
     * @param array $data массив с данными
533
     * @param mixed $val значение которые необходимо удалить из массива
534
     * @return array отчищеный массив с данными
535
     */
536
    private function unsetArrayVal($data, $val)
537
    {
538
        $out = array();
539
        if (is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) can never be false.
Loading history...
540
            foreach ($data as $item) {
541
                if ($item != $val) {
542
                    $out[] = $item;
543
                } else {
544
                    continue;
545
                }
546
            }
547
        }
548
549
        return $out;
550
    }
551
552
    /**
553
     * Генерация URL страницы
554
     *
555
     * @param int $id уникальный идентификатор страницы
556
     * @return string URL страницы
557
     */
558
    public function getUrl($id = 0)
559
    {
560
        $id = ((int)$id > 0) ? (int)$id : $this->getCurrentMODXPageID();
561
562
        $link = $this->checkExtender('request') ? $this->extender['request']->getLink() : $this->getRequest();
563
        if ($id == $this->modx->config['site_start']) {
564
            $url = $this->modx->config['site_url'] . ($link != '' ? "?{$link}" : "");
565
        } else {
566
            $url = $this->modx->makeUrl($id, '', $link, $this->getCFGDef('urlScheme', ''));
567
        }
568
569
        return $url;
570
    }
571
572
    /**
573
     * Получение массива документов из базы
574
     * @param mixed $tvlist дополнительные параметры выборки
575
     * @return array Массив документов выбранных из базы
576
     */
577
    abstract public function getDocs($tvlist = '');
578
579
    /**
580
     * Подготовка результатов к отображению.
581
     *
582
     * @param string $tpl шаблон
583
     * @return mixed подготовленный к отображению результат выборки
584
     */
585
    abstract public function _render($tpl = '');
586
587
    /**
588
     * Подготовка результатов к отображению в соответствии с настройками
589
     *
590
     * @param string $tpl шаблон
591
     * @return string
592
     */
593
    public function render($tpl = '')
594
    {
595
        $this->debug->debug(array('Render data with template ' => $tpl), 'render', 2, array('html'));
596
        $out = '';
597
        if (1 == $this->getCFGDef('tree', '0')) {
598
            foreach ($this->_tree as $item) {
599
                $out .= $this->renderTree($item);
600
            }
601
            $out = $this->renderWrap($out);
602
        } else {
603
            $out = $this->_render($tpl);
604
        }
605
606
        if ($out) {
607
            $this->outData = DLTemplate::getInstance($this->modx)->parseDocumentSource($out);
608
        }
609
        $this->debug->debugEnd('render');
610
611
        return $this->outData;
612
    }
613
614
    /***************************************************
615
     ****************** CORE Block *********************
616
     ***************************************************/
617
618
    /**
619
     * Определение ID страницы открытой во фронте
620
     *
621
     * @return int
622
     */
623
    public function getCurrentMODXPageID()
624
    {
625
        $id = isset($this->modx->documentIdentifier) ? (int)$this->modx->documentIdentifier : 0;
626
        $docData = isset($this->modx->documentObject) ? $this->modx->documentObject : array();
627
628
        return empty($id) ? \APIHelpers::getkey($docData, 'id', 0) : $id;
629
    }
630
631
    /**
632
     * Display and save error information
633
     *
634
     * @param string $message error message
635
     * @param integer $code error number
636
     * @param string $file error on file
637
     * @param integer $line error on line
638
     * @param array $trace stack trace
639
     */
640
    public function ErrorLogger($message, $code, $file, $line, $trace)
641
    {
642
        if (abs($this->getCFGDef('debug', '0')) == '1') {
643
            $out = "CODE #" . $code . "<br />";
644
            $out .= "on file: " . $file . ":" . $line . "<br />";
645
            $out .= "<pre>";
646
            $out .= print_r($trace, 1);
647
            $out .= "</pre>";
648
649
            $message = $out . $message;
650
        }
651
        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...
652
    }
653
654
    /**
655
     * Получение объекта DocumentParser
656
     *
657
     * @return DocumentParser
658
     */
659
    public function getMODX()
660
    {
661
        return $this->modx;
662
    }
663
664
    /**
665
     * load extenders
666
     *
667
     * @param string $ext name extender separated by ,
668
     * @return boolean status load extenders
669
     * @throws Exception
670
     */
671
    public function loadExtender($ext = '')
672
    {
673
        $out = true;
674
        if ($ext != '') {
675
            $ext = explode(",", $ext);
676
            foreach ($ext as $item) {
677
                if ($item != '' && !$this->_loadExtender($item)) {
678
                    throw new Exception('Error load ' . APIHelpers::e($item) . ' extender');
679
                }
680
            }
681
        }
682
683
        return $out;
684
    }
685
686
    /**
687
     * Получение информации из конфига
688
     *
689
     * @param string $name имя параметра в конфиге
690
     * @param mixed $def значение по умолчанию, если в конфиге нет искомого параметра
691
     * @return mixed значение из конфига
692
     */
693
    public function getCFGDef($name, $def = null)
694
    {
695
        return $this->config->getCFGDef($name, $def);
696
    }
697
698
    /**
699
     * Сохранение данных в массив плейсхолдеров
700
     *
701
     * @param mixed $data данные
702
     * @param int $set устанавливать ли глобальнй плейсхолдер MODX
703
     * @param string $key ключ локального плейсхолдера
704
     * @return string
705
     */
706
    public function toPlaceholders($data, $set = 0, $key = 'contentPlaceholder')
707
    {
708
        $this->debug->debug(null, 'toPlaceholders', 2);
709
        if ($set == 0) {
710
            $set = $this->getCFGDef('contentPlaceholder', 0);
711
        }
712
        $this->_plh[$key] = $data;
713
        $id = $this->getCFGDef('id', '');
714
        if ($id != '') {
715
            $id .= ".";
716
        }
717
        $out = DLTemplate::getInstance($this->getMODX())->toPlaceholders($data, $set, $key, $id);
718
719
        $this->debug->debugEnd(
720
            "toPlaceholders", array($key . " placeholder" => $data), array('html')
721
        );
722
723
        return $out;
724
    }
725
726
    /**
727
     * Предварительная обработка данных перед вставкой в SQL запрос вида IN
728
     * Если данные в виде строки, то происходит попытка сформировать массив из этой строки по разделителю $sep
729
     * Точно по тому, по которому потом данные будут собраны обратно
730
     *
731
     * @param integer|string|array $data данные для обработки
732
     * @param string $sep разделитель
733
     * @param boolean $quote заключать ли данные на выходе в кавычки
734
     * @return string обработанная строка
735
     */
736
    public function sanitarIn($data, $sep = ',', $quote = true)
737
    {
738
        if(is_scalar($data)) $data = explode($sep, $data);
739
        if(!is_array($data)) $data = array(); //@TODO: throw
0 ignored issues
show
introduced by
The condition ! is_array($data) can never be true.
Loading history...
740
741
        $out = array();
742
        foreach ($data as $item) {
743
            if ($item !== '') {
744
                $out[] = $this->modx->db->escape($item);
745
            }
746
        }
747
        $q = $quote ? "'" : "";
748
        $out = $q . implode($q . "," . $q, $out) . $q;
749
750
        return $out;
751
    }
752
753
    /**
754
     * Загрузка кастомного лексикона
755
     *
756
     * В файле с кастомным лексиконом ключи в массиве дожны быть полные
757
     * Например:
758
     *      - core.bla-bla
759
     *      - paginate.next
760
     *
761
     * @param string $lang имя языкового пакета
762
     * @return array
763
     */
764
    public function getCustomLang($lang = '')
765
    {
766
        if (empty($lang)) {
767
            $lang = $this->getCFGDef('lang', $this->modx->config['manager_language']);
768
        }
769
        if (file_exists(dirname(dirname(__FILE__)) . "/lang/" . $lang . ".php")) {
770
            $tmp = include(dirname(__FILE__) . "/lang/" . $lang . ".php");
771
            $this->_customLang = is_array($tmp) ? $tmp : array();
772
        }
773
774
        return $this->_customLang;
775
    }
776
777
    /**
778
     * Загрузка языкового пакета
779
     *
780
     * @param array|string $name ключ языкового пакета
781
     * @param string $lang имя языкового пакета
782
     * @param boolean $rename Переименовывать ли элементы массива
783
     * @return array массив с лексиконом
784
     */
785
    public function loadLang($name = 'core', $lang = '', $rename = true)
786
    {
787
        if (empty($lang)) {
788
            $lang = $this->getCFGDef('lang', $this->modx->config['manager_language']);
789
        }
790
791
        $this->debug->debug('Load language ' . $this->debug->dumpData($name) . "." . $this->debug->dumpData($lang),
792
            'loadlang', 2);
793
        if (is_scalar($name)) {
794
            $name = array($name);
795
        }
796
        foreach ($name as $n) {
797
            if (file_exists(dirname(__FILE__) . "/lang/" . $lang . "/" . $n . ".inc.php")) {
798
                $tmp = include(dirname(__FILE__) . "/lang/" . $lang . "/" . $n . ".inc.php");
799
                if (is_array($tmp)) {
800
                    /**
801
                     * Переименовыываем элементы массива из array('test'=>'data') в array('name.test'=>'data')
802
                     */
803
                    if ($rename) {
804
                        $tmp = $this->renameKeyArr($tmp, $n, '', '.');
805
                    }
806
                    $this->_lang = array_merge($this->_lang, $tmp);
807
                }
808
            }
809
        }
810
        $this->debug->debugEnd("loadlang");
811
812
        return $this->_lang;
813
    }
814
815
    /**
816
     * Получение строки из языкового пакета
817
     *
818
     * @param string $name имя записи в языковом пакете
819
     * @param string $def Строка по умолчанию, если запись в языковом пакете не будет обнаружена
820
     * @return string строка в соответствии с текущими языковыми настройками
821
     */
822
    public function getMsg($name, $def = '')
823
    {
824
        if (isset($this->_customLang[$name])) {
825
            $say = $this->_customLang[$name];
826
        } else {
827
            $say = \APIHelpers::getkey($this->_lang, $name, $def);
828
        }
829
830
        return $say;
831
    }
832
833
    /**
834
     * Переменовывание элементов массива
835
     *
836
     * @param array $data массив с данными
837
     * @param string $prefix префикс ключей
838
     * @param string $suffix суффикс ключей
839
     * @param string $sep разделитель суффиксов, префиксов и ключей массива
840
     * @return array массив с переименованными ключами
841
     */
842
    public function renameKeyArr($data, $prefix = '', $suffix = '', $sep = '.')
843
    {
844
        return \APIHelpers::renameKeyArr($data, $prefix, $suffix, $sep);
845
    }
846
847
    /**
848
     * Установка локали
849
     *
850
     * @param string $locale локаль
851
     * @return string имя установленной локали
852
     */
853
    public function setLocate($locale = '')
854
    {
855
        if ('' == $locale) {
856
            $locale = $this->getCFGDef('locale', '');
857
        }
858
        if ('' != $locale) {
859
            setlocale(LC_ALL, $locale);
860
        }
861
862
        return $locale;
863
    }
864
865
    /**
866
     * Шаблонизация дерева.
867
     * Перевод из массива в HTML в соответствии с указанным шаблоном
868
     *
869
     * @param array $data массив сформированный как дерево
870
     * @return string строка для отображения пользователю
871
     */
872
    protected function renderTree($data)
873
    {
874
        $out = '';
875
        if (!empty($data['#childNodes'])) {
876
            foreach ($data['#childNodes'] as $item) {
877
                $out .= $this->renderTree($item);
878
            }
879
        }
880
881
        $data[$this->getCFGDef("sysKey", "dl") . ".wrap"] = $this->renderWrap($out);
882
        $out = $this->parseChunk($this->getCFGDef('tpl', ''), $data);
883
884
        return $out;
885
    }
886
887
    /**
888
     * refactor $modx->getChunk();
889
     *
890
     * @param string $name Template: chunk name || @CODE: template || @FILE: file with template
891
     * @return string html template with placeholders without data
892
     */
893
    private function _getChunk($name)
894
    {
895
        $this->debug->debug(array('Get chunk by name' => $name), "getChunk", 2, array('html'));
896
        //without trim
897
        $tpl = DLTemplate::getInstance($this->getMODX())->getChunk($name);
898
        $tpl = $this->parseLang($tpl);
899
900
        $this->debug->debugEnd("getChunk");
901
902
        return $tpl;
903
    }
904
905
    /**
906
     * Замена в шаблоне фраз из лексикона
907
     *
908
     * @param string $tpl HTML шаблон
909
     * @return string
910
     */
911
    public function parseLang($tpl)
912
    {
913
        $this->debug->debug(array("parseLang" => $tpl), "parseLang", 2, array('html'));
914
        if (is_scalar($tpl) && !empty($tpl)) {
915
            if (preg_match_all("/\[\%([a-zA-Z0-9\.\_\-]+)\%\]/", $tpl, $match)) {
916
                $langVal = array();
917
                foreach ($match[1] as $item) {
918
                    $langVal[] = $this->getMsg($item);
919
                }
920
                $tpl = str_replace($match[0], $langVal, $tpl);
921
            }
922
        } else {
923
            $tpl = '';
924
        }
925
        $this->debug->debugEnd("parseLang");
926
927
        return $tpl;
928
    }
929
930
    /**
931
     * refactor $modx->parseChunk();
932
     *
933
     * @param string $name Template: chunk name || @CODE: template || @FILE: file with template
934
     * @param array $data paceholder
935
     * @param bool $parseDocumentSource render html template via DocumentParser::parseDocumentSource()
936
     * @return string html template with data without placeholders
937
     */
938
    public function parseChunk($name, $data = array(), $parseDocumentSource = false)
939
    {
940
        $this->debug->debug(
941
            array("parseChunk" => $name, "With data" => print_r($data, 1)),
942
            "parseChunk",
943
            2, array('html', null)
944
        );
945
946
        $out = $this->DLTemplate->parseChunk($name, $data, $parseDocumentSource);
947
        $out = $this->parseLang($out);
948
        if (empty($out)) {
949
            $this->debug->debug("Empty chunk: " . $this->debug->dumpData($name), '', 2);
950
        }
951
        $this->debug->debugEnd("parseChunk");
952
953
        return $out;
954
    }
955
956
    /**
957
     * Get full template from parameter name
958
     *
959
     * @param string $name param name
960
     * @param string $val default value
961
     *
962
     * @return string html template from parameter
963
     */
964
    public function getChunkByParam($name, $val = '')
965
    {
966
        $data = $this->getCFGDef($name, $val);
967
        $data = $this->_getChunk($data);
968
969
        return $data;
970
    }
971
972
    /**
973
     * Помещение html кода в какой-то блок обертку
974
     *
975
     * @param string $data html код который нужно обернуть в ownerTPL
976
     * @return string результатирующий html код
977
     */
978
    public function renderWrap($data)
979
    {
980
        $out = $data;
981
        $docs = count($this->_docs) - $this->skippedDocs;
982
        if ((($this->getCFGDef("noneWrapOuter", "1") && $docs == 0) || $docs > 0) && !empty($this->ownerTPL)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: {currentAssign}, Probably Intended Meaning: {alternativeAssign}
Loading history...
983
            $this->debug->debug("", "renderWrapTPL", 2);
984
            $parse = true;
985
            $plh = array($this->getCFGDef("sysKey", "dl") . ".wrap" => $data);
986
            /**
987
             * @var $extPrepare prepare_DL_Extender
988
             */
989
            $extPrepare = $this->getExtender('prepare');
990
            if ($extPrepare) {
0 ignored issues
show
introduced by
The condition $extPrepare can never be true.
Loading history...
991
                $params = $extPrepare->init($this, array(
992
                    'data'      => array(
993
                        'docs'         => $this->_docs,
994
                        'placeholders' => $plh
995
                    ),
996
                    'nameParam' => 'prepareWrap',
997
                    'return'    => 'placeholders'
998
                ));
999
                if (is_bool($params) && $params === false) {
1000
                    $out = $data;
1001
                    $parse = false;
1002
                }
1003
                $plh = $params;
1004
            }
1005
            if ($parse && !empty($this->ownerTPL)) {
1006
                $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

1006
                $this->debug->/** @scrutinizer ignore-call */ 
1007
                              updateMessage(
Loading history...
1007
                    array("render ownerTPL" => $this->ownerTPL, "With data" => print_r($plh, 1)),
1008
                    "renderWrapTPL",
1009
                    array('html', null)
1010
                );
1011
                $out = $this->parseChunk($this->ownerTPL, $plh);
1012
            }
1013
            if (empty($this->ownerTPL)) {
1014
                $this->debug->updateMessage("empty ownerTPL", "renderWrapTPL");
1015
            }
1016
            $this->debug->debugEnd("renderWrapTPL");
1017
        }
1018
1019
        return $out;
1020
    }
1021
1022
    /**
1023
     * Единые обработки массива с данными о документе для всех контроллеров
1024
     *
1025
     * @param array $data массив с данными о текущем документе
1026
     * @param int $i номер итерации в цикле
1027
     * @return array массив с данными которые можно использовать в цикле render метода
1028
     */
1029
    protected function uniformPrepare(&$data, $i = 0)
1030
    {
1031
        $class = array();
1032
1033
        $iterationName = ($i % 2 == 1) ? 'Odd' : 'Even';
1034
        $tmp = strtolower($iterationName);
1035
        $class[] = $this->getCFGDef($tmp . 'Class', $tmp);
1036
1037
        $this->renderTPL = $this->getCFGDef('tplId' . $i, $this->renderTPL);
1038
        $this->renderTPL = $this->getCFGDef('tpl' . $iterationName, $this->renderTPL);
1039
        $iteration = $i;
1040
1041
        if ($this->extPaginate) {
1042
            $offset = $this->getCFGDef('reversePagination',
1043
                0) && $this->extPaginate->currentPage() > 1 ? $this->extPaginate->totalPage() * $this->getCFGDef('display',
1044
                    0) - $this->extPaginate->totalDocs() : 0;
1045
            if ($this->getCFGDef('maxDocs', 0) && !$this->getCFGDef('reversePagination',
1046
                    0) && $this->extPaginate->currentPage() == $this->extPaginate->totalPage()
1047
            ) {
1048
                $iteration += $this->getCFGDef('display', 0);
1049
            }
1050
            $iteration += $this->getCFGDef('display',
1051
                    0) * ($this->extPaginate->currentPage() - 1) - $offset;
1052
        }
1053
1054
        $data[$this->getCFGDef("sysKey",
1055
            "dl") . '.full_iteration'] = $iteration;
1056
1057
        if ($i == 1) {
1058
            $this->renderTPL = $this->getCFGDef('tplFirst', $this->renderTPL);
1059
            $class[] = $this->getCFGDef('firstClass', 'first');
1060
        }
1061
        if ($i == (count($this->_docs) - $this->skippedDocs)) {
1062
            $this->renderTPL = $this->getCFGDef('tplLast', $this->renderTPL);
1063
            $class[] = $this->getCFGDef('lastClass', 'last');
1064
        }
1065
        if ($this->modx->documentIdentifier == $data['id']) {
1066
            $this->renderTPL = $this->getCFGDef('tplCurrent', $this->renderTPL);
1067
            $data[$this->getCFGDef("sysKey",
1068
                "dl") . '.active'] = 1; //[+active+] - 1 if $modx->documentIdentifer equal ID this element
1069
            $class[] = $this->getCFGDef('currentClass', 'current');
1070
        } else {
1071
            $data[$this->getCFGDef("sysKey", "dl") . '.active'] = 0;
1072
        }
1073
1074
        $class = implode(" ", $class);
1075
        $data[$this->getCFGDef("sysKey", "dl") . '.class'] = $class;
1076
1077
        /**
1078
         * @var $extE e_DL_Extender
1079
         */
1080
        $extE = $this->getExtender('e', true, true);
1081
        if ($out = $extE->init($this, compact('data'))) {
1082
            if (is_array($out)) {
1083
                $data = $out;
1084
            }
1085
        }
1086
1087
        return compact('class', 'iterationName');
1088
    }
1089
1090
    /**
1091
     * Формирование JSON ответа
1092
     *
1093
     * @param array $data массив данных которые подготавливаются к выводу в JSON
1094
     * @param mixed $fields список полей учавствующих в JSON ответе. может быть либо массив, либо строка с разделителем , (запятая)
1095
     * @param array $array данные которые необходимо примешать к ответу на каждой записи $data
1096
     * @return string JSON строка
1097
     */
1098
    public function getJSON($data, $fields, $array = array())
1099
    {
1100
        $out = array();
1101
        $fields = is_array($fields) ? $fields : explode(",", $fields);
1102
        if (is_array($array) && count($array) > 0) {
1103
            $tmp = array();
1104
            foreach ($data as $i => $v) { //array_merge not valid work with integer index key
1105
                $tmp[$i] = (isset($array[$i]) ? array_merge($v, $array[$i]) : $v);
1106
            }
1107
            $data = $tmp;
1108
        }
1109
1110
        foreach ($data as $num => $doc) {
1111
            $tmp = array();
1112
            foreach ($doc as $name => $value) {
1113
                if (in_array($name, $fields) || (isset($fields[0]) && $fields[0] == '1')) {
1114
                    $tmp[str_replace(".", "_", $name)] = $value; //JSON element name without dot
1115
                }
1116
            }
1117
            $out[$num] = $tmp;
1118
        }
1119
1120
        if ('new' == $this->getCFGDef('JSONformat', 'old')) {
1121
            $return = array();
1122
1123
            $return['rows'] = array();
1124
            foreach ($out as $key => $item) {
1125
                $return['rows'][] = $item;
1126
            }
1127
            $return['total'] = $this->getChildrenCount();
1128
        } elseif ('simple' == $this->getCFGDef('JSONformat', 'old')) {
1129
            $return = array();
1130
            foreach ($out as $key => $item) {
1131
                $return[] = $item;
1132
            }
1133
        } else {
1134
            $return = $out;
1135
        }
1136
        $this->outData = json_encode($return);
1137
        $this->isErrorJSON($return);
1138
1139
        return jsonHelper::json_format($this->outData);
1140
    }
1141
1142
    /**
1143
     * @param array $item
1144
     * @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...
1145
     * @param string $introField
1146
     * @param string $contentField
1147
     * @return mixed|string
1148
     */
1149
    protected function getSummary(array $item = array(), $extSummary = null, $introField = '', $contentField = '')
1150
    {
1151
        $out = '';
1152
1153
        if (is_null($extSummary)) {
1154
            /**
1155
             * @var $extSummary summary_DL_Extender
1156
             */
1157
            $extSummary = $this->getExtender('summary');
1158
        }
1159
        $introField = $this->getCFGDef("introField", $introField);
1160
        $contentField = $this->getCFGDef("contentField", $contentField);
1161
1162
        if (!empty($introField) && !empty($item[$introField]) && mb_strlen($item[$introField], 'UTF-8') > 0) {
1163
            $out = $item[$introField];
1164
        } else {
1165
            if (!empty($contentField) && !empty($item[$contentField]) && mb_strlen($item[$contentField], 'UTF-8') > 0) {
1166
                $out = $extSummary->init($this, array(
1167
                    "content"      => $item[$contentField],
1168
                    "action"       => $this->getCFGDef("summary", ""),
1169
                    "cutSummary"   => $this->getCFGDef('cutSummary'),
1170
                    "dotSummary"   => $this->getCFGDef('dotSummary'),
1171
                    'breakSummary' => $this->getCFGDef('breakSummary')
1172
                ));
1173
            }
1174
        }
1175
1176
        return $out;
1177
    }
1178
1179
    /**
1180
     * @param string $name extender name
1181
     * @return boolean status extender load
1182
     */
1183
    public function checkExtender($name)
1184
    {
1185
        return (isset($this->extender[$name]) && $this->extender[$name] instanceof $name . "_DL_Extender");
1186
    }
1187
1188
    /**
1189
     * @param $name
1190
     * @param $obj
1191
     */
1192
    public function setExtender($name, $obj)
1193
    {
1194
        $this->extender[$name] = $obj;
1195
    }
1196
1197
    /**
1198
     * Вытащить экземпляр класса экстендера из общего массива экстендеров
1199
     *
1200
     * @param string $name имя экстендера
1201
     * @param bool $autoload Если экстендер не загружен, то пытаться ли его загрузить
1202
     * @param bool $nop если экстендер не загружен, то загружать ли xNop
1203
     * @return null|xNop
1204
     */
1205
    public function getExtender($name, $autoload = false, $nop = false)
1206
    {
1207
        $out = null;
1208
        if ((is_scalar($name) && $this->checkExtender($name)) || ($autoload && $this->_loadExtender($name))) {
1209
            $out = $this->extender[$name];
1210
        }
1211
        if ($nop && is_null($out)) {
1212
            $out = new xNop();
1213
        }
1214
1215
        return $out;
1216
    }
1217
1218
    /**
1219
     * load extender
1220
     *
1221
     * @param string $name name extender
1222
     * @return boolean $flag status load extender
1223
     */
1224
    protected function _loadExtender($name)
1225
    {
1226
        $this->debug->debug('Load Extender ' . $this->debug->dumpData($name), 'LoadExtender', 2);
1227
        $flag = false;
1228
1229
        $classname = ($name != '') ? $name . "_DL_Extender" : "";
1230
        if ($classname != '' && isset($this->extender[$name]) && $this->extender[$name] instanceof $classname) {
1231
            $flag = true;
1232
1233
        } else {
1234
            if (!class_exists($classname, false) && $classname != '') {
1235
                if (file_exists(dirname(__FILE__) . "/extender/" . $name . ".extender.inc")) {
1236
                    include_once(dirname(__FILE__) . "/extender/" . $name . ".extender.inc");
1237
                }
1238
            }
1239
            if (class_exists($classname, false) && $classname != '') {
1240
                $this->extender[$name] = new $classname($this, $name);
1241
                $flag = true;
1242
            }
1243
        }
1244
        if (!$flag) {
1245
            $this->debug->debug("Error load Extender " . $this->debug->dumpData($name));
1246
        }
1247
        $this->debug->debugEnd('LoadExtender');
1248
1249
        return $flag;
1250
    }
1251
1252
    /*************************************************
1253
     ****************** IDs BLOCK ********************
1254
     ************************************************/
1255
1256
    /**
1257
     * Очистка массива $IDs по которому потом будет производиться выборка документов
1258
     *
1259
     * @param mixed $IDs список id документов по которым необходима выборка
1260
     * @return array очищенный массив
1261
     */
1262
    public function setIDs($IDs)
1263
    {
1264
        $this->debug->debug('set ID list ' . $this->debug->dumpData($IDs), 'setIDs', 2);
1265
        $IDs = $this->cleanIDs($IDs);
1266
        $type = $this->getCFGDef('idType', 'parents');
1267
        $depth = $this->getCFGDef('depth', '');
1268
        if ($type == 'parents' && $depth > 0) {
1269
            $tmp = $IDs;
1270
            do {
1271
                if (count($tmp) > 0) {
1272
                    $tmp = $this->getChildrenFolder($tmp);
1273
                    $IDs = array_merge($IDs, $tmp);
1274
                }
1275
            } while ((--$depth) > 0);
1276
        }
1277
        $this->debug->debugEnd("setIDs");
1278
1279
        return ($this->IDs = $IDs);
1280
    }
1281
1282
    /**
1283
     * @return int
1284
     */
1285
    public function getIDs()
1286
    {
1287
        return $this->IDs;
1288
    }
1289
1290
    /**
1291
     * Очистка данных и уникализация списка цифр.
1292
     * Если был $IDs был передан как строка, то эта строка будет преобразована в массив по разделителю $sep
1293
     * @param mixed $IDs данные для обработки
1294
     * @param string $sep разделитель
1295
     * @return array очищенный массив с данными
1296
     */
1297
    public function cleanIDs($IDs, $sep = ',')
1298
    {
1299
        $this->debug->debug('clean IDs ' . $this->debug->dumpData($IDs) . ' with separator ' . $this->debug->dumpData($sep),
1300
            'cleanIDs', 2);
1301
        $out = array();
1302
        if (!is_array($IDs)) {
1303
            $IDs = explode($sep, $IDs);
1304
        }
1305
        foreach ($IDs as $item) {
1306
            $item = trim($item);
1307
            if (is_numeric($item) && (int)$item >= 0) { //Fix 0xfffffffff
1308
                $out[] = (int)$item;
1309
            }
1310
        }
1311
        $out = array_unique($out);
1312
        $this->debug->debugEnd("cleanIDs");
1313
1314
        return $out;
1315
    }
1316
1317
    /**
1318
     * Проверка массива с id-шниками документов для выборки
1319
     * @return boolean пригодны ли данные для дальнейшего использования
1320
     */
1321
    protected function checkIDs()
1322
    {
1323
        return (is_array($this->IDs) && count($this->IDs) > 0) ? true : false;
0 ignored issues
show
introduced by
The condition is_array($this->IDs) && count($this->IDs) > 0 can never be true.
Loading history...
1324
    }
1325
1326
    /**
1327
     * Get all field values from array documents
1328
     *
1329
     * @param string $userField field name
1330
     * @param boolean $uniq Only unique values
1331
     * @global array $_docs all documents
1332
     * @return array all field values
1333
     */
1334
    public function getOneField($userField, $uniq = false)
1335
    {
1336
        $out = array();
1337
        foreach ($this->_docs as $doc => $val) {
1338
            if (isset($val[$userField]) && (($uniq && !in_array($val[$userField], $out)) || !$uniq)) {
1339
                $out[$doc] = $val[$userField];
1340
            }
1341
        }
1342
1343
        return $out;
1344
    }
1345
1346
    /**
1347
     * @return DLCollection
1348
     */
1349
    public function docsCollection()
1350
    {
1351
        return new DLCollection($this->modx, $this->_docs);
1352
    }
1353
1354
    /**********************************************************
1355
     ********************** SQL BLOCK *************************
1356
     *********************************************************/
1357
1358
    /**
1359
     * Подсчет документов удовлетворящиюх выборке
1360
     *
1361
     * @return int Число дочерних документов
1362
     */
1363
    abstract public function getChildrenCount();
1364
1365
    /**
1366
     * Выборка документов которые являются дочерними относительно $id документа и в тоже время
1367
     * являются родителями для каких-нибудь других документов
1368
     *
1369
     * @param string|array $id значение PrimaryKey родителя
1370
     * @return array массив документов
1371
     */
1372
    abstract public function getChildrenFolder($id);
1373
1374
    /**
1375
     * @param string $group
1376
     * @return string
1377
     */
1378
    protected function getGroupSQL($group = '')
1379
    {
1380
        $out = '';
1381
        if ($group != '') {
1382
            $out = 'GROUP BY ' . $group;
1383
        }
1384
1385
        return $out;
1386
    }
1387
1388
    /**
1389
     *    Sorting method in SQL queries
1390
     *
1391
     * @global string $order
1392
     * @global string $orderBy
1393
     * @global string $sortBy
1394
     *
1395
     * @param string $sortName default sort field
1396
     * @param string $orderDef default order (ASC|DESC)
1397
     *
1398
     * @return string Order by for SQL
1399
     */
1400
    protected function SortOrderSQL($sortName, $orderDef = 'DESC')
1401
    {
1402
        $this->debug->debug('', 'sortORDER', 2);
1403
1404
        $sort = '';
1405
        switch ($this->getCFGDef('sortType', '')) {
1406
            case 'none':
1407
                break;
1408
            case 'doclist':
1409
                $idList = $this->sanitarIn($this->IDs, ',', false);
1410
                $out = array('orderBy' => "FIND_IN_SET({$this->getCFGDef('sortBy', $this->getPK())}, '{$idList}')");
1411
                $this->config->setConfig($out); //reload config;
1412
                $sort = "ORDER BY " . $out['orderBy'];
1413
                break;
1414
            default:
1415
                $out = array('orderBy' => '', 'order' => '', 'sortBy' => '');
1416
                if (($tmp = $this->getCFGDef('orderBy', '')) != '') {
1417
                    $out['orderBy'] = $tmp;
1418
                } else {
1419
                    switch (true) {
1420
                        case ('' != ($tmp = $this->getCFGDef('sortDir', ''))): //higher priority than order
1421
                            $out['order'] = $tmp;
1422
                        // no break
1423
                        case ('' != ($tmp = $this->getCFGDef('order', ''))):
1424
                            $out['order'] = $tmp;
1425
                        // no break
1426
                    }
1427
                    if ('' == $out['order'] || !in_array(strtoupper($out['order']), array('ASC', 'DESC'))) {
1428
                        $out['order'] = $orderDef; //Default
1429
                    }
1430
1431
                    $out['sortBy'] = (($tmp = $this->getCFGDef('sortBy', '')) != '') ? $tmp : $sortName;
1432
                    $out['orderBy'] = $out['sortBy'] . " " . $out['order'];
1433
                }
1434
                $this->config->setConfig($out); //reload config;
1435
                $sort = "ORDER BY " . $out['orderBy'];
1436
                break;
1437
        }
1438
        $this->debug->debugEnd("sortORDER", 'Get sort order for SQL: ' . $this->debug->dumpData($sort));
1439
1440
        return $sort;
1441
    }
1442
1443
    /**
1444
     * Получение LIMIT вставки в SQL запрос
1445
     *
1446
     * @return string LIMIT вставка в SQL запрос
1447
     */
1448
    protected function LimitSQL($limit = 0, $offset = 0)
1449
    {
1450
        $this->debug->debug('', 'limitSQL', 2);
1451
        $ret = '';
1452
        if ($limit == 0) {
1453
            $limit = $this->getCFGDef('display', 0);
1454
        }
1455
        $maxDocs = $this->getCFGDef('maxDocs', 0);
1456
        if ($maxDocs > 0 && $limit > $maxDocs) {
1457
            $limit = $maxDocs;
1458
        }
1459
        if ($offset == 0) {
1460
            $offset = $this->getCFGDef('offset', 0);
1461
        }
1462
        $offset += $this->getCFGDef('start', 0);
1463
        $total = $this->getCFGDef('total', 0);
1464
        if ($limit < ($total - $limit)) {
1465
            $limit = $total - $offset;
1466
        }
1467
1468
        if ($limit != 0) {
1469
            $ret = "LIMIT " . (int)$offset . "," . (int)$limit;
1470
        } else {
1471
            if ($offset != 0) {
1472
                /**
1473
                 * 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
1474
                 * @see http://dev.mysql.com/doc/refman/5.0/en/select.html
1475
                 */
1476
                $ret = "LIMIT " . (int)$offset . ",18446744073709551615";
1477
            }
1478
        }
1479
        $this->debug->debugEnd("limitSQL", "Get limit for SQL: " . $this->debug->dumpData($ret));
1480
1481
        return $ret;
1482
    }
1483
1484
    /**
1485
     * Clean up the modx and html tags
1486
     *
1487
     * @param string $data String for cleaning
1488
     * @param string $charset
1489
     * @return string Clear string
1490
     */
1491
    public function sanitarData($data, $charset = 'UTF-8')
1492
    {
1493
        return APIHelpers::sanitarTag($data, $charset);
1494
    }
1495
1496
    /**
1497
     * run tree build
1498
     *
1499
     * @param string $idField default name id field
1500
     * @param string $parentField default name parent field
1501
     * @return array
1502
     */
1503
    public function treeBuild($idField = 'id', $parentField = 'parent')
1504
    {
1505
        return $this->_treeBuild($this->_docs, $this->getCFGDef('idField', $idField),
1506
            $this->getCFGDef('parentField', $parentField));
1507
    }
1508
1509
    /**
1510
     * @see: https://github.com/DmitryKoterov/DbSimple/blob/master/lib/DbSimple/Generic.php#L986
1511
     *
1512
     * @param array $data Associative data array
1513
     * @param string $idName name ID field in associative data array
1514
     * @param string $pidName name parent field in associative data array
1515
     * @return array
1516
     */
1517
    private function _treeBuild($data, $idName, $pidName)
1518
    {
1519
        $children = array(); // children of each ID
1520
        $ids = array();
1521
        foreach ($data as $i => $r) {
1522
            $row =& $data[$i];
1523
            $id = $row[$idName];
1524
            $pid = $row[$pidName];
1525
            $children[$pid][$id] =& $row;
1526
            if (!isset($children[$id])) {
1527
                $children[$id] = array();
1528
            }
1529
            $row['#childNodes'] =& $children[$id];
1530
            $ids[$row[$idName]] = true;
1531
        }
1532
        // Root elements are elements with non-found PIDs.
1533
        $this->_tree = array();
1534
        foreach ($data as $i => $r) {
1535
            $row =& $data[$i];
1536
            if (!isset($ids[$row[$pidName]])) {
1537
                $this->_tree[$row[$idName]] = $row;
1538
            }
1539
        }
1540
1541
        return $this->_tree;
1542
    }
1543
1544
    /**
1545
     * Получение PrimaryKey основной таблицы.
1546
     * По умолчанию это id. Переопределить можно в контроллере присвоив другое значение переменной idField
1547
     * @param bool $full если true то возвращается значение для подстановки в запрос
1548
     * @return string PrimaryKey основной таблицы
1549
     */
1550
    public function getPK($full = true)
1551
    {
1552
        $idField = isset($this->idField) ? $this->idField: 'id';
1553
        if ($full) {
1554
            $idField = '`' . $idField . '`';
1555
            if (!empty($this->alias)) {
1556
                $idField = '`' . $this->alias . '`.' . $idField;
1557
            }
1558
        }
1559
1560
        return $idField;
1561
    }
1562
1563
    /**
1564
     * Получение Parent key
1565
     * По умолчанию это parent. Переопределить можно в контроллере присвоив другое значение переменной parentField
1566
     * @param bool $full если true то возвращается значение для подстановки в запрос
1567
     * @return string Parent Key основной таблицы
1568
     */
1569
    public function getParentField($full = true)
1570
    {
1571
        $parentField = isset($this->parentField) ? $this->parentField : '';
1572
        if ($full && !empty($parentField)) {
1573
            $parentField = '`' . $parentField . '`';
1574
            if (!empty($this->alias)) {
1575
                $parentField = '`' . $this->alias . '`.' . $parentField;
1576
            }
1577
        }
1578
1579
        return $parentField;
1580
    }
1581
1582
    /**
1583
     * Разбор фильтров
1584
     * OR(AND(filter:field:operator:value;filter2:field:oerpator:value);(...)), etc.
1585
     *
1586
     * @param string $filter_string строка со всеми фильтрами
1587
     * @return mixed результат разбора фильтров
1588
     */
1589
    protected function getFilters($filter_string)
1590
    {
1591
        $this->debug->debug("getFilters: " . $this->debug->dumpData($filter_string), 'getFilter', 1);
1592
        // the filter parameter tells us, which filters can be used in this query
1593
        $filter_string = trim($filter_string, ' ;');
1594
        if (!$filter_string) {
1595
            return;
1596
        }
1597
        $output = array('join' => '', 'where' => '');
1598
        $logic_op_found = false;
1599
        $joins = $wheres = array();
1600
        foreach ($this->_logic_ops as $op => $sql) {
1601
            if (strpos($filter_string, $op) === 0) {
1602
                $logic_op_found = true;
1603
                $subfilters = mb_substr($filter_string, strlen($op) + 1, mb_strlen($filter_string, "UTF-8"), "UTF-8");
1604
                $subfilters = $this->smartSplit($subfilters);
1605
                foreach ($subfilters as $subfilter) {
1606
                    $subfilter = $this->getFilters(trim($subfilter));
1607
                    if (!$subfilter) {
1608
                        continue;
1609
                    }
1610
                    if ($subfilter['join']) {
1611
                        $joins[] = $subfilter['join'];
1612
                    }
1613
                    if ($subfilter['where']) {
1614
                        $wheres[] = $subfilter['where'];
1615
                    }
1616
                }
1617
                $output['join'] = !empty($joins) ? implode(' ', $joins) : '';
1618
                $output['where'] = !empty($wheres) ? '(' . implode($sql, $wheres) . ')' : '';
1619
            }
1620
        }
1621
1622
        if (!$logic_op_found) {
1623
            $filter = $this->loadFilter($filter_string);
1624
            if (!$filter) {
1625
                $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

1625
                $this->debug->/** @scrutinizer ignore-call */ 
1626
                              warning('Error while loading DocLister filter "' . $this->debug->dumpData($filter_string) . '": check syntax!');
Loading history...
1626
                $output = false;
1627
            } else {
1628
                $output['join'] = $filter->get_join();
1629
                $output['where'] = stripslashes($filter->get_where());
1630
            }
1631
        }
1632
        $this->debug->debug('getFilter');
1633
1634
        return $output;
1635
    }
1636
1637
    /**
1638
     * @return mixed
1639
     */
1640
    public function filtersWhere()
1641
    {
1642
        return APIHelpers::getkey($this->_filters, 'where', '');
1643
    }
1644
1645
    /**
1646
     * @return mixed
1647
     */
1648
    public function filtersJoin()
1649
    {
1650
        return APIHelpers::getkey($this->_filters, 'join', '');
1651
    }
1652
1653
    /**
1654
     * @param string $join
1655
     * @return $this
1656
     */
1657
    public function setFiltersJoin($join = '') {
1658
        if (!empty($join)) {
1659
            if (!empty($this->_filters['join'])) {
1660
                $this->_filters['join'] .= ' ' . $join;
1661
            } else {
1662
                $this->_filters['join'] = $join;
1663
            }
1664
        }
1665
1666
        return $this;
1667
    }
1668
1669
    /**
1670
     * Приведение типа поля
1671
     *
1672
     * @param $field string имя поля
1673
     * @param $type string тип фильтрации
1674
     * @return string имя поля с учетом приведения типа
1675
     */
1676
    public function changeSortType($field, $type)
1677
    {
1678
        $type = trim($type);
1679
        switch (strtoupper($type)) {
1680
            case 'DECIMAL':
1681
                $field = 'CAST(' . $field . ' as DECIMAL(10,2))';
1682
                break;
1683
            case 'UNSIGNED':
1684
                $field = 'CAST(' . $field . ' as UNSIGNED)';
1685
                break;
1686
            case 'BINARY':
1687
                $field = 'CAST(' . $field . ' as BINARY)';
1688
                break;
1689
            case 'DATETIME':
1690
                $field = 'CAST(' . $field . ' as DATETIME)';
1691
                break;
1692
            case 'SIGNED':
1693
                $field = 'CAST(' . $field . ' as SIGNED)';
1694
                break;
1695
        }
1696
1697
        return $field;
1698
    }
1699
1700
    /**
1701
     * Загрузка фильтра
1702
     * @param string $filter срока с параметрами фильтрации
1703
     * @return bool
1704
     */
1705
    protected function loadFilter($filter)
1706
    {
1707
        $this->debug->debug('Load filter ' . $this->debug->dumpData($filter), 'loadFilter', 2);
1708
        $out = false;
1709
        $fltr_params = explode(':', $filter, 2);
1710
        $fltr = APIHelpers::getkey($fltr_params, 0, null);
1711
        /**
1712
        * @var tv_DL_filter|content_DL_filter $fltr_class
1713
        */
1714
        $fltr_class = $fltr . '_DL_filter';
1715
        // check if the filter is implemented
1716
        if (!is_null($fltr)) {
1717
            if (!class_exists($fltr_class) && file_exists(__DIR__ . '/filter/' . $fltr . '.filter.php')) {
0 ignored issues
show
Bug introduced by
$fltr_class of type tv_DL_filter|content_DL_filter is incompatible with the type string expected by parameter $class_name 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

1717
            if (!class_exists(/** @scrutinizer ignore-type */ $fltr_class) && file_exists(__DIR__ . '/filter/' . $fltr . '.filter.php')) {
Loading history...
1718
                require_once dirname(__FILE__) . '/filter/' . $fltr . '.filter.php';
1719
            }
1720
            if (class_exists($fltr_class)) {
1721
                $this->totalFilters++;
1722
                $fltr_obj = new $fltr_class();
1723
                if ($fltr_obj->init($this, $filter)) {
1724
                    $out = $fltr_obj;
1725
                } else {
1726
                    $this->debug->error("Wrong filter parameter: '{$this->debug->dumpData($filter)}'", 'Filter');
1727
                }
1728
            }
1729
        }
1730
        if (!$out) {
0 ignored issues
show
introduced by
The condition ! $out can never be false.
Loading history...
1731
            $this->debug->error("Error load Filter: '{$this->debug->dumpData($filter)}'", 'Filter');
1732
        }
1733
1734
        $this->debug->debugEnd("loadFilter");
1735
1736
        return $out;
1737
    }
1738
1739
    /**
1740
     * Общее число фильтров
1741
     * @return int
1742
     */
1743
    public function getCountFilters()
1744
    {
1745
        return (int)$this->totalFilters;
1746
    }
1747
1748
    /**
1749
     * Выполнить SQL запрос
1750
     * @param string $q SQL запрос
1751
     */
1752
    public function dbQuery($q)
1753
    {
1754
        $this->debug->debug($q, "query", 1, 'sql');
1755
        $out = $this->modx->db->query($q);
1756
        $this->debug->debugEnd("query");
1757
1758
        return $out;
1759
    }
1760
1761
    /**
1762
     * Экранирование строки в SQL запросе LIKE
1763
     * @see: http://stackoverflow.com/a/3683868/2323306
1764
     *
1765
     * @param string $field поле по которому осуществляется поиск
1766
     * @param string $value искомое значение
1767
     * @param string $escape экранирующий символ
1768
     * @param string $tpl шаблон подстановки значения в SQL запрос
1769
     * @return string строка для подстановки в SQL запрос
1770
     */
1771
    public function LikeEscape($field, $value, $escape = '=', $tpl = '%[+value+]%')
1772
    {
1773
        return sqlHelper::LikeEscape($this->modx, $field, $value, $escape, $tpl);
1774
    }
1775
1776
    /**
1777
     * Получение REQUEST_URI без GET-ключа с
1778
     * @return string
1779
     */
1780
    public function getRequest()
1781
    {
1782
        $URL = null;
1783
        parse_str(parse_url(MODX_SITE_URL . $_SERVER['REQUEST_URI'], PHP_URL_QUERY), $URL);
1 ignored issue
show
Bug introduced by
The constant MODX_SITE_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1784
1785
        return http_build_query(array_merge($URL, array(DocLister::AliasRequest => null)));
0 ignored issues
show
Bug introduced by
$URL of type null is incompatible with the type array expected by parameter $array1 of array_merge(). ( Ignorable by Annotation )

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

1785
        return http_build_query(array_merge(/** @scrutinizer ignore-type */ $URL, array(DocLister::AliasRequest => null)));
Loading history...
1786
    }
1787
}
1788