AdminListHelper::genPopupActionJS()   B
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 24
rs 8.9713
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 33 and the first side effect is on line 12.

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\Helper;
4
5
use Bitrix\Main\Context;
6
use Bitrix\Main\HttpRequest;
7
use Bitrix\Main\Localization\Loc;
8
use Bitrix\Main\Entity\DataManager;
9
use Bitrix\Main\DB\Result;
10
use DigitalWand\AdminHelper\EntityManager;
11
12
Loc::loadMessages(__FILE__);
13
14
/**
15
 * Базовый класс для реализации страницы списка админки.
16
 * При создании своего класса необходимо переопределить следующие переменные:
17
 * <ul>
18
 * <li> static protected $model </Li>
19
 * </ul>
20
 *
21
 * Этого будет дастаточно для получения минимальной функциональности
22
 * Также данный класс может использоваться для отображения всплывающих окон с возможностью выбора элемента из списка
23
 *
24
 * @see AdminBaseHelper::$model
25
 * @see AdminBaseHelper::$module
26
 * @see AdminBaseHelper::$editViewName
27
 * @see AdminBaseHelper::$viewName
28
 * @package AdminHelper
29
 *
30
 * @author Nik Samokhvalov <[email protected]>
31
 * @author Artem Yarygin <[email protected]>
32
 */
33
abstract class AdminListHelper extends AdminBaseHelper
34
{
35
	const OP_GROUP_ACTION = 'AdminListHelper::__construct_groupAction';
36
	const OP_ADMIN_VARIABLES_FILTER = 'AdminListHelper::prepareAdminVariables_filter';
37
	const OP_ADMIN_VARIABLES_HEADER = 'AdminListHelper::prepareAdminVariables_header';
38
	const OP_GET_DATA_BEFORE = 'AdminListHelper::getData_before';
39
	const OP_ADD_ROW_CELL = 'AdminListHelper::addRowCell';
40
	const OP_CREATE_FILTER_FORM = 'AdminListHelper::createFilterForm';
41
	const OP_CHECK_FILTER = 'AdminListHelper::checkFilter';
42
	const OP_EDIT_ACTION = 'AdminListHelper::editAction';
43
44
	/**
45
	 * @var bool
46
	 * Выводить кнопку экспорта в Excel
47
	 * @api
48
	 */
49
	protected $exportExcel = true;
50
	/**
51
	 * @var bool
52
	 * Выводить в списке кол-во элементов пункт Все
53
	 */
54
	protected $showAll = true;
55
	/**
56
	 * @var bool
57
	 * Является ли список всплывающим окном для выбора элементов из списка.
58
	 * В этой версии не должно быть операций удаления/перехода к редактированию.
59
	 */
60
	protected $isPopup = false;
61
	/**
62
	 * @var string
63
	 * Название поля, в котором хранится результат выбора во всплывающем окне
64
	 */
65
	protected $fieldPopupResultName = '';
66
	/**
67
	 * @var string
68
	 * Уникальный индекс поля, в котором хранится результат выбора во всплывающем окне
69
	 */
70
	protected $fieldPopupResultIndex = '';
71
	protected $sectionFields = array();
72
	/**
73
	 * @var string
74
	 * Название столбца, в котором хранится название элемента
75
	 */
76
	protected $fieldPopupResultElTitle = '';
77
	/**
78
	 * @var string
79
	 * Название функции, вызываемой при даблклике на строке списка, в случае, если список выводится в режиме
80
	 *     всплывающего окна
81
	 */
82
	protected $popupClickFunctionName = 'selectRow';
83
	/**
84
	 * @var string
85
	 * Код функции, вызываемой при клике на строке списка
86
	 * @see AdminListHelper::genPipupActionJS()
87
	 */
88
	protected $popupClickFunctionCode;
89
	/**
90
	 * @var array
91
	 * Массив с заголовками таблицы
92
	 * @see \CAdminList::AddHeaders()
93
	 */
94
	protected $arHeader = array();
95
	/**
96
	 * @var array
97
	 * параметры фильтрации списка в классическим битриксовом формате
98
	 */
99
	protected $arFilter = array();
100
	/**
101
	 * @var array
102
	 * Массив, хранящий тип фильтра для данного поля. Позволяет избежать лишнего парсинга строк.
103
	 */
104
	protected $filterTypes = array();
105
	/**
106
	 * @var array
107
	 * Поля, предназначенные для фильтрации
108
	 * @see \CAdminList::InitFilter();
109
	 */
110
	protected $arFilterFields = array();
111
	/**
112
	 * Список полей, для которых доступна фильтрация
113
	 * @var array
114
	 * @see \CAdminFilter::__construct();
115
	 */
116
	protected $arFilterOpts = array();
117
	/**
118
	 * @var \CAdminList
119
	 */
120
	protected $list;
121
	/**
122
	 * @var string
123
	 * Префикс таблицы. Нужен, чтобы обеспечить уникальность относительно других админ. интерфейсов.
124
	 * Без его добавления к конструктору таблицы повычается вероятность, что возникнет конфликт с таблицей из другого
125
	 * административного интерфейса, в результате чего неправильно будет работать паджинация, фильтрация. Вероятны
126
	 * ошибки запросов к БД.
127
	 */
128
	static protected $tablePrefix = "digitalwand_admin_helper_";
129
	/**
130
	 * @var array
131
	 * @see \CAdminList::AddGroupActionTable()
132
	 */
133
	protected $groupActionsParams = array();
134
	/**
135
	 * Текущие параметры пагинации,
136
	 * требуются для составления смешанного списка разделов и элементов
137
	 * @var array
138
	 */
139
	protected $navParams = array();
140
	/**
141
	 * Количество элементов смешанном списке
142
	 * @see AdminListHelper::CustomNavStart
143
	 * @var int
144
	 */
145
	protected $totalRowsCount = 0;
146
	/**
147
	 * Массив для слияния столбцов элементов и разделов
148
	 * @var array
149
	 */
150
	protected $tableColumnsMap = array();
151
	/**
152
	 * @var string
153
	 * HTML верхней части таблицы
154
	 * @api
155
	 */
156
	public $prologHtml;
157
158
	/**
159
	 * @var string
160
	 * HTML нижней части таблицы
161
	 * @api
162
	 */
163
	public $epilogHtml;
164
165
	/**
166
	 * Производится инициализация переменных, обработка запросов на редактирование
167
	 *
168
	 * @param array $fields
169
	 * @param bool $isPopup
170
	 * @throws \Bitrix\Main\ArgumentException
171
	 */
172
	public function __construct(array $fields, $isPopup = false)
0 ignored issues
show
Coding Style introduced by
__construct 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...
Coding Style introduced by
__construct uses the super-global variable $_GET 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...
173
	{
174
		$this->isPopup = $isPopup;
175
176
		if ($this->isPopup) {
177
			$this->fieldPopupResultName = preg_replace("/[^a-zA-Z0-9_:\\[\\]]/", "", $_REQUEST['n']);
178
			$this->fieldPopupResultIndex = preg_replace("/[^a-zA-Z0-9_:]/", "", $_REQUEST['k']);
179
			$this->fieldPopupResultElTitle = $_REQUEST['eltitle'];
180
		}
181
182
		parent::__construct($fields);
183
184
		$this->restoreLastGetQuery();
185
		$this->prepareAdminVariables();
186
187
		$className = static::getModel();
188
		$oSort = $this->initSortingParameters(Context::getCurrent()->getRequest());
189
		$this->list = new \CAdminList($this->getListTableID(), $oSort);
190
		$this->list->InitFilter($this->arFilterFields);
191
192
		if ($this->list->EditAction() AND $this->hasWriteRights()) {
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...
193
			global $FIELDS;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
194
			foreach ($FIELDS as $id => $fields) {
195
				if (!$this->list->IsUpdated($id)) {
196
					continue;
197
				}
198
				$this->editAction($id, $fields);
199
			}
200
		}
201
		if ($IDs = $this->list->GroupAction() AND $this->hasWriteRights()) {
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...
202
			if ($_REQUEST['action_target'] == 'selected') {
203
				$this->setContext(AdminListHelper::OP_GROUP_ACTION);
204
				$IDs = array();
205
206
				//Текущий фильтр должен быть модифицирован виждтами
207
				//для соответствия результатов фильтрации тому, что видит пользователь в интерфейсе.
208
				$raw = array(
209
					'SELECT' => $this->pk(),
210
					'FILTER' => $this->arFilter,
211
					'SORT' => array()
212
				);
213
214
				foreach ($this->fields as $code => $settings) {
215
					$widget = $this->createWidgetForField($code);
216
					$widget->changeGetListOptions($this->arFilter, $raw['SELECT'], $raw['SORT'], $raw);
0 ignored issues
show
Documentation introduced by
$raw['SELECT'] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
217
				}
218
219
				$res = $className::getList(array(
220
					'filter' => $this->arFilter,
221
					'select' => array($this->pk()),
222
				));
223
224
				while ($el = $res->Fetch()) {
225
					$IDs[] = $el[$this->pk()];
226
				}
227
			}
228
229
			$filteredIDs = array();
230
231
			foreach ($IDs as $id) {
232
				if (strlen($id) <= 0) {
233
					continue;
234
				}
235
				$filteredIDs[] = IntVal($id);
236
			}
237
			$this->groupActions($IDs, $_REQUEST['action']);
238
		}
239
240
		if (isset($_REQUEST['action']) || isset($_REQUEST['action_button']) && count($this->getErrors()) == 0) {
241
			$listHelperClass = $this->getHelperClass(AdminListHelper::className());
242
			$id = isset($_GET['ID']) ? $_GET['ID'] : null;
243
			$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : $_REQUEST['action_button'];
244
			if ($action != 'edit' && $_REQUEST['cancel'] != 'Y') {
245
				$params = $_GET;
246
				unset($params['action']);
247
				unset($params['action_button']);
248
				$this->customActions($action, $id);
0 ignored issues
show
Unused Code introduced by
The call to the method DigitalWand\AdminHelper\...Helper::customActions() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
249
				LocalRedirect($listHelperClass::getUrl($params));
250
			}
251
		}
252
253
		if ($this->isPopup()) {
254
			$this->genPopupActionJS();
255
		}
256
257
		// Получаем параметры навигации
258
		$navUniqSettings = array('sNavID' => $this->getListTableID());
259
		$this->navParams = array(
260
			'nPageSize' => \CAdminResult::GetNavSize($this->getListTableID()),
261
			'navParams' => \CAdminResult::GetNavParams($navUniqSettings)
262
		);
263
	}
264
265
	/**
266
	 * Инициализирует параметры сортировки на основании запроса
267
	 * @return \CAdminSorting
268
	 */
269
	protected function initSortingParameters(HttpRequest $request)
270
	{
271
		$sortByParameter = 'by';
272
		$sortOrderParameter = 'order';
273
274
		$sortBy = $request->get($sortByParameter);
275
		$sortBy = $sortBy ?: static::pk();
276
277
		$sortOrder = $request->get($sortOrderParameter);
278
		$sortOrder = $sortOrder ?: 'desc';
279
280
		return new \CAdminSorting($this->getListTableID(), $sortBy, $sortOrder, $sortByParameter, $sortOrderParameter);
281
	}
282
283
	/**
284
	 * Подготавливает переменные, используемые для инициализации списка.
285
	 *
286
	 * - добавляет поля в список фильтра только если FILTER не задано false по умолчанию для виджета и поле не является
287
	 * полем связи сущностью разделов
288
	 */
289
	protected function prepareAdminVariables()
0 ignored issues
show
Coding Style introduced by
prepareAdminVariables 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...
Coding Style introduced by
prepareAdminVariables uses the super-global variable $_GET 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...
290
	{
291
		$this->arHeader = array();
292
		$this->arFilter = array();
293
		$this->arFilterFields = array();
294
		$arFilter = array();
295
		$this->filterTypes = array();
296
		$this->arFilterOpts = array();
297
298
		$sectionField = static::getSectionField();
299
300
		foreach ($this->fields as $code => $settings) {
301
			$widget = $this->createWidgetForField($code);
302
303
			if (
304
				($sectionField != $code && $widget->getSettings('FILTER') !==false)
305
				&&
306
				((isset($settings['FILTER']) AND $settings['FILTER'] != false) OR !isset($settings['FILTER']))
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...
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...
307
			) {
308
309
				$this->setContext(AdminListHelper::OP_ADMIN_VARIABLES_FILTER);
310
				$filterVarName = 'find_' . $code;
311
				$this->arFilterFields[] = $filterVarName;
312
				$filterType = '';
313
314
				if (is_string($settings['FILTER'])) {
315
					$filterType = $settings['FILTER'];
316
				}
317
318
				if (isset($_REQUEST[$filterVarName])
319
					AND !isset($_REQUEST['del_filter'])
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...
320
					AND $_REQUEST['del_filter'] != 'Y'
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...
321
				) {
322
					$arFilter[$filterType . $code] = $_REQUEST[$filterVarName];
323
					$this->filterTypes[$code] = $filterType;
324
				}
325
326
				$this->arFilterOpts[$code] = $widget->getSettings('TITLE');
327
			}
328
329
			if (!isset($settings['LIST']) || $settings['LIST'] === true) {
330
				$this->setContext(AdminListHelper::OP_ADMIN_VARIABLES_HEADER);
331
				$mergedColumn = false;
332
				// проверяем есть ли столбец раздела с таким названием
333
				if ($widget->getSettings('LIST_TITLE')) {
334
					$sectionHeader = $this->getSectionsHeader();
335
					foreach ($sectionHeader as $sectionColumn) {
336
						if ($sectionColumn['content'] == $widget->getSettings('LIST_TITLE')) {
337
							// добавляем столбец элементов в карту столбцов
338
							$this->tableColumnsMap[$code] = $sectionColumn['id'];
339
							$mergedColumn = true;
340
							break;
341
						}
342
					}
343
				}
344
				if (!$mergedColumn) {
345
					$this->arHeader[] = array(
346
						"id" => $code,
347
						"content" => $widget->getSettings('LIST_TITLE') ? $widget->getSettings('LIST_TITLE') : $widget->getSettings('TITLE'),
348
						"sort" => $code,
349
						"default" => !isset($settings['HEADER']) || $settings['HEADER'] === true,
350
						'admin_list_helper_sort' => $widget->getSettings('LIST_COLUMN_SORT') ? $widget->getSettings('LIST_COLUMN_SORT') : 100
351
					);
352
				}
353
			}
354
		}
355
356
		if ($this->checkFilter($arFilter)) {
357
			$this->arFilter = $arFilter;
358
		}
359
360
		if (static::getHelperClass(AdminSectionEditHelper::className())) {
361
			$this->arFilter[static::getSectionField()] = $_GET['ID'];
362
		}
363
	}
364
365
	/**
366
	 * Возвращает список столбцов для разделов
367
	 * @return array
368
	 */
369
	public function getSectionsHeader()
370
	{
371
		$arSectionsHeaders = array();
372
		$sectionHelper = static::getHelperClass(AdminSectionEditHelper::className());
373
		$sectionsInterfaceSettings = static::getInterfaceSettings($sectionHelper::getViewName());
374
		$this->sectionFields = $sectionsInterfaceSettings['FIELDS'];
375
376
		foreach ($sectionsInterfaceSettings['FIELDS'] as $code => $settings) {
377
378
			if (!isset($settings['LIST']) || $settings['LIST'] === true) {
379
				$arSectionsHeaders[] = array(
380
					"id" => $code,
381
					"content" => isset($settings['LIST_TITLE']) ? $settings['LIST_TITLE'] : $settings['TITLE'],
382
					"sort" => $code,
383
					"default" => !isset($settings['HEADER']) || $settings['HEADER'] === true,
384
					'admin_list_helper_sort' => isset($settings['LIST_COLUMN_SORT']) ? $settings['LIST_COLUMN_SORT'] : 100
385
				);
386
			}
387
			unset($settings['WIDGET']);
388
389
			foreach ($settings as $c => $v) {
390
				$sectionsInterfaceSettings['FIELDS'][$code]['WIDGET']->setSetting($c, $v);
391
			}
392
		}
393
394
		return $arSectionsHeaders;
395
	}
396
397
	/**
398
	 * Производит проверку корректности данных (в массиве $_REQUEST), переданных в фильтр
399
	 * @TODO: нужно сделать вывод сообщений об ошибке фильтрации.
400
	 * @param $arFilter
401
	 * @return bool
402
	 */
403
	protected function checkFilter($arFilter)
404
	{
405
		$this->setContext(AdminListHelper::OP_CHECK_FILTER);
406
		$filterValidationErrors = array();
407
		foreach ($this->filterTypes as $code => $type) {
408
			$widget = $this->createWidgetForField($code);
409
			$value = $arFilter[$type . $code];
410
			if (!$widget->checkFilter($type, $value)) {
411
				$filterValidationErrors = array_merge($filterValidationErrors,
412
					$widget->getValidationErrors());
413
			}
414
		}
415
416
		return empty($filterValidationErrors);
417
	}
418
419
	/**
420
	 * Подготавливает массив с настройками контекстного меню. По-умолчанию добавлена кнопка "создать элемент".
421
	 *
422
	 * @see $contextMenu
423
	 *
424
	 * @api
425
	 */
426
	protected function getContextMenu()
0 ignored issues
show
Coding Style introduced by
getContextMenu uses the super-global variable $_GET 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...
427
	{
428
		$contextMenu = array();
429
		$sectionEditHelper = static::getHelperClass(AdminSectionEditHelper::className());
430
		if ($sectionEditHelper) {
431
			$this->additionalUrlParams['SECTION_ID'] = $_GET['ID'];
432
		}
433
434
		/**
435
		 * Если задан для разделов добавляем кнопку создать раздел и
436
		 * кнопку на уровень вверх если это не корневой раздел
437
		 */
438
		if ($sectionEditHelper && isset($_GET['ID'])) {
439
			if ($_GET['ID']) {
440
				$params = $this->additionalUrlParams;
441
				$sectionModel = $sectionEditHelper::getModel();
442
				$sectionField = $sectionEditHelper::getSectionField();
443
				$section = $sectionModel::getById(
444
					$this->getCommonPrimaryFilterById($sectionModel, null, $_GET['ID'])
445
				)->Fetch();
446
				if ($this->isPopup()) {
447
					$params = array_merge($_GET);
448
				}
449
				if ($section[$sectionField]) {
450
					$params['ID'] = $section[$sectionField];
451
				}
452
				else {
453
					unset($params['ID']);
454
				}
455
				unset($params['SECTION_ID']);
456
				$contextMenu[] = $this->getButton('LIST_SECTION_UP', array(
457
					'LINK' => static::getUrl($params),
458
					'ICON' => 'btn_list'
459
				));
460
			}
461
		}
462
463
		/**
464
		 * Добавляем кнопку создать элемент и создать раздел
465
		 */
466
		if (!$this->isPopup() && $this->hasWriteRights()) {
467
			$editHelperClass = static::getHelperClass(AdminEditHelper::className());
468 View Code Duplication
			if ($editHelperClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
469
				$contextMenu[] = $this->getButton('LIST_CREATE_NEW', array(
470
					'LINK' => $editHelperClass::getUrl($this->additionalUrlParams),
471
					'ICON' => 'btn_new'
472
				));
473
			}
474
			$sectionsHelperClass = static::getHelperClass(AdminSectionEditHelper::className());
475 View Code Duplication
			if ($sectionsHelperClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
476
				$contextMenu[] = $this->getButton('LIST_CREATE_NEW_SECTION', array(
477
					'LINK' => $sectionsHelperClass::getUrl($this->additionalUrlParams),
478
					'ICON' => 'btn_new'
479
				));
480
			}
481
		}
482
483
		return $contextMenu;
484
	}
485
486
	/**
487
	 * Возвращает массив с настройками групповых действий над списком.
488
	 *
489
	 * @return array
490
	 *
491
	 * @api
492
	 */
493
	protected function getGroupActions()
494
	{
495
		$result = array();
496
497
		if (!$this->isPopup()) {
498
			if ($this->hasDeleteRights()) {
499
				$result = array('delete' => Loc::getMessage("DIGITALWAND_ADMIN_HELPER_LIST_DELETE"));
500
			}
501
		}
502
503
		return $result;
504
	}
505
506
	/**
507
	 * Обработчик групповых операций. По-умолчанию прописаны операции активации / деактивации и удаления.
508
	 *
509
	 * @param array $IDs
510
	 * @param string $action
511
	 *
512
	 * @api
513
	 */
514
	protected function groupActions($IDs, $action)
0 ignored issues
show
Coding Style introduced by
groupActions 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...
Coding Style introduced by
groupActions uses the super-global variable $_GET 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...
515
	{
516
		$sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());
517
		$listHelperClass = $this->getHelperClass(AdminListHelper::className());
518
519
		$className = static::getModel();
520
		if (isset($_REQUEST['model'])) {
521
			$className = $_REQUEST['model'];
522
		}
523
524 View Code Duplication
		if ($sectionEditHelperClass && !isset($_REQUEST['model-section'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
525
			$sectionClassName = $sectionEditHelperClass::getModel();
526
		}
527
		else {
528
			$sectionClassName = $_REQUEST['model-section'];
529
		}
530
531
		if ($action == 'delete') {
532
			if ($this->hasDeleteRights()) {
533
				$complexPrimaryKey = is_array($className::getEntity()->getPrimary());
534
				if ($complexPrimaryKey) {
535
					$IDs = $this->getIds();
536
				}
537
538
				// ищем правильный урл для перехода
539
				if (!empty($IDs[0])) {
540
541
					$id = $complexPrimaryKey ? $IDs[0][$this->pk()] : $IDs[0];
542
					$model = $className;
543
544
					if (strpos($id, 's') === 0) {
545
						$model = $sectionClassName;
546
						$listHelper = $this->getHelperClass(AdminSectionListHelper::className());
547
						if (!$listHelper) {
548
							$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SECTION_HELPER_NOT_FOUND'));
549
							unset($_GET['ID']);
550
							return;
551
						}
552
						$id = substr($id, 1);
553
					} else {
554
						$listHelper = $listHelperClass;
555
					}
556
557
					if ($listHelper) {
558
						$id = $this->getCommonPrimaryFilterById($model, null, $id);
559
						$element = $model::getById($id)->Fetch();
560
						$sectionField = $listHelper::getSectionField();
561 View Code Duplication
						if ($element[$sectionField]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
562
							$_GET[$this->pk()] = $element[$sectionField];
563
						} else {
564
							unset($_GET['ID']);
565
						}
566
					}
567
				}
568
569
				foreach ($IDs as $id) {
570
					$model = $className;
571
					if (strpos($id[$this->pk()], 's') === 0) {
572
						$model = $sectionClassName;
573
						$id[$this->pk()] = substr($id[$this->pk()], 1);
574
					}
575
					/** @var EntityManager $entityManager */
576
					$entityManager = new static::$entityManager($model, empty($this->data) ? [] : $this->data, $id,
0 ignored issues
show
Bug introduced by
The property data does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
577
						$this);
578
					$result = $entityManager->delete();
579
					$this->addNotes($entityManager->getNotes());
580
					if (!$result->isSuccess()) {
581
						$this->addErrors($result->getErrorMessages());
582
						break;
583
					}
584
				}
585
			}
586
			else {
587
				$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_FORBIDDEN'));
588
			}
589
		}
590
591
		if ($action == 'delete-section') {
592
			if ($this->hasDeleteRights()) {
593
594
				// ищем правильный урл для перехода
595
				if (!empty($IDs[0])) {
596
					$id = $this->getCommonPrimaryFilterById($sectionClassName, null, $IDs[0]);
597
					$sectionListHelperClass = $this->getHelperClass(AdminSectionListHelper::className());
598
					if ($sectionListHelperClass) {
599
						$element = $sectionClassName::getById($id)->Fetch();
600
						$sectionField = $sectionListHelperClass::getSectionField();
601 View Code Duplication
						if ($element[$sectionField]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
602
							$_GET[$this->pk()] = $element[$sectionField];
603
						} else {
604
							unset($_GET['ID']);
605
						}
606
					} else {
607
						unset($_GET['ID']);
608
						$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SECTION_HELPER_NOT_FOUND'));
609
						return;
610
					}
611
				}
612
613
				foreach ($IDs as $id) {
614
					$id = $this->getCommonPrimaryFilterById($sectionClassName, null, $id);
615
					$entityManager = new static::$entityManager($sectionClassName, [], $id, $this);
616
					$result = $entityManager->delete();
617
					$this->addNotes($entityManager->getNotes());
618
					if(!$result->isSuccess()){
619
						$this->addErrors($result->getErrorMessages());
620
						break;
621
					}
622
				}
623
			}
624
			else {
625
				$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_FORBIDDEN'));
626
			}
627
		}
628
	}
629
630
	/**
631
	 * Сохранение полей для отной записи, отредактированной в списке.
632
	 * Этапы:
633
	 * <ul>
634
	 * <li> Выборка элемента по ID, чтобы удостовериться, что он существует. В противном случае  возвращается
635
	 * ошибка</li>
636
	 * <li> Создание виджета для каждой ячейки, валидация значений поля</li>
637
	 * <li> TODO: вывод ошибок валидации</li>
638
	 * <li> Сохранение записи</li>
639
	 * <li> Вывод ошибок сохранения, если таковые появились</li>
640
	 * <li> Модификация данных сроки виджетами.</li>
641
	 * </ul>
642
	 *
643
	 * @param int $id ID записи в БД
644
	 * @param array $fields Поля с изменениями
645
	 *
646
	 * @see HelperWidget::processEditAction();
647
	 * @see HelperWidget::processAfterSaveAction();
648
	 */
649
	protected function editAction($id, $fields)
0 ignored issues
show
Coding Style introduced by
editAction 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...
650
	{
651
		$this->setContext(AdminListHelper::OP_EDIT_ACTION);
652
		if(strpos($id, 's')===0){ // для раделов другой класс модели
653
			$editHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());
654
			$sectionsInterfaceSettings = static::getInterfaceSettings($editHelperClass::getViewName());
655
			$className = $editHelperClass::getModel();
656
			$id = str_replace('s','',$id);
657
		}else{
658
			$className = static::getModel();
659
			$sectionsInterfaceSettings = false;
660
		}
661
662
		$idForLog = $id;
663
		$complexPrimaryKey = is_array($className::getEntity()->getPrimary());
664
		if ($complexPrimaryKey) {
665
			$oldRequest = $_REQUEST;
666
			$_REQUEST = [$this->pk() => $id];
667
			$id = $this->getCommonPrimaryFilterById($className, null, $id);
668
			$idForLog = json_encode($id);
669
			$_REQUEST = $oldRequest;
670
		}
671
672
		$el = $className::getById($id);
673
		if ($el->getSelectedRowsCount() == 0) {
674
			$this->list->AddGroupError(Loc::getMessage("MAIN_ADMIN_SAVE_ERROR"), $idForLog);
675
			return;
676
		}
677
678
		// замена кодов для столбцов элементов соединенных со столбцами разделов
679
		if($sectionsInterfaceSettings==false){
680
			$tableColumnsMap = array_flip($this->tableColumnsMap);
681
			$replacedFields = array();
682
			foreach($fields as $key => $value){
683
				if(!empty($tableColumnsMap[$key])) {
684
					$key = $tableColumnsMap[$key];
685
				}
686
				$replacedFields[$key] = $value;
687
			}
688
			$fields = $replacedFields;
689
		}
690
691
		$allWidgets = array();
692
		foreach ($fields as $key => $value) {
693
			if($sectionsInterfaceSettings!==false){ // для разделов свои виджеты
694
				$widget = $sectionsInterfaceSettings['FIELDS'][$key]['WIDGET'];
695
			}else{
696
				$widget = $this->createWidgetForField($key, $fields); // для элементов свои
697
			}
698
699
			$widget->processEditAction();
700
			$this->validationErrors = array_merge($this->validationErrors, $widget->getValidationErrors());
701
			$allWidgets[] = $widget;
702
		}
703
		//FIXME: может, надо добавить вывод ошибок ДО сохранения?..
704
		$this->addErrors($this->validationErrors);
705
706
		$result = $className::update($id, $fields);
707
		$errors = $result->getErrorMessages();
708
		if (empty($this->validationErrors) AND !empty($errors)) {
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...
709
			$fieldList = implode("\n", $errors);
710
			$this->list->AddGroupError(Loc::getMessage("MAIN_ADMIN_SAVE_ERROR") . " " . $fieldList, $idForLog);
711
		}
712
713
		if (!empty($errors)) {
714
			foreach ($allWidgets as $widget) {
715
				/** @var \DigitalWand\AdminHelper\Widget\HelperWidget $widget */
716
				$widget->setData($fields);
717
				$widget->processAfterSaveAction();
718
			}
719
		}
720
	}
721
722
	/**
723
	 * Является ли список всплывающим окном для выбора элементов из списка.
724
	 * В этой версии не должно быть операций удаления/перехода к редактированию.
725
	 *
726
	 * @return boolean
727
	 */
728
	public function isPopup()
729
	{
730
		return $this->isPopup;
731
	}
732
733
	/**
734
	 * Функция определяет js-функцию для двойонго клика по строке.
735
	 * Вызывается в том случае, если окно открыто в режиме попапа.
736
	 *
737
	 * @api
738
	 */
739
	protected function genPopupActionJS()
740
	{
741
		$this->popupClickFunctionCode = '<script>
742
			function ' . $this->popupClickFunctionName . '(data){
743
				var input = window.opener.document.getElementById("' . $this->fieldPopupResultName . '[' . $this->fieldPopupResultIndex . ']");
744
				if(!input)
745
					input = window.opener.document.getElementById("' . $this->fieldPopupResultName . '");
746
				if(input)
747
				{
748
					input.value = data.ID;
749
					if (window.opener.BX)
750
						window.opener.BX.fireEvent(input, "change");
751
				}
752
				var span = window.opener.document.getElementById("sp_' . md5($this->fieldPopupResultName) . '_' . $this->fieldPopupResultIndex . '");
753
				if(!span)
754
					span = window.opener.document.getElementById("sp_' . $this->fieldPopupResultName . '");
755
				if(!span)
756
					span = window.opener.document.getElementById("' . $this->fieldPopupResultName . '_link");
757
				if(span)
758
					span.innerHTML = data["' . $this->fieldPopupResultElTitle . '"];
759
				window.close();
760
			}
761
		</script>';
762
	}
763
764
	/**
765
	 * Основной цикл отображения списка. Этапы:
766
	 * <ul>
767
	 * <li> Вывод заголовков страницы </li>
768
	 * <li> Определение списка видимых колонок и колонок, участвующих в выборке. </li>
769
	 * <li> Создание виджета для каждого поля выборки </li>
770
	 * <li> Модификация параметров запроса каждым из виджетов </li>
771
	 * <li> Выборка данных </li>
772
	 * <li> Вывод строк таблицы. Во время итерации по строкам возможна модификация данных строки. </li>
773
	 * <li> Отрисовка футера таблицы, добавление контекстного меню </li>
774
	 * </ul>
775
	 *
776
	 * @param array $sort Настройки сортировки.
777
	 *
778
	 * @see AdminListHelper::getList();
779
	 * @see AdminListHelper::getMixedData();
780
	 * @see AdminListHelper::modifyRowData();
781
	 * @see AdminListHelper::addRowCell();
782
	 * @see AdminListHelper::addRow();
783
	 * @see HelperWidget::changeGetListOptions();
784
	 */
785
	public function buildList($sort)
0 ignored issues
show
Coding Style introduced by
buildList uses the super-global variable $_GET 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...
786
	{
787
		$this->setContext(AdminListHelper::OP_GET_DATA_BEFORE);
788
789
		$headers = $this->arHeader;
790
791
		$sectionEditHelper = static::getHelperClass(AdminSectionEditHelper::className());
792
793
		if ($sectionEditHelper) { // если есть реализация класса AdminSectionEditHelper, значит используются разделы
794
			$sectionHeaders = $this->getSectionsHeader();
795
			foreach ($sectionHeaders as $sectionHeader) {
796
				$found = false;
797
				foreach ($headers as $i => $elementHeader) {
798
					if ($sectionHeader['content'] == $elementHeader['content'] || $sectionHeader['id'] == $elementHeader['id']) {
799
						if (!$elementHeader['default'] && $sectionHeader['default']) {
800
							$headers[$i] = $sectionHeader;
801
						} else {
802
							$found = true;	
803
						}
804
						break;
805
					}
806
				}
807
				if (!$found) {
808
					$headers[] = $sectionHeader;
809
				}
810
			}
811
		}
812
813
		// сортировка столбцов с сохранением исходной позиции в
814
		// массиве для развнозначных элементов
815
		// массив $headers модифицируется
816
		$this->mergeSortHeader($headers);
817
818
		$this->list->AddHeaders($headers);
819
		$visibleColumns = $this->list->GetVisibleHeaderColumns();
820
821
		$modelClass = $this->getModel();
822
		$elementFields = array_keys($modelClass::getEntity()->getFields());
823
824
		if ($sectionEditHelper) {
825
			$sectionsVisibleColumns = array();
826
			foreach ($visibleColumns as $k => $v) {
827
				if (isset($this->sectionFields[$v])) {
828
					if(!in_array($v, $elementFields)){
829
						unset($visibleColumns[$k]);
830
					}
831
					if (!isset($this->sectionFields[$v]['LIST']) || $this->sectionFields[$v]['LIST'] !== false) {
832
						$sectionsVisibleColumns[] = $v;
833
					}
834
				}
835
			}
836
			$visibleColumns = array_values($visibleColumns);
837
			$visibleColumns = array_merge($visibleColumns, array_keys($this->tableColumnsMap));
838
		}
839
840
		$className = static::getModel();
841
		$visibleColumns[] = $this->pk();
842
		$sectionsVisibleColumns[] = $this->sectionPk();
0 ignored issues
show
Bug introduced by
The variable $sectionsVisibleColumns does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
843
844
		$raw = array(
845
			'SELECT' => $visibleColumns,
846
			'FILTER' => $this->arFilter,
847
			'SORT' => $sort
848
		);
849
850
		foreach ($this->fields as $name => $settings) {
851
			$key = array_search($name, $visibleColumns);
852
			if ((isset($settings['VIRTUAL']) AND $settings['VIRTUAL'] == true)) {
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...
853
				unset($visibleColumns[$key]);
854
				unset($this->arFilter[$name]);
855
				unset($sort[$name]);
856
			}
857 View Code Duplication
			if (isset($settings['LIST']) && $settings['LIST'] === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
858
				unset($visibleColumns[$key]);
859
			}
860 View Code Duplication
			if (isset($settings['FORCE_SELECT']) AND $settings['FORCE_SELECT'] == true) {
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...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
861
				$visibleColumns[] = $name;
862
			}
863
		}
864
865
		$visibleColumns = array_unique($visibleColumns);
866
		$sectionsVisibleColumns = array_unique($sectionsVisibleColumns);
867
868
		// Поля для селекта (перевернутый массив)
869
		$listSelect = array_flip($visibleColumns);
870
		foreach ($this->fields as $code => $settings) {
871
			$widget = $this->createWidgetForField($code);
872
			$widget->changeGetListOptions($this->arFilter, $visibleColumns, $sort, $raw);
873
			// Множественные поля не должны быть в селекте
874
			if (!empty($settings['MULTIPLE'])) {
875
				unset($listSelect[$code]);
876
			}
877
		}
878
		// Поля для селекта (множественные поля отфильтрованы)
879
		$listSelect = array_flip($listSelect);
880
881
		if ($sectionEditHelper) // Вывод разделов и элементов в одном списке
882
		{
883
			$mixedData = $this->getMixedData($sectionsVisibleColumns, $visibleColumns, $sort, $raw);
884
			$res = new \CDbResult;
885
			$res->InitFromArray($mixedData);
886
			$res = new \CAdminResult($res, $this->getListTableID());
887
			$res->nSelectedCount = $this->totalRowsCount;
888
			// используем кастомный NavStart что бы определить правильное количество страниц и элементов в списке
889
			$this->customNavStart($res);
890
			$this->list->NavText($res->GetNavPrint(Loc::getMessage("PAGES")));
891
			while ($data = $res->NavNext(false)) {
892
				$this->modifyRowData($data);
893
				if ($data['IS_SECTION']) // для разделов своя обработка
894
				{
895
					list($link, $name) = $this->getRow($data, $this->getHelperClass(AdminSectionEditHelper::className()));
896
					$row = $this->list->AddRow('s' . $data[$this->pk()], $data, $link, $name);
897
					foreach ($this->sectionFields as $code => $settings) {
898
						if (in_array($code, $sectionsVisibleColumns)) {
899
							$this->addRowSectionCell($row, $code, $data);
900
						}
901
					}
902
					$row->AddActions($this->getRowActions($data, true));
903
				}
904
				else // для элементов своя
905
				{
906
					$this->modifyRowData($data);
907
					list($link, $name) = $this->getRow($data);
908
					// объединение полей элемента с полями раздела
909
					foreach ($this->tableColumnsMap as $elementCode => $sectionCode) {
910
						if (isset($data[$elementCode])) {
911
							$data[$sectionCode] = $data[$elementCode];
912
						}
913
					}
914
					$row = $this->list->AddRow($data[$this->pk()], $data, $link, $name);
915
					foreach ($this->fields as $code => $settings) {
916
						if(in_array($code, $listSelect)) {
917
							$this->addRowCell($row, $code, $data,
918
							isset($this->tableColumnsMap[$code]) ? $this->tableColumnsMap[$code] : false);
919
						}
920
					}
921
					$row->AddActions($this->getRowActions($data));
922
				}
923
			}
924
		}
925
		else // Обычный вывод элементов без использования разделов
926
		{
927
			$this->totalRowsCount = $className::getCount($this->getElementsFilter($this->arFilter));
928
			$res = $this->getData($className, $this->arFilter, $listSelect, $sort, $raw);
0 ignored issues
show
Bug introduced by
It seems like $className defined by static::getModel() on line 840 can be null; however, DigitalWand\AdminHelper\...inListHelper::getData() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
929
			$res = new \CAdminResult($res, $this->getListTableID());
930
			$this->customNavStart($res);
931
			// отключаем отображение всех элементов, если установлено св-во
932
			$res->bShowAll = $this->showAll;
933
			$this->list->NavText($res->GetNavPrint(Loc::getMessage("PAGES")));
934
			while ($data = $res->NavNext(false)) {
935
				$this->modifyRowData($data);
936
				list($link, $name) = $this->getRow($data);
937
				$row = $this->list->AddRow($data[$this->pk()], $data, $link, $name);
938
				foreach ($this->fields as $code => $settings) {
939
					if(in_array($code, $listSelect)) {
940
						$this->addRowCell($row, $code, $data);
941
					}
942
				}
943
				$row->AddActions($this->getRowActions($data));
944
			}
945
		}
946
947
		$this->list->AddFooter($this->getFooter($res));
948
		$this->list->AddGroupActionTable($this->getGroupActions(), $this->groupActionsParams);
949
		$this->list->AddAdminContextMenu($this->getContextMenu(), $this->exportExcel);
950
951
		$this->list->BeginPrologContent();
952
		echo $this->prologHtml;
953
		$this->list->EndPrologContent();
954
955
		$this->list->BeginEpilogContent();
956
		echo $this->epilogHtml;
957
		$this->list->EndEpilogContent();
958
959
		// добавляем ошибки в CAdminList для режимов list и frame
960
		$errors = $this->getErrors();
961
		if(in_array($_GET['mode'], array('list','frame')) && is_array($errors)) {
962
			foreach($errors as $error) {
963
				$this->list->addGroupError($error);
964
			}
965
		}
966
967
		$this->list->CheckListMode();
968
	}
969
970
	/**
971
	 * Функция сортировки столбцов c сохранением порядка равнозначных элементов
972
	 * @param $array
973
	 */
974
	protected function mergeSortHeader(&$array)
975
	{
976
		// для сортировки нужно хотя бы 2 элемента
977
		if (count($array) < 2) return;
978
979
		// делим массив пополам
980
		$halfway = count($array) / 2;
981
		$array1 = array_slice($array, 0, $halfway);
982
		$array2 = array_slice($array, $halfway);
983
984
		// реукрсивно сортируем каждую половину
985
		$this->mergeSortHeader($array1);
986
		$this->mergeSortHeader($array2);
987
988
		// если последний элемент первой половины меньше или равен первому элементу
989
		// второй половины, то просто соединяем массивы
990
		if ($this->mergeSortHeaderCompare(end($array1), $array2[0]) < 1) {
991
			$array = array_merge($array1, $array2);
992
			return;
993
		}
994
995
		// соединяем 2 отсортированных половины в один отсортированный массив
996
		$array = array();
997
		$ptr1 = $ptr2 = 0;
998
		while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
999
			// собираем в 1 массив последовательную цепочку
1000
			// элементов из 2-х отсортированных половинок
1001
			if ($this->mergeSortHeaderCompare($array1[$ptr1], $array2[$ptr2]) < 1) {
1002
				$array[] = $array1[$ptr1++];
1003
			}
1004
			else {
1005
				$array[] = $array2[$ptr2++];
1006
			}
1007
		}
1008
1009
		// если в исходных массивах что-то осталось забираем в основной массив
1010
		while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
1011
		while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
1012
1013
		return;
1014
	}
1015
1016
	/**
1017
	 * Функция сравнения столбцов по их весу в сортировке
1018
	 * @param $a
1019
	 * @param $b
1020
	 * @return int
1021
	 */
1022
	public function mergeSortHeaderCompare($a, $b)
1023
	{
1024
		$a = $a['admin_list_helper_sort'];
1025
		$b = $b['admin_list_helper_sort'];
1026
		if ($a == $b) {
1027
			return 0;
1028
		}
1029
1030
		return ($a < $b) ? -1 : 1;
1031
	}
1032
1033
	/**
1034
	 * Получение смешанного списка из разделов и элементов.
1035
	 *
1036
	 * @param $sectionsVisibleColumns
1037
	 * @param $elementVisibleColumns
1038
	 * @param $sort
1039
	 * @param $raw
1040
	 * @return array
1041
	 */
1042
	protected function getMixedData($sectionsVisibleColumns, $elementVisibleColumns, $sort, $raw)
0 ignored issues
show
Coding Style introduced by
getMixedData uses the super-global variable $_GET 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...
Coding Style introduced by
getMixedData 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...
1043
	{
1044
		$sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());
1045
		$elementEditHelperClass = $this->getHelperClass(AdminEditHelper::className());
1046
		$sectionField = $sectionEditHelperClass::getSectionField();
1047
		$sectionId = $_GET['SECTION_ID'] ? $_GET['SECTION_ID'] : $_GET['ID'];
1048
		$returnData = array();
1049
		/**
1050
		 * @var DataManager $sectionModel
1051
		 */
1052
		$sectionModel = $sectionEditHelperClass::getModel();
1053
		$sectionFilter = array();
1054
1055
		// добавляем из фильтра те поля которые есть у разделов
1056
		foreach ($this->arFilter as $field => $value) {
1057
			$fieldName = $this->escapeFilterFieldName($field);
1058
1059
			if(!empty($this->tableColumnsMap[$fieldName])) {
1060
				$field = str_replace($fieldName, $this->tableColumnsMap[$fieldName], $field);
1061
				$fieldName = $this->tableColumnsMap[$fieldName];
1062
			}
1063
1064
			if (in_array($fieldName, $sectionsVisibleColumns)) {
1065
				$sectionFilter[$field] = $value;
1066
			}
1067
		}
1068
1069
		$sectionFilter[$sectionField] = $sectionId;
1070
1071
		$raw['SELECT'] = array_unique($raw['SELECT']);
1072
1073
		// при использовании в качестве popup окна исключаем раздел из выборке
1074
		// что бы не было возможности сделать раздел родителем самого себя
1075
		if (!empty($_REQUEST['self_id'])) {
1076
			$sectionFilter['!' . $this->sectionPk()] = $_REQUEST['self_id'];
1077
		}
1078
1079
		$sectionSort = array();
1080
		$limitData = $this->getLimits();
1081
		// добавляем к общему количеству элементов количество разделов
1082
		$this->totalRowsCount = $sectionModel::getCount($this->getSectionsFilter($sectionFilter));
1083
		foreach ($sort as $field => $direction) {
1084
			if (in_array($field, $sectionsVisibleColumns)) {
1085
				$sectionSort[$field] = $direction;
1086
			}
1087
		}
1088
		// добавляем к выборке разделы
1089
		$rsSections = $sectionModel::getList(array(
1090
			'filter' => $this->getSectionsFilter($sectionFilter),
1091
			'select' => $sectionsVisibleColumns,
1092
			'order' => $sectionSort,
1093
			'limit' => $limitData[1],
1094
			'offset' => $limitData[0],
1095
		));
1096
1097
		while ($section = $rsSections->fetch()) {
1098
			$section['IS_SECTION'] = true;
1099
			$returnData[] = $section;
1100
		}
1101
1102
		// расчитываем offset и limit для элементов
1103
		if (count($returnData) > 0) {
1104
			$elementOffset = 0;
1105
		}
1106
		else {
1107
			$elementOffset = $limitData[0] - $this->totalRowsCount;
1108
		}
1109
1110
		// для списка разделов элементы не нужны
1111
		if (static::getHelperClass(AdminSectionListHelper::className()) == static::className()) {
1112
			return $returnData;
1113
		}
1114
1115
		$elementLimit = $limitData[1] - count($returnData);
1116
		$elementModel = static::$model;
1117
		$elementFilter = $this->arFilter;
1118
		$elementFilter[$elementEditHelperClass::getSectionField()] = $_GET['ID'];
1119
		// добавляем к общему количеству элементов количество элементов
1120
		$this->totalRowsCount += $elementModel::getCount($this->getElementsFilter($elementFilter));
1121
1122
		// возвращае данные без элементов если разделы занимают всю страницу выборки
1123
		if (!empty($returnData) && $limitData[0] == 0 && $limitData[1] == $this->totalRowsCount) {
1124
			return $returnData;
1125
		}
1126
1127
		$elementSort = array();
1128
		foreach ($sort as $field => $direction) {
1129
			if (in_array($field, $elementVisibleColumns)) {
1130
				$elementSort[$field] = $direction;
1131
			}
1132
		}
1133
1134
		$elementParams = array(
1135
			'filter' => $this->getElementsFilter($elementFilter),
1136
			'select' => $elementVisibleColumns,
1137
			'order' => $elementSort,
1138
		);
1139
		if ($elementLimit > 0 && $elementOffset >= 0) {
1140
			$elementParams['limit'] = $elementLimit;
1141
			$elementParams['offset'] = $elementOffset;
1142
			// добавляем к выборке элементы
1143
			$rsSections = $elementModel::getList($elementParams);
1144
1145
			while ($element = $rsSections->fetch()) {
1146
				$element['IS_SECTION'] = false;
1147
				$returnData[] = $element;
1148
			}
1149
		}
1150
1151
		return $returnData;
1152
	}
1153
1154
	/**
1155
	 * Огранчения выборки из CAdminResult
1156
	 * @return array
1157
	 */
1158
	protected function getLimits()
1159
	{
1160
		if ($this->navParams['navParams']['SHOW_ALL']) {
1161
			return array();
1162
		}
1163
		else {
1164
			if (!intval($this->navParams['navParams']['PAGEN']) OR !isset($this->navParams['navParams']['PAGEN'])) {
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...
1165
				$this->navParams['navParams']['PAGEN'] = 1;
1166
			}
1167
			$from = $this->navParams['nPageSize'] * ((int)$this->navParams['navParams']['PAGEN'] - 1);
1168
1169
			return array($from, $this->navParams['nPageSize']);
1170
		}
1171
	}
1172
1173
	/**
1174
	 * Очищает название поля от операторов фильтра
1175
	 * @param string $fieldName названия поля из фильтра
1176
	 * @return string название поля без без операторов фильтра
1177
	 */
1178
	protected function escapeFilterFieldName($fieldName)
1179
	{
1180
		return str_replace(array('!','<', '<=', '>', '>=', '><', '=', '%'), '', $fieldName);
1181
	}
1182
1183
	/**
1184
	 * Выполняет CDBResult::NavNext с той разницей, что общее количество элементов берется не из count($arResult),
1185
	 * а из нашего параметра, полученного из SQL-запроса.
1186
	 * array_slice также не делается.
1187
	 *
1188
	 * @param \CAdminResult $res
1189
	 */
1190
	protected function customNavStart(&$res)
0 ignored issues
show
Coding Style introduced by
customNavStart uses the super-global variable $_SESSION 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...
1191
	{
1192
		$res->NavStart($this->navParams['nPageSize'],
1193
			$this->navParams['navParams']['SHOW_ALL'],
1194
			(int)$this->navParams['navParams']['PAGEN']
1195
		);
1196
		// отключаем отображение всех элементов
1197
		$res->bShowAll = $this->showAll;
1198
1199
		$res->NavRecordCount = $this->totalRowsCount;
1200
		if ($res->NavRecordCount < 1)
1201
			return;
1202
1203
		if ($res->NavShowAll)
1204
			$res->NavPageSize = $res->NavRecordCount;
1205
1206
		$res->NavPageCount = floor($res->NavRecordCount / $res->NavPageSize);
1207
		if ($res->NavRecordCount % $res->NavPageSize > 0)
1208
			$res->NavPageCount++;
1209
1210
		$res->NavPageNomer =
1211
			($res->PAGEN < 1 || $res->PAGEN > $res->NavPageCount
1212
				?
1213
				(\CPageOption::GetOptionString("main", "nav_page_in_session", "Y") != "Y"
1214
				|| $_SESSION[$res->SESS_PAGEN] < 1
1215
				|| $_SESSION[$res->SESS_PAGEN] > $res->NavPageCount
1216
					?
1217
					1
1218
					:
1219
					$_SESSION[$res->SESS_PAGEN]
1220
				)
1221
				:
1222
				$res->PAGEN
1223
			);
1224
	}
1225
1226
	/**
1227
	 * Преобразует данные строки, перед тем как добавлять их в список.
1228
	 *
1229
	 * @param $data
1230
	 *
1231
	 * @see AdminListHelper::getList()
1232
	 *
1233
	 * @api
1234
	 */
1235
	protected function modifyRowData(&$data)
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
1236
	{
1237
	}
1238
1239
	/**
1240
	 * Настройки строки таблицы.
1241
	 *
1242
	 * @param array $data Данные текущей строки БД.
1243
	 * @param bool|string $class Класс хелпера через метод getUrl которого идет получение ссылки.
1244
	 *
1245
	 * @return array Возвращает ссылку на детальную страницу и её название.
1246
	 *
1247
	 * @api
1248
	 */
1249
	protected function getRow($data, $class = false)
1250
	{
1251
		if (empty($class)) {
1252
			$class = static::getHelperClass(AdminEditHelper::className());
1253
		}
1254
		if ($this->isPopup()) {
1255
			return array();
1256
		}
1257
		else {
1258
			$query = array_merge($this->additionalUrlParams, array(
1259
				'lang' => LANGUAGE_ID,
1260
				$this->pk() => $data[$this->pk()]
1261
			));
1262
1263
			return array($class::getUrl($query));
1264
		}
1265
	}
1266
1267
	/**
1268
	 * Для каждой ячейки(раздела) таблицы создаёт виджет соответствующего типа.
1269
	 * Виджет подготавливает необходимый HTML для списка.
1270
	 *
1271
	 * @param \CAdminListRow $row
1272
	 * @param $code Сивольный код поля.
1273
	 * @param $data Данные текущей строки.
1274
	 *
1275
	 * @throws Exception
1276
	 *
1277
	 * @see HelperWidget::generateRow()
1278
	 */
1279
	protected function addRowSectionCell($row, $code, $data)
1280
	{
1281
		$sectionEditHelper = $this->getHelperClass(AdminSectionEditHelper::className());
1282 View Code Duplication
		if (!isset($this->sectionFields[$code]['WIDGET'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1283
			$error = str_replace('#CODE#', $code, 'Can\'t create widget for the code "#CODE#"');
1284
			throw new Exception($error, Exception::CODE_NO_WIDGET);
1285
		}
1286
1287
		/**
1288
		 * @var \DigitalWand\AdminHelper\Widget\HelperWidget $widget
1289
		 */
1290
		$widget = $this->sectionFields[$code]['WIDGET'];
1291
1292
		$widget->setHelper($this);
1293
		$widget->setCode($code);
1294
		$widget->setData($data);
1295
		$widget->setEntityName($sectionEditHelper::getModel());
1296
1297
		$this->setContext(AdminListHelper::OP_ADD_ROW_CELL);
1298
		$widget->generateRow($row, $data);
1299
	}
1300
1301
	/**
1302
	 * Возвращает массив со списком действий при клике правой клавишей мыши на строке таблицы
1303
	 * По-умолчанию:
1304
	 * <ul>
1305
	 * <li> Редактировать элемент </li>
1306
	 * <li> Удалить элемент </li>
1307
	 * <li> Если это всплывающее окно - запустить кастомную JS-функцию. </li>
1308
	 * </ul>
1309
	 *
1310
	 * @param $data Данные текущей строки.
1311
	 * @param $section Признак списка для раздела.
1312
	 *
1313
	 * @return array
1314
	 *
1315
	 * @see CAdminListRow::AddActions
1316
	 *
1317
	 * @api
1318
	 */
1319
	protected function getRowActions($data, $section = false)
1320
	{
1321
		$actions = array();
1322
1323
		if ($this->isPopup()) {
1324
			$jsData = \CUtil::PhpToJSObject($data);
1325
			$actions['select'] = array(
1326
				'ICON' => 'select',
1327
				'DEFAULT' => true,
1328
				'TEXT' => Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SELECT'),
1329
				"ACTION" => 'javascript:' . $this->popupClickFunctionName . '(' . $jsData . ')'
1330
			);
1331
		}
1332
		else {
1333
			$viewQueryString = 'module=' . static::getModule() . '&view=' . static::getViewName() . '&entity=' . static::getEntityCode();
1334
			$query = array_merge($this->additionalUrlParams,
1335
				array($this->pk() => $data[$this->pk()]));
1336
			if ($this->hasWriteRights()) {
1337
				$sectionHelperClass = static::getHelperClass(AdminSectionEditHelper::className());
1338
				$editHelperClass = static::getHelperClass(AdminEditHelper::className());
1339
1340
				$actions['edit'] = array(
1341
					'ICON' => 'edit',
1342
					'DEFAULT' => true,
1343
					'TEXT' => Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_EDIT'),
1344
					'ACTION' => $this->list->ActionRedirect($section ? $sectionHelperClass::getUrl($query) : $editHelperClass::getUrl($query))
1345
				);
1346
			}
1347
			if ($this->hasDeleteRights()) {
1348
				$actions['delete'] = array(
1349
					'ICON' => 'delete',
1350
					'TEXT' => Loc::getMessage("DIGITALWAND_ADMIN_HELPER_LIST_DELETE"),
1351
					'ACTION' => "if(confirm('" . Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_CONFIRM') . "')) " . $this->list->ActionDoGroup($data[$this->pk()],
1352
							$section ? "delete-section" : "delete", $viewQueryString)
1353
				);
1354
			}
1355
		}
1356
1357
		return $actions;
1358
	}
1359
1360
	/**
1361
	 * Для каждой ячейки таблицы создаёт виджет соответствующего типа. Виджет подготавливает необходимый HTML-код
1362
	 * для списка.
1363
	 *
1364
	 * @param \CAdminListRow $row Объект строки списка записей.
1365
	 * @param string $code Сивольный код поля.
1366
	 * @param array $data Данные текущей строки.
1367
	 * @param bool $virtualCode
1368
	 *
1369
	 * @throws Exception
1370
	 *
1371
	 * @see HelperWidget::generateRow()
1372
	 */
1373
	protected function addRowCell($row, $code, $data, $virtualCode = false)
1374
	{
1375
		$widget = $this->createWidgetForField($code, $data);
1376
		$this->setContext(AdminListHelper::OP_ADD_ROW_CELL);
1377
1378
		// устанавливаем виртуальный код ячейки, используется при слиянии столбцов
1379
		if ($virtualCode) {
1380
			$widget->setCode($virtualCode);
0 ignored issues
show
Documentation introduced by
$virtualCode is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1381
		}
1382
1383
		$widget->generateRow($row, $data);
1384
1385
		if ($virtualCode) {
1386
			$widget->setCode($code);
1387
		}
1388
	}
1389
1390
	/**
1391
	 * Производит выборку данных. Функцию стоит переопределить в случае, если необходима своя логика, и её нельзя
1392
	 * вынести в класс модели.
1393
	 *
1394
	 * @param DataManager $className
1395
	 * @param array $filter
1396
	 * @param array $select
1397
	 * @param array $sort
1398
	 * @param array $raw
1399
	 *
1400
	 * @return Result
1401
	 *
1402
	 * @api
1403
	 */
1404
	protected function getData($className, $filter, $select, $sort, $raw)
0 ignored issues
show
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...
1405
	{
1406
		$limits = $this->getLimits();
1407
		$parameters = array(
1408
			'filter' => $this->getElementsFilter($filter),
1409
			'select' => $select,
1410
			'order' => $sort,
1411
			'offset' => $limits[0],
1412
			'limit' => $limits[1],
1413
		);
1414
1415
		/** @var Result $res */
1416
		$res = $className::getList($parameters);
1417
1418
		return $res;
1419
	}
1420
1421
	/**
1422
	 * Подготавливает массив с настройками футера таблицы Bitrix
1423
	 * @param \CAdminResult $res - результат выборки данных
1424
	 * @see \CAdminList::AddFooter()
1425
	 * @return array[]
1426
	 */
1427
	protected function getFooter($res)
1428
	{
1429
		return array(
1430
			$this->getButton('MAIN_ADMIN_LIST_SELECTED', array("value" => $res->SelectedRowsCount())),
1431
			$this->getButton('MAIN_ADMIN_LIST_CHECKED', array("value" => $res->SelectedRowsCount()), array(
1432
				"counter" => true,
1433
				"value" => "0",
1434
			)),
1435
		);
1436
	}
1437
1438
	/**
1439
	 * Выводит форму фильтрации списка
1440
	 */
1441
	public function createFilterForm()
1442
	{
1443
		//нужно пробрасывать параметр popup в форму, если она является таковой
1444
		if($this->isPopup())
1445
		{
1446
			$this->additionalUrlParams['popup'] = 'Y';
1447
		}
1448
1449
		$this->setContext(AdminListHelper::OP_CREATE_FILTER_FORM);
1450
		print ' <form name="find_form" method="GET" action="' . static::getUrl($this->additionalUrlParams) . '?">';
1451
1452
		$sectionHelper = $this->getHelperClass(AdminSectionEditHelper::className());
1453
		if($sectionHelper) {
1454
			$sectionsInterfaceSettings = static::getInterfaceSettings($sectionHelper::getViewName());
1455
			foreach($this->arFilterOpts as $code => &$name) {
1456
				if(!empty($this->tableColumnsMap[$code])) {
1457
					$name = $sectionsInterfaceSettings['FIELDS'][$this->tableColumnsMap[$code]]['WIDGET']->getSettings('TITLE');
1458
				}
1459
			}
1460
		}
1461
1462
		$oFilter = new \CAdminFilter($this->getListTableID() . '_filter', $this->arFilterOpts);
1463
		$oFilter->Begin();
1464
1465
		foreach ($this->arFilterOpts as $code => $name) {
1466
			$widget = $this->createWidgetForField($code);
1467
			if($widget->getSettings('TITLE') != $this->arFilterOpts[$code]) {
1468
				$widget->setSetting('TITLE', $this->arFilterOpts[$code]);
1469
			}
1470
			$widget->showFilterHtml();
1471
		}
1472
1473
		$oFilter->Buttons(array(
1474
			"table_id" => $this->getListTableID(),
1475
			"url" => static::getUrl($this->additionalUrlParams),
1476
			"form" => "find_form",
1477
		));
1478
		$oFilter->End();
1479
1480
		print '</form>';
1481
	}
1482
1483
	/**
1484
	 * Возвращает ID таблицы, который не должен конфликтовать с ID в других разделах админки, а также нормально
1485
	 * парситься в JS
1486
	 *
1487
	 * @return string
1488
	 */
1489
	protected function getListTableID()
1490
	{
1491
		return str_replace('.', '', static::$tablePrefix . $this->table());
1492
	}
1493
1494
	/**
1495
	 * Выводит сформированный список.
1496
	 * Сохраняет обработанный GET-запрос в сессию
1497
	 */
1498
	public function show()
1499
	{
1500
		if (!$this->hasReadRights()) {
1501
			$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_ACCESS_FORBIDDEN'));
1502
			$this->showMessages();
1503
1504
			return false;
1505
		}
1506
		$this->showMessages();
1507
		$this->list->DisplayList();
1508
1509
		if ($this->isPopup()) {
1510
			print $this->popupClickFunctionCode;
1511
		}
1512
1513
		$this->saveGetQuery();
1514
	}
1515
1516
	/**
1517
	 * Сохраняет параметры запроса для поторного использования после возврата с других страниц (к примеру, после
1518
	 * перехода с детальной обратно в список - чтобы вернуться в точности в тот раздел, с которого ранее ушли)
1519
	 */
1520
	private function saveGetQuery()
0 ignored issues
show
Coding Style introduced by
saveGetQuery uses the super-global variable $_SESSION 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...
Coding Style introduced by
saveGetQuery uses the super-global variable $_GET 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...
1521
	{
1522
		$_SESSION['LAST_GET_QUERY'][get_called_class()] = $_GET;
1523
	}
1524
1525
	/**
1526
	 * Восстанавливает последний GET-запрос, если в текущем задан параметр restore_query=Y
1527
	 */
1528
	private function restoreLastGetQuery()
0 ignored issues
show
Coding Style introduced by
restoreLastGetQuery uses the super-global variable $_SESSION 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...
Coding Style introduced by
restoreLastGetQuery 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...
Coding Style introduced by
restoreLastGetQuery uses the super-global variable $_GET 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...
1529
	{
1530
		if (!isset($_SESSION['LAST_GET_QUERY'][get_called_class()])
1531
			OR !isset($_REQUEST['restore_query'])
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...
1532
			OR $_REQUEST['restore_query'] != 'Y'
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...
1533
		) {
1534
			return;
1535
		}
1536
1537
		$_GET = array_merge($_GET, $_SESSION['LAST_GET_QUERY'][get_called_class()]);
1538
		$_REQUEST = array_merge($_REQUEST, $_SESSION['LAST_GET_QUERY'][get_called_class()]);
1539
	}
1540
1541
	/**
1542
	 * @inheritdoc
1543
	 */
1544
	public static function getUrl(array $params = array())
1545
	{
1546
		return static::getViewURL(static::getViewName(), static::$listPageUrl, $params);
1547
	}
1548
1549
	/**
1550
	 * Кастомизация фильтра разделов
1551
	 * @param $filter
1552
	 * @return mixed
1553
	 */
1554
	protected function getSectionsFilter(array $filter)
1555
	{
1556
		return $filter;
1557
	}
1558
1559
	/**
1560
	 * Кастомизация фильтра элементов
1561
	 * @param $filter
1562
	 * @return mixed
1563
	 */
1564
	protected function getElementsFilter($filter)
1565
	{
1566
		return $filter;
1567
	}
1568
1569
	/**
1570
	 * Список идентификаторов для групповых операций
1571
	 *
1572
	 * @return array
1573
	 */
1574
	protected function getIds()
0 ignored issues
show
Coding Style introduced by
getIds 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...
1575
	{
1576
		$className = static::getModel();
1577
		if (isset($_REQUEST['model'])) {
1578
			$className = $_REQUEST['model'];
1579
		}
1580
1581
		$sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());
1582 View Code Duplication
		if ($sectionEditHelperClass && !isset($_REQUEST['model-section'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1583
			$sectionClassName = $sectionEditHelperClass::getModel();
1584
		}
1585
		else {
1586
			$sectionClassName = $_REQUEST['model-section'];
1587
		}
1588
1589
		if (isset($this->getPk()[$this->pk()]) && is_array($this->getPk()[$this->pk()])) {
1590
			foreach ($this->getPk()[$this->pk()] as $id) {
1591
				$class = strpos($id, 's') === 0 ? $sectionClassName : $className;
1592
				$ids[] = $this->getCommonPrimaryFilterById($class, null, $id);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$ids was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ids = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1593
			}
1594
		} else {
1595
			$ids = [$this->getPk()];
1596
		}
1597
1598
		return $ids;
0 ignored issues
show
Bug introduced by
The variable $ids does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1599
	}
1600
1601
	/**
1602
	 * Получить оставшуюся часть составного первичного ключа
1603
	 *
1604
	 * @param $className
1605
	 * @param null $sectionClassName
1606
	 * @param $id
1607
	 * @return array
1608
	 */
1609
	protected function getCommonPrimaryFilterById($className, $sectionClassName = null, $id)
1610
	{
1611
        $class = $this->getHelperClass($sectionClassName);
1612
        if (!empty($class) && strpos($id, 's') === 0) {
1613
			$primary = $sectionClassName::getEntity()->getPrimary();
1614
		} else {
1615
			$primary = $className::getEntity()->getPrimary();
1616
		}
1617
1618
		if (count($primary) === 1) {
1619
			return [$this->pk() => $id];
1620
		}
1621
1622
		$key = $this->getPk();
1623
		$key[$this->pk()] = $id;
1624
1625
		return $key;
1626
	}
1627
}