HelperWidget::addError()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 197 and the first side effect is on line 147.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace DigitalWand\AdminHelper\Widget;
4
5
use Bitrix\Main\Localization\Loc;
6
use DigitalWand\AdminHelper\Helper\AdminBaseHelper;
7
use DigitalWand\AdminHelper\Helper\AdminEditHelper;
8
use DigitalWand\AdminHelper\Helper\AdminListHelper;
9
use Bitrix\Main\Entity\DataManager;
10
11
/**
12
 * Виджет - класс, отвечающий за внешний вид отдельно взятого поля сущности. Один виджет отвечает за:
13
 * <ul>
14
 * <li>Отображение поля на странице редактирования</li>
15
 * <li>Отображение ячейки поля в таблице списка - при просмотре и редактировании</li>
16
 * <li>Отображение фильтра по данному полю</li>
17
 * <li>Валидацию значения поля</li>
18
 * </ul>
19
 *
20
 * Также виджетами осуществляется предварительная обработка данных:
21
 * <ul>
22
 * <li>Перед сохранением значения поля в БД</li>
23
 * <li>После получения значения поля из БД</li>
24
 * <li>Модификация запроса перед фильтрацией</li>
25
 * <li>Модификация пуеДшые перед выборкой данных</li>
26
 * </ul>
27
 *
28
 * Для получения минимальной функциональности достаточно переопределить основные методы, отвечающие за отображение
29
 * виджета в списке и на детальной.
30
 *
31
 * Каждый виджет имеет ряд специфических настроек, некоторые из которых обязательны для заполнения. Подробную
32
 * документацию по настройкам стоит искать в документации к конкретному виджету. Настройки могут быть переданы в
33
 * виджет как при описании всего интерфейса в файле Interface.php, так и непосредственно во время исполнения,
34
 * внутри Helper-классов.
35
 *
36
 * При указании настроек типа "да"/"нет", нельзя использовать строковые обозначения "Y"/"N":
37
 * для этого есть булевы true и false.
38
 *
39
 * Настройки базового класса:
40
 * <ul>
41
 * <li><b>HIDE_WHEN_CREATE</b> - скрывает поле в форме редактирования, если создаётся новый элемент, а не открыт
42
 *     существующий на редактирование.</li>
43
 * <li><b>TITLE</b> - название поля. Если не задано то возьмется значение title из DataManager::getMap()
44
 *       через getField($code)->getTitle(). Будет использовано в фильтре, заголовке таблицы и в качестве подписи поля
45
 *     на
46
 *     странице редактирования.</li>
47
 * <li><b>REQUIRED</b> - является ли поле обязательным.</li>
48
 * <li><b>READONLY</b> - поле нельзя редактировать, предназначено только для чтения</li>
49
 * <li><b>FILTER</b> - позволяет указать способ фильтрации по полю. В базовом классе возможен только вариант "BETWEEN"
50
 *     или "><". И в том и в другом случае это будет означать фильтрацию по диапазону значений. Количество возможных
51
 *     вариантов этого параметра может быть расширено в наследуемых классах</li>
52
 * <li><b>UNIQUE</b> - поле должно содержать только уникальные значения</li>
53
 * <li><b>VIRTUAL</b> - особая настройка, отражается как на поведении виджета, так и на поведении хэлперов. Поле,
54
 *     объявленное виртуальным, отображается в графическом интерфейче, однако не участвоует в запросах к БД. Опция
55
 *     может быть полезной при реализации нестандартной логики, когда, к примеру, под именем одного поля могут
56
 *     выводиться данные из нескольких полей сразу. </li>
57
 * <li><b>EDIT_IN_LIST</b> - параметр не обрабатывается непосредственно виджетом, однако используется хэлпером.
58
 *     Указывает, можно ли редактировать данное поле в спискке</li>
59
 * <li><b>MULTIPLE</b> - bool является ли поле множественным</li>
60
 * <li><b>MULTIPLE_FIELDS</b> - bool поля используемые в хранилище множественных значений и их алиасы</li>
61
 * <li><b>LIST</b> - отображать ли поле в списке доступных в настройках столбцов таблицы (по-умолчанию true)</li>
62
 * <li><b>HEADER</b> - является ли столбец отображаемым по-умолчанию, если вывод столбцов таблицы не настроен (по-умолчанию true)</li>
63
 * </ul>
64
 *
65
 * Как сделать виджет множественным?
66
 * <ul>
67
 * <li>Реализуйте метод genMultipleEditHTML(). Метод должен выводить множественную форму ввода. Для реализации формы
68
 * ввода есть JS хелпер HelperWidget::jsHelper()</li>
69
 * <li>Опишите поля, которые будут переданы связи в EntityManager. Поля описываются в настройке "MULTIPLE_FIELDS"
70
 *     виджета. По умолчанию множественный виджет использует поля ID, ENTITY_ID, VALUE</li>
71
 * <li>Полученные от виджета данные будут переданы в EntityManager и сохранены как связанные данные</li>
72
 * </ul>
73
 * Пример реализации можно увидеть в виджете StringWidget
74
 *
75
 * Как использовать множественный виджет?
76
 * <ul>
77
 * <li>
78
 * Создайте таблицу и модель, которая будет хранить данные поля
79
 * - Таблица обязательно должна иметь поля, которые требует виджет.
80
 * Обязательные поля виджета по умолчанию описаны в: HelperWidget::$settings['MULTIPLE_FIELDS']
81
 * Если у виджета нестандартный набор полей, то они хранятся в: SomeWidget::$settings['MULTIPLE_FIELDS']
82
 * - Если поля, которые требует виджет есть в вашей таблице, но они имеют другие названия,
83
 * можно настроить виджет для работы с вашими полями.
84
 * Для этого переопределите настройку MULTIPLE_FIELDS при объявлении поля в интерфейсе следующим способом:
85
 * ```
86
 * 'RELATED_LINKS' => array(
87
 *        'WIDGET' => new StringWidget(),
88
 *        'TITLE' => 'Ссылки',
89
 *        'MULTIPLE' => true,
90
 *        // Обратите внимание, именно тут переопределяются поля виджета
91
 *        'MULTIPLE_FIELDS' => array(
92
 *            'ID', // Должны быть прописаны все поля, даже те, которые не нужно переопределять
93
 *            'ENTITY_ID' => 'NEWS_ID', // ENTITY_ID - поле, которое требует виджет, NEWS_ID - пример поля, которое
94
 *     будет использоваться вместо ENTITY_ID
95
 *            'VALUE' => 'LINK', // VALUE - поле, которое требует виджет, LINK - пример поля, которое будет
96
 *     использоваться вместо VALUE
97
 *        )
98
 *    ),
99
 * ```
100
 * </li>
101
 *
102
 * <li>
103
 * Далее в основной модели (та, которая указана в AdminBaseHelper::$model) нужно прописать связь с моделью,
104
 * в которой вы хотите хранить данные поля
105
 * Пример объявления связи:
106
 * ```
107
 * new Entity\ReferenceField(
108
 *        'RELATED_LINKS',
109
 *        'namespace\NewsLinksTable',
110
 *        array('=this.ID' => 'ref.NEWS_ID'),
111
 *          // Условия FIELD и ENTITY не обязательны, подробности смотрите в комментариях к классу @see EntityManager
112
 *        'ref.FIELD' => new DB\SqlExpression('?s', 'NEWS_LINKS'),
113
 *        'ref.ENTITY' => new DB\SqlExpression('?s', 'news'),
114
 * ),
115
 * ```
116
 * </li>
117
 *
118
 * <li>
119
 * Что бы виджет работал во множественном режиме, нужно при описании интерфейса поля указать параметр MULTIPLE => true
120
 * ```
121
 * 'RELATED_LINKS' => array(
122
 *        'WIDGET' => new StringWidget(),
123
 *        'TITLE' => 'Ссылки',
124
 *        // Включаем режим множественного ввода
125
 *        'MULTIPLE' => true,
126
 * )
127
 * ```
128
 * </li>
129
 *
130
 * <li>
131
 * Готово :)
132
 * </li>
133
 * </ul>
134
 *
135
 * О том как сохраняются данные множественных виджетов можно узнать из комментариев 
136
 * класса \DigitalWand\AdminHelper\EntityManager.
137
 *
138
 * @see EntityManager
139
 * @see HelperWidget::getEditHtml()
140
 * @see HelperWidget::generateRow()
141
 * @see showFilterHtml::showFilterHTML()
142
 * @see HelperWidget::setSetting()
143
 * 
144
 * @author Nik Samokhvalov <[email protected]>
145
 * @author Dmitriy Baibuhtin <[email protected]>
146
 */
147
abstract class HelperWidget
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
148
{
149
    const LIST_HELPER = 1;
150
    const EDIT_HELPER = 2;
151
152
    /**
153
     * @var string Код поля.
154
     */
155
    protected $code;
156
    /**
157
     * @var array $settings Настройки виджета для данной модели.
158
     */
159
    protected $settings = array(
160
        // Поля множественного виджета по умолчанию (array('ОРИГИНАЛЬНОЕ НАЗВАНИЕ', 'ОРИГИНАЛЬНОЕ НАЗВАНИЕ' => 'АЛИАС'))
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
161
        'MULTIPLE_FIELDS' => array('ID', 'VALUE', 'ENTITY_ID')
162
    );
163
    /**
164
     * @var array Настройки "по-умолчанию" для модели.
165
     */
166
    static protected $defaults;
167
    /**
168
     * @var DataManager Название класса модели.
169
     */
170
    protected $entityName;
171
    /**
172
     * @var array Данные модели.
173
     */
174
    protected $data;
175
    /** @var  AdminBaseHelper|AdminListHelper|AdminEditHelper $helper Экземпляр хэлпера, вызывающий данный виджет.
176
     */
177
    protected $helper;
178
    /**
179
     * @var bool Статус отображения JS хелпера. Используется для исключения дублирования JS-кода.
180
     */
181
    protected $jsHelper = false;
182
    /**
183
     * @var array $validationErrors Ошибки валидации поля.
184
     */
185
    protected $validationErrors = array();
186
    /**
187
     * @var string Строка, добавляемая к полю name полей фильтра.
188
     */
189
    protected $filterFieldPrefix = 'find_';
190
191
    /**
192
     * Эксемпляр виджета создаётся всего один раз, при описании настроек интерфейса. При создании есть возможность
193
     * сразу указать для него необходимые настройки.
194
     * 
195
     * @param array $settings
196
     */
197
    public function __construct(array $settings = array())
198
    {
199
        Loc::loadMessages(__FILE__);
200
        
201
        $this->settings = $settings;
202
    }
203
204
    /**
205
     * Генерирует HTML для редактирования поля.
206
     *
207
     * @return string
208
     * 
209
     * @api
210
     */
211
    abstract protected function getEditHtml();
212
213
    /**
214
     * Генерирует HTML для редактирования поля в мульти-режиме.
215
     *
216
     * @return string
217
     * 
218
     * @api
219
     */
220
    protected function getMultipleEditHtml()
221
    {
222
        return Loc::getMessage('DIGITALWAND_AH_MULTI_NOT_SUPPORT');
223
    }
224
225
    /**
226
     * Оборачивает поле в HTML код, который в большинстве случаев менять не придется. Далее вызывается 
227
     * кастомизируемая часть.
228
     *
229
     * @param bool $isPKField Является ли поле первичным ключом модели.
230
     *
231
     * @see HelperWidget::getEditHtml();
232
     */
233
    public function showBasicEditField($isPKField)
234
    {
235
        if ($this->getSettings('HIDE_WHEN_CREATE') AND !isset($this->data['ID'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
236
            return;
237
        }
238
239
        // JS хелперы
240
        $this->jsHelper();
241
242
        if ($this->getSettings('USE_BX_API')) {
243
            $this->getEditHtml();
244
        } else {
245
            print '<tr>';
246
            $title = $this->getSettings('TITLE');
247
            if ($this->getSettings('REQUIRED') === true) {
248
                $title = '<b>' . $title . '</b>';
249
            }
250
            print '<td width="40%" style="vertical-align: top;">' . $title . ':</td>';
251
252
            $field = $this->getValue();
253
            
254
            if (is_null($field)) {
255
                $field = '';
256
            }
257
258
            $readOnly = $this->getSettings('READONLY');
259
260
            if (!$readOnly AND !$isPKField) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
261
                if ($this->getSettings('MULTIPLE')) {
262
                    $field = $this->getMultipleEditHtml();
263
                } else {
264
                    $field = $this->getEditHtml();
265
                }
266
            } else {
267
                if ($readOnly) {
268
                    if ($this->getSettings('MULTIPLE')) {
269
                        $field = $this->getMultipleValueReadonly();
270
                    } else {
271
                        $field = $this->getValueReadonly();
272
                    }
273
                }
274
            }
275
276
            print '<td width="60%">' . $field . '</td>';
277
            print '</tr>';
278
        }
279
    }
280
281
    /**
282
     * Возвращает значение поля в форме "только для чтения" для не множественных свойств.
283
     *
284
     * @return mixed
285
     */
286
    protected function getValueReadonly()
287
    {
288
        return static::prepareToOutput($this->getValue());
289
    }
290
291
    /**
292
     * Возвращает значения множественного поля.
293
     * 
294
     * @return array
295
     */
296
    protected function getMultipleValue()
297
    {
298
        $rsEntityData = null;
0 ignored issues
show
Unused Code introduced by
$rsEntityData is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
299
        $values = array();
300
        if (!empty($this->data['ID'])) {
301
            $entityName = $this->entityName;
302
            $rsEntityData = $entityName::getList(array(
303
                'select' => array('REFERENCE_' => $this->getCode() . '.*'),
304
                'filter' => array('=ID' => $this->data['ID'])
305
            ));
306
307
            if ($rsEntityData) {
308
                while ($referenceData = $rsEntityData->fetch()) {
309
                    if (empty($referenceData['REFERENCE_' . $this->getMultipleField('ID')])) {
310
                        continue;
311
                    }
312
                    $values[] = $referenceData['REFERENCE_' . $this->getMultipleField('VALUE')];
313
                }
314
            }
315
        } else {
316
            if ($this->data[$this->code]) {
317
                $values = $this->data[$this->code];
318
            }
319
        }
320
321
        return $values;
322
    }
323
324
    /**
325
     * Возвращает значение поля в форме "только для чтения" для множественных свойств.
326
     *
327
     * @return string
328
     */
329
    protected function getMultipleValueReadonly()
330
    {
331
        $values = $this->getMultipleValue();
332
333
        foreach ($values as &$value) {
334
            $value = static::prepareToOutput($value);
335
        }
336
337
        return join('<br/>', $values);
338
    }
339
340
    /**
341
     * Обработка строки для безопасного отображения. Если нужно отобразить текст как аттрибут тега, 
342
     * используйте static::prepareToTag().
343
     *
344
     * @param string $string
345
     * @param bool $hideTags Скрыть теги:
346
     * 
347
     * - true - вырезать теги оставив содержимое. Результат обработки: <b>text</b> = text
348
     * 
349
     * - false - отобразаить теги в виде текста. Результат обработки: <b>text</b> = &lt;b&gt;text&lt;/b&gt;
350
     *
351
     * @return string
352
     */
353
    public static function prepareToOutput($string, $hideTags = true)
354
    {
355
        if ($hideTags) {
356
            return preg_replace('/<.+>/mU', '', $string);
357
        } else {
358
            return htmlspecialchars($string, ENT_QUOTES, SITE_CHARSET);
359
        }
360
    }
361
    
362
    /**
363
     * Подготовка строки для использования в аттрибутах тегов. Например:
364
     * ```
365
     * <input name="test" value="<?= HelperWidget::prepareToTagAttr($value) ?>"/>
366
     * ```
367
     * 
368
     * @param string $string
369
     *
370
     * @return string
371
     */
372
    public static function prepareToTagAttr($string)
373
    {
374
        // Не используйте addcslashes в этом методе, иначе в тегах будут дубли обратных слешей
375
        return htmlspecialchars($string, ENT_QUOTES, SITE_CHARSET);
376
    }
377
378
    /**
379
     * Подготовка строки для использования в JS.
380
     *
381
     * @param string $string
382
     *
383
     * @return string
384
     */
385
    public static function prepareToJs($string)
386
    {
387
        $string = htmlspecialchars($string, ENT_QUOTES, SITE_CHARSET);
388
        $string = addcslashes($string, "\r\n\"\\");
389
390
        return $string;
391
    }
392
393
    /**
394
     * Генерирует HTML для поля в списке.
395
     *
396
     * @param \CAdminListRow $row
397
     * @param array $data Данные текущей строки.
398
     *
399
     * @return void
400
     *
401
     * @see AdminListHelper::addRowCell()
402
     * 
403
     * @api
404
     */
405
    abstract public function generateRow(&$row, $data);
406
407
    /**
408
     * Генерирует HTML для поля фильтрации.
409
     *
410
     * @return void
411
     *
412
     * @see AdminListHelper::createFilterForm()
413
     * 
414
     * @api
415
     */
416
    abstract public function showFilterHtml();
417
418
    /**
419
     * Возвращает массив настроек данного виджета, либо значение отдельного параметра, если указано его имя.
420
     *
421
     * @param string $name Название конкретного параметра.
422
     *
423
     * @return array|mixed
424
     * 
425
     * @api
426
     */
427
    public function getSettings($name = '')
428
    {
429
        if (empty($name)) {
430
            return $this->settings;
431
        } else {
432
            if (isset($this->settings[$name])) {
433
                return $this->settings[$name];
434
            } else {
435
                if (isset(static::$defaults[$name])) {
436
                    return static::$defaults[$name];
437
                } else {
438
                    return false;
439
                }
440
            }
441
        }
442
    }
443
444
    /**
445
     * Передаёт в виджет ссылку на вызывающий его объект.
446
     *
447
     * @param AdminBaseHelper $helper
448
     */
449
    public function setHelper(&$helper)
450
    {
451
        $this->helper = $helper;
452
    }
453
454
    /**
455
     * Возвращает текукщее значение поля фильтрации (спец. символы экранированы).
456
     *
457
     * @return bool|string
458
     */
459
    protected function getCurrentFilterValue()
0 ignored issues
show
Coding Style introduced by
getCurrentFilterValue uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
460
    {
461
        if (isset($GLOBALS[$this->filterFieldPrefix . $this->code])) {
462
            return htmlspecialcharsbx($GLOBALS[$this->filterFieldPrefix . $this->code]);
463
        } else {
464
            return false;
465
        }
466
    }
467
468
    /**
469
     * Проверяет корректность введенных в фильтр значений
470
     *
471
     * @param string $operationType тип операции
472
     * @param mixed $value значение фильтра
473
     *
474
     * @see AdminListHelper::checkFilter();
475
     * @return bool
476
     */
477
    public function checkFilter($operationType, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $operationType is not used and could be removed.

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

Loading history...
478
    {
479
        return true;
480
    }
481
482
    /**
483
     * Позволяет модифицировать опции, передаваемые в getList, непосредственно перед выборкой.
484
     * Если в настройках явно указан способ фильтрации, до добавляет соответствующий префикс в $arFilter.
485
     * Если фильтр BETWEEN, то формирует сложную логику фильтрации.
486
     *
487
     * @param array $filter $arFilter целиком
488
     * @param array $select
489
     * @param       $sort
490
     * @param array $raw $arSelect, $arFilter, $arSort до примененных к ним преобразований.
491
     *
492
     * @see AdlinListHelper::getData();
493
     */
494
    public function changeGetListOptions(&$filter, &$select, &$sort, $raw)
0 ignored issues
show
Unused Code introduced by
The parameter $select is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $sort is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $raw is not used and could be removed.

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

Loading history...
Coding Style introduced by
changeGetListOptions uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
495
    {
496
        if ($this->isFilterBetween()) {
497
            $field = $this->getCode();
498
            $from = $to = false;
499
500
            if (isset($_REQUEST['find_' . $field . '_from'])) {
501
                $from = $_REQUEST['find_' . $field . '_from'];
502
                if (is_a($this, 'DateWidget')) {
503
                    $from = date('Y-m-d H:i:s', strtotime($from));
504
                }
505
            }
506
            if (isset($_REQUEST['find_' . $field . '_to'])) {
507
                $to = $_REQUEST['find_' . $field . '_to'];
508
                if (is_a($this, 'DateWidget')) {
509
                    $to = date('Y-m-d 23:59:59', strtotime($to));
510
                } else if (
511
                        is_a($this, '\DigitalWand\AdminHelper\Widget\DateTimeWidget') &&
512
                        !preg_match('/\d{2}:\d{2}:\d{2}/', $to)
513
                ) {
514
                    $to = date('d.m.Y 23:59:59', strtotime($to));
515
                }
516
            }
517
518
            if ($from !== false AND $to !== false) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
519
                $filter['><' . $field] = array($from, $to);
520
            } else {
521
                if ($from !== false) {
522
                    $filter['>' . $field] = $from;
523
                } else {
524
                    if ($to !== false) {
525
                        $filter['<' . $field] = $to;
526
                    }
527
                }
528
            }
529
        } else {
530
            if ($filterPrefix = $this->getSettings('FILTER') AND $filterPrefix !== true AND isset($filter[$this->getCode()])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
531
                $filter[$filterPrefix . $this->getCode()] = $filter[$this->getCode()];
532
                unset($filter[$this->getCode()]);
533
            }
534
        }
535
    }
536
537
    /**
538
     * Проверяет оператор фильтрации.
539
     * 
540
     * @return bool
541
     */
542
    protected function isFilterBetween()
543
    {
544
        return $this->getSettings('FILTER') === '><' OR $this->getSettings('FILTER') === 'BETWEEN';
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
545
    }
546
547
    /**
548
     * Действия, выполняемые над полем в процессе редактирования элемента, до его сохранения.
549
     * По-умолчанию выполняется проверка обязательных полей и уникальности.
550
     *
551
     * @see AdminEditHelper::editAction();
552
     * @see AdminListHelper::editAction();
553
     */
554
    public function processEditAction()
555
    {
556
        if (!$this->checkRequired()) {
557
            $this->addError('DIGITALWAND_AH_REQUIRED_FIELD_ERROR');
558
        }
559
        if ($this->getSettings('UNIQUE') && !$this->isUnique()) {
560
            $this->addError('DIGITALWAND_AH_DUPLICATE_FIELD_ERROR');
561
        }
562
    }
563
564
    /**
565
     * В совсем экзотических случаях может потребоваться моджифицировать значение поля уже после его сохраненния в БД -
566
     * для последующей обработки каким-либо другим классом.
567
     */
568
    public function processAfterSaveAction()
569
    {
570
    }
571
572
    /**
573
     * Добавляет строку ошибки в массив ошибок.
574
     *
575
     * @param string $messageId Код сообщения об ошибке из лэнг-файла. Плейсхолдер #FIELD# будет заменён на значение 
576
     * параметра TITLE.
577
     * @param array $replace Данные для замены.
578
     *
579
     * @see Loc::getMessage()
580
     */
581
    protected function addError($messageId, $replace = array())
582
    {
583
        $this->validationErrors[$this->getCode()] = Loc::getMessage(
584
            $messageId,
585
            array_merge(array('#FIELD#' => $this->getSettings('TITLE')), $replace)
586
        );
587
    }
588
589
    /**
590
     * Проверка заполненности обязательных полей.
591
     * Не должны быть null или содержать пустую строку.
592
     *
593
     * @return bool
594
     */
595 View Code Duplication
    public function checkRequired()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
596
    {
597
        if ($this->getSettings('REQUIRED') == true) {
598
            $value = $this->getValue();
599
600
            return !is_null($value) && !empty($value);
601
        } else {
602
            return true;
603
        }
604
    }
605
606
    /**
607
     * Выставляет код для данного виджета при инициализации. Перегружает настройки.
608
     * 
609
     * @param string $code
610
     */
611
    public function setCode($code)
612
    {
613
        $this->code = $code;
614
        $this->loadSettings();
615
    }
616
617
    /**
618
     * @return mixed
619
     */
620
    public function getCode()
621
    {
622
        return $this->code;
623
    }
624
625
    /**
626
     * Устанавливает настройки интерфейса для текущего поля.
627
     *
628
     * @param string $code
629
     *
630
     * @return bool
631
     * 
632
     * @see AdminBaseHelper::getInterfaceSettings()
633
     * @see AdminBaseHelper::setFields()
634
     */
635
    public function loadSettings($code = null)
636
    {
637
        $interface = $this->helper->getInterfaceSettings();
638
639
        $code = is_null($code) ? $this->code : $code;
640
641
        if (!isset($interface['FIELDS'][$code])) {
642
            return false;
643
        }
644
        unset($interface['FIELDS'][$code]['WIDGET']);
645
        $this->settings = array_merge($this->settings, $interface['FIELDS'][$code]);
646
        $this->setDefaultValue();
647
648
        return true;
649
    }
650
651
    /**
652
     * Возвращает название сущности данной модели.
653
     * 
654
     * @return string|DataManager
655
     */
656
    public function getEntityName()
657
    {
658
        return $this->entityName;
659
    }
660
661
    /**
662
     * @param string $entityName
663
     */
664
    public function setEntityName($entityName)
665
    {
666
        $this->entityName = $entityName;
0 ignored issues
show
Documentation Bug introduced by
It seems like $entityName of type string is incompatible with the declared type object<Bitrix\Main\Entity\DataManager> of property $entityName.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
667
        $this->setDefaultValue();
668
    }
669
670
    /**
671
     * Устанавливает значение по-умолчанию для данного поля
672
     */
673
    public function setDefaultValue()
674
    {
675
        if (isset($this->settings['DEFAULT']) && is_null($this->getValue())) {
676
            $this->setValue($this->settings['DEFAULT']);
677
        }
678
    }
679
680
    /**
681
     * Передает ссылку на данные сущности в виджет
682
     *
683
     * @param $data
684
     */
685
    public function setData(&$data)
686
    {
687
        $this->data = &$data;
688
        //FIXME: нелепый оверхэд ради того, чтобы можно было централизованно преобразовывать значение при записи
689
        $this->setValue($data[$this->getCode()]);
690
    }
691
692
    /**
693
     * Возвращает текущее значение, хранимое в поле виджета
694
     * Если такого поля нет, возвращает null
695
     *
696
     * @return mixed|null
697
     */
698
    public function getValue()
699
    {
700
        $code = $this->getCode();
701
702
        return isset($this->data[$code]) ? $this->data[$code] : null;
703
    }
704
705
    /**
706
     * Устанавливает значение поля
707
     *
708
     * @param $value
709
     *
710
     * @return bool
711
     */
712
    protected function setValue($value)
713
    {
714
        $code = $this->getCode();
715
        $this->data[$code] = $value;
716
717
        return true;
718
    }
719
720
    /**
721
     * Получения названия поля таблицы, в которой хранятся множественные данные этого виджета
722
     *
723
     * @param string $fieldName Название поля
724
     *
725
     * @return bool|string
726
     */
727
    public function getMultipleField($fieldName)
728
    {
729
        $fields = $this->getSettings('MULTIPLE_FIELDS');
730
        if (empty($fields)) {
731
            return $fieldName;
732
        }
733
734
        // Поиск алиаса названия поля
735
        if (isset($fields[$fieldName])) {
736
            return $fields[$fieldName];
737
        }
738
739
        // Поиск оригинального названия поля
740
        $fieldsFlip = array_flip($fields);
741
742
        if (isset($fieldsFlip[$fieldName])) {
743
            return $fieldsFlip[$fieldName];
744
        }
745
746
        return $fieldName;
747
    }
748
749
    /**
750
     * Выставляет значение отдельной настройки
751
     *
752
     * @param string $name
753
     * @param mixed $value
754
     */
755
    public function setSetting($name, $value)
756
    {
757
        $this->settings[$name] = $value;
758
    }
759
760
    /**
761
     * Возвращает собранные ошибки валидации
762
     * @return array
763
     */
764
    public function getValidationErrors()
765
    {
766
        return $this->validationErrors;
767
    }
768
769
    /**
770
     * Возвращает имена для атрибута name полей фильтра.
771
     * Если это фильтр BETWEEN, то вернёт массив с вариантами from и to.
772
     *
773
     * @return array|string
774
     */
775
    protected function getFilterInputName()
776
    {
777
        if ($this->isFilterBetween()) {
778
            $baseName = $this->filterFieldPrefix . $this->code;;
779
            $inputNameFrom = $baseName . '_from';
780
            $inputNameTo = $baseName . '_to';
781
782
            return array($inputNameFrom, $inputNameTo);
783
        } else {
784
            return $this->filterFieldPrefix . $this->code;
785
        }
786
    }
787
788
    /**
789
     * Возвращает текст для атрибута name инпута редактирования.
790
     *
791
     * @param null $suffix опциональное дополнение к названию поля
792
     *
793
     * @return string
794
     */
795
    protected function getEditInputName($suffix = null)
796
    {
797
        return 'FIELDS[' . $this->getCode() . $suffix . ']';
798
    }
799
800
    /**
801
     * Уникальный ID для DOM HTML
802
     * @return string
803
     */
804
    protected function getEditInputHtmlId()
805
    {
806
        $htmlId = end(explode('\\', $this->entityName)) . '-' . $this->getCode();
0 ignored issues
show
Bug introduced by
explode('\\', $this->entityName) cannot be passed to end() as the parameter $array expects a reference.
Loading history...
807
808
        return strtolower(preg_replace('/[^A-z-]/', '-', $htmlId));
809
    }
810
811
    /**
812
     * Возвращает текст для атрибута name инпута редактирования поля в списке
813
     * @return string
814
     */
815
    protected function getEditableListInputName()
816
    {
817
        $id = $this->data['ID'];
818
819
        return 'FIELDS[' . $id . '][' . $this->getCode() . ']';
820
    }
821
822
    /**
823
     * Определяет тип вызывающего хэлпера, от чего может зависить поведение виджета.
824
     *
825
     * @return bool|int
826
     * @see HelperWidget::EDIT_HELPER
827
     * @see HelperWidget::LIST_HELPER
828
     */
829
    protected function getCurrentViewType()
830
    {
831
        if (is_a($this->helper, 'DigitalWand\AdminHelper\Helper\AdminListHelper')) {
832
            return self::LIST_HELPER;
833
        } else {
834
            if (is_a($this->helper, 'DigitalWand\AdminHelper\Helper\AdminEditHelper')) {
835
                return self::EDIT_HELPER;
836
            }
837
        }
838
839
        return false;
840
    }
841
842
    /**
843
     * Проверяет значение поля на уникальность
844
     * @return bool
845
     */
846
    private function isUnique()
847
    {
848
        if ($this->getSettings('VIRTUAL')) {
849
            return true;
850
        }
851
852
        $value = $this->getValue();
853
        if (empty($value)) {
854
            return true;
855
        }
856
857
        /** @var DataManager $class */
858
        $class = $this->entityName;
859
        $field = $this->getCode();
860
        $idField = 'ID';
861
        $id = $this->data[$idField];
862
863
        $filter = array(
864
            $field => $value,
865
        );
866
867
        if (!empty($id)) {
868
            $filter["!=" . $idField] = $id;
869
        }
870
871
        $count = $class::getCount($filter);
872
873
        if (!$count) {
874
            return true;
875
        }
876
877
        return false;
878
    }
879
880
    /**
881
     * Проверяет, не является ли текущий запрос попыткой выгрузить данные в Excel
882
     * @return bool
883
     */
884
    protected function isExcelView()
0 ignored issues
show
Coding Style introduced by
isExcelView uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
885
    {
886
        if (isset($_REQUEST['mode']) && $_REQUEST['mode'] == 'excel') {
887
            return true;
888
        }
889
890
        return false;
891
    }
892
893
    /**
894
     * @todo Вынести в ресурс (\CJSCore::Init()).
895
     * @todo Описать.
896
     */
897
    protected function jsHelper()
898
    {
899
        if ($this->jsHelper == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
900
            return true;
901
        }
902
903
        $this->jsHelper = true;
904
        \CJSCore::Init(array("jquery"));
905
        ?>
906
        <script>
907
            /**
908
             * Менеджер множественных полей
909
             * Позволяет добавлять и удалять любой HTML код с возможность подстановки динамических данных
910
             * Инструкция:
911
             * - создайте контейнер, где будут хранится отображаться код
912
             * - создайте экземпляр MultipleWidgetHelper
913
             * Например: var multiple = MultipleWidgetHelper(селектор контейнера, шаблон)
914
             * шаблон - это HTML код, который можно будет добавлять и удалять в интерфейсе
915
             * В шаблон можно добавлять переменные, их нужно обрамлять фигурными скобками. Например {{entity_id}}
916
             * Если в шаблоне несколько полей, переменная {{field_id}} обязательна
917
             * Например <input type="text" name="image[{{field_id}}][SRC]"><input type="text" name="image[{{field_id}}][DESCRIPTION]">
918
             * Если добавляемые поле не новое, то обязательно передавайте в addField переменную field_id с ID записи,
919
             * для новосозданных полей переменная заполнится автоматически
920
             */
921
            function MultipleWidgetHelper(container, fieldTemplate) {
922
                this.$container = $(container);
923
                if (this.$container.size() == 0) {
924
                    throw 'Главный контейнер полей не найден (' + container + ')';
925
                }
926
                if (!fieldTemplate) {
927
                    throw 'Не передан обязательный параметр fieldTemplate';
928
                }
929
                this.fieldTemplate = fieldTemplate;
930
                this._init();
931
            }
932
933
            MultipleWidgetHelper.prototype = {
934
                /**
935
                 * Основной контейнер
936
                 */
937
                $container: null,
938
                /**
939
                 * Контейнер полей
940
                 */
941
                $fieldsContainer: null,
942
                /**
943
                 * Шаблон поля
944
                 */
945
                fieldTemplate: null,
946
                /**
947
                 * Счетчик добавлений полей
948
                 */
949
                fieldsCounter: 0,
950
                /**
951
                 * Добавления поля
952
                 * @param data object Данные для шаблона в виде ключ: значение
953
                 */
954
                addField: function (data) {
955
                    // console.log('Добавление поля');
956
                    this.addFieldHtml(this.fieldTemplate, data);
957
                },
958
                addFieldHtml: function (fieldTemplate, data) {
959
                    this.fieldsCounter++;
960
                    this.$fieldsContainer.append(this._generateFieldContent(fieldTemplate, data));
961
                },
962
                /**
963
                 * Удаление поля
964
                 * @param field string|object Селектор или jQuery объект
965
                 */
966
                deleteField: function (field) {
967
                    // console.log('Удаление поля');
968
                    $(field).remove();
969
                    if (this.$fieldsContainer.find('> *').size() == 0) {
970
                        this.addField();
971
                    }
972
                },
973
                _init: function () {
974
                    this.$container.append('<div class="fields-container"></div>');
975
                    this.$fieldsContainer = this.$container.find('.fields-container');
976
                    this.$container.append(this._getAddButton());
977
978
                    this._trackEvents();
979
                },
980
                /**
981
                 * Генерация контента контейнера поля
982
                 * @param data
983
                 * @returns {string}
984
                 * @private
985
                 */
986
                _generateFieldContent: function (fieldTemplate, data) {
987
                    return '<div class="field-container" style="margin-bottom: 5px;">'
988
                        + this._generateFieldTemplate(fieldTemplate, data) + this._getDeleteButton()
989
                        + '</div>';
990
                },
991
                /**
992
                 * Генерация шаблона поля
993
                 * @param data object Данные для подстановки
994
                 * @returns {null}
995
                 * @private
996
                 */
997
                _generateFieldTemplate: function (fieldTemplate, data) {
998
                    if (!data) {
999
                        data = {};
1000
                    }
1001
1002
                    if (typeof data.field_id == 'undefined') {
1003
                        data.field_id = 'new_' + this.fieldsCounter;
1004
                    }
1005
1006
                    $.each(data, function (key, value) {
1007
                        // Подставление значений переменных
1008
                        fieldTemplate = fieldTemplate.replace(new RegExp('\{\{' + key + '\}\}', ['g']), value);
1009
                    });
1010
1011
                    // Удаление из шаблона необработанных переменных
1012
                    fieldTemplate = fieldTemplate.replace(/\{\{.+?\}\}/g, '');
1013
1014
                    return fieldTemplate;
1015
                },
1016
                /**
1017
                 * Кнопка удаления
1018
                 * @returns {string}
1019
                 * @private
1020
                 */
1021
                _getDeleteButton: function () {
1022
                    return '<input type="button" value="-" class="delete-field-button" style="margin-left: 5px;">';
1023
                },
1024
                /**
1025
                 * Кнопка добавления
1026
                 * @returns {string}
1027
                 * @private
1028
                 */
1029
                _getAddButton: function () {
1030
                    return '<input type="button" value="Добавить..." class="add-field-button">';
1031
                },
1032
                /**
1033
                 * Отслеживание событий
1034
                 * @private
1035
                 */
1036
                _trackEvents: function () {
1037
                    var context = this;
1038
                    // Добавление поля
1039
                    this.$container.find('.add-field-button').on('click', function () {
1040
                        context.addField();
1041
                    });
1042
                    // Удаление поля
1043
                    this.$container.on('click', '.delete-field-button', function () {
1044
                        context.deleteField($(this).parents('.field-container'));
1045
                    });
1046
                }
1047
            };
1048
        </script>
1049
        <?
0 ignored issues
show
Security Best Practice introduced by
It is not recommend to use PHP's short opening tag <?, better use <?php, or <?= in case of outputting.

Short opening tags are disabled in PHP’s default configuration. In such a case, all content of this file is output verbatim to the browser without being parsed, or executed.

As a precaution to avoid these problems better use the long opening tag <?php.

Loading history...
1050
    }
1051
}