Completed
Push — hash-nonce ( 07e2e8 )
by Sam
08:52
created

GridField   F

Complexity

Total Complexity 132

Size/Duplication

Total Lines 1057
Duplicated Lines 6.24 %

Coupling/Cohesion

Components 2
Dependencies 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 66
loc 1057
rs 0.9031
wmc 132
lcom 2
cbo 20

34 Methods

Rating   Name   Duplication   Size   Complexity  
A newRowClasses() 0 21 4
A __construct() 0 20 3
A index() 0 3 1
A setModelClass() 0 5 1
B getModelClass() 0 17 5
A getConfig() 0 3 1
A setConfig() 0 5 1
A getComponents() 0 3 1
A getCastedValue() 0 22 3
A setList() 0 5 1
A getList() 0 3 1
A getManipulatedList() 0 11 3
A getState() 0 7 2
A newCell() 0 7 1
A newRow() 0 7 1
A getRowAttributes() 0 9 1
A getAttributes() 0 8 1
A getColumns() 0 11 3
B getColumnContent() 0 23 4
A addDataFields() 0 7 2
A getDataFieldValue() 0 17 4
B getColumnAttributes() 33 33 5
B getColumnMetadata() 33 33 5
A getColumnCount() 0 7 2
A buildColumnDispatch() 0 13 4
C gridFieldAlterAction() 0 53 9
A handleAlterAction() 0 18 4
D handleRequest() 0 84 23
A saveInto() 0 7 3
A getOptionalTableHeader() 0 9 2
A getOptionalTableBody() 0 9 2
A getOptionalTableFooter() 0 9 2
F FieldHolder() 0 229 26
A Field() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like GridField often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GridField, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Displays a {@link SS_List} in a grid format.
5
 *
6
 * GridField is a field that takes an SS_List and displays it in an table with rows and columns.
7
 * It reminds of the old TableFields but works with SS_List types and only loads the necessary
8
 * rows from the list.
9
 *
10
 * The minimum configuration is to pass in name and title of the field and a SS_List.
11
 *
12
 * <code>
13
 * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'));
14
 * </code>
15
 *
16
 * @see SS_List
17
 *
18
 * @package forms
19
 * @subpackage fields-gridfield
20
 * @property GridState_Data $State The gridstate of this object
21
 */
22
class GridField extends FormField {
23
	/**
24
	 * @var array
25
	 */
26
	private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
27
		'index',
28
		'gridFieldAlterAction',
29
	);
30
31
	/**
32
	 * Data source.
33
	 *
34
	 * @var SS_List
35
	 */
36
	protected $list = null;
37
38
	/**
39
	 * Class name of the DataObject that the GridField will display.
40
	 *
41
	 * Defaults to the value of $this->list->dataClass.
42
	 *
43
	 * @var string
44
	 */
45
	protected $modelClassName = '';
46
47
	/**
48
	 * Current state of the GridField.
49
	 *
50
	 * @var GridState
51
	 */
52
	protected $state = null;
53
54
	/**
55
	 * @var GridFieldConfig
56
	 */
57
	protected $config = null;
58
59
	/**
60
	 * Components list.
61
	 *
62
	 * @var array
63
	 */
64
	protected $components = array();
65
66
	/**
67
	 * Internal dispatcher for column handlers.
68
	 *
69
	 * Keys are column names and values are GridField_ColumnProvider objects.
70
	 *
71
	 * @var array
72
	 */
73
	protected $columnDispatch = null;
74
75
	/**
76
	 * Map of callbacks for custom data fields.
77
	 *
78
	 * @var array
79
	 */
80
	protected $customDataFields = array();
81
82
	/**
83
	 * @var string
84
	 */
85
	protected $name = '';
86
87
	/**
88
	 * @param string $name
89
	 * @param string $title
90
	 * @param SS_List $dataList
91
	 * @param GridFieldConfig $config
92
	 */
93
	public function __construct($name, $title = null, SS_List $dataList = null, GridFieldConfig $config = null) {
94
		parent::__construct($name, $title, null);
95
96
		$this->name = $name;
97
98
		if($dataList) {
99
			$this->setList($dataList);
100
		}
101
102
		if(!$config) {
103
			$config = GridFieldConfig_Base::create();
104
		}
105
106
		$this->setConfig($config);
107
108
		$this->config->addComponent(new GridState_Component());
109
		$this->state = new GridState($this);
110
111
		$this->addExtraClass('ss-gridfield');
112
	}
113
114
	/**
115
	 * @param SS_HTTPRequest $request
116
	 *
117
	 * @return string
118
	 */
119
	public function index($request) {
120
		return $this->gridFieldAlterAction(array(), $this->getForm(), $request);
121
	}
122
123
	/**
124
	 * Set the modelClass (data object) that this field will get it column headers from.
125
	 *
126
	 * If no $displayFields has been set, the display fields will be $summary_fields.
127
	 *
128
	 * @see GridFieldDataColumns::getDisplayFields()
129
	 *
130
	 * @param string $modelClassName
131
	 *
132
	 * @return $this
133
	 */
134
	public function setModelClass($modelClassName) {
135
		$this->modelClassName = $modelClassName;
136
137
		return $this;
138
	}
139
140
	/**
141
	 * Returns a data class that is a DataObject type that this GridField should look like.
142
	 *
143
	 * @return string
144
	 *
145
	 * @throws LogicException
146
	 */
147
	public function getModelClass() {
148
		if($this->modelClassName) {
149
			return $this->modelClassName;
150
		}
151
152
		if($this->list && $this->list->hasMethod('dataClass')) {
153
			$class = $this->list->dataClass();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SS_List as the method dataClass() does only exist in the following implementations of said interface: ArrayList, DataList, FieldList, HasManyList, ManyManyList, Member_GroupSet, PolymorphicHasManyList, RelationList, UnsavedRelationList.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
154
155
			if($class) {
156
				return $class;
157
			}
158
		}
159
160
		throw new LogicException(
161
			'GridField doesn\'t have a modelClassName, so it doesn\'t know the columns of this grid.'
162
		);
163
	}
164
165
	/**
166
	 * @return GridFieldConfig
167
	 */
168
	public function getConfig() {
169
		return $this->config;
170
	}
171
172
	/**
173
	 * @param GridFieldConfig $config
174
	 *
175
	 * @return $this
176
	 */
177
	public function setConfig(GridFieldConfig $config) {
178
		$this->config = $config;
179
180
		return $this;
181
	}
182
183
	/**
184
	 * @return ArrayList
185
	 */
186
	public function getComponents() {
187
		return $this->config->getComponents();
188
	}
189
190
	/**
191
	 * Cast an arbitrary value with the help of a $castingDefinition.
192
	 *
193
	 * @todo refactor this into GridFieldComponent
194
	 *
195
	 * @param mixed $value
196
	 * @param string|array $castingDefinition
197
	 *
198
	 * @return mixed
199
	 */
200
	public function getCastedValue($value, $castingDefinition) {
201
		$castingParams = array();
202
203
		if(is_array($castingDefinition)) {
204
			$castingParams = $castingDefinition;
205
			array_shift($castingParams);
206
			$castingDefinition = array_shift($castingDefinition);
207
		}
208
209
		if(strpos($castingDefinition, '->') === false) {
210
			$castingFieldType = $castingDefinition;
211
			$castingField = DBField::create_field($castingFieldType, $value);
212
213
			return call_user_func_array(array($castingField, 'XML'), $castingParams);
214
		}
215
216
		list($castingFieldType, $castingMethod) = explode('->', $castingDefinition);
217
218
		$castingField = DBField::create_field($castingFieldType, $value);
219
220
		return call_user_func_array(array($castingField, $castingMethod), $castingParams);
221
	}
222
223
	/**
224
	 * Set the data source.
225
	 *
226
	 * @param SS_List $list
227
	 *
228
	 * @return $this
229
	 */
230
	public function setList(SS_List $list) {
231
		$this->list = $list;
232
233
		return $this;
234
	}
235
236
	/**
237
	 * Get the data source.
238
	 *
239
	 * @return SS_List
240
	 */
241
	public function getList() {
242
		return $this->list;
243
	}
244
245
	/**
246
	 * Get the data source after applying every {@link GridField_DataManipulator} to it.
247
	 *
248
	 * @return SS_List
249
	 */
250
	public function getManipulatedList() {
251
		$list = $this->getList();
252
253
		foreach($this->getComponents() as $item) {
254
			if($item instanceof GridField_DataManipulator) {
255
				$list = $item->getManipulatedData($this, $list);
256
			}
257
		}
258
259
		return $list;
260
	}
261
262
	/**
263
	 * Get the current GridState_Data or the GridState.
264
	 *
265
	 * @param bool $getData
266
	 *
267
	 * @return GridState_Data|GridState
268
	 */
269
	public function getState($getData = true) {
270
		if($getData) {
271
			return $this->state->getData();
272
		}
273
274
		return $this->state;
275
	}
276
277
	/**
278
	 * Returns the whole gridfield rendered with all the attached components.
279
	 *
280
	 * @param array $properties
281
	 *
282
	 * @return HTMLText
283
	 */
284
	public function FieldHolder($properties = array()) {
285
		Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
286
		Requirements::css(FRAMEWORK_DIR . '/css/GridField.css');
287
288
		Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
289
		Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-ui/jquery-ui.js');
290
		Requirements::javascript(THIRDPARTY_DIR . '/json-js/json2.js');
291
		Requirements::javascript(FRAMEWORK_DIR . '/javascript/i18n.js');
292
		Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
293
		Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
294
		Requirements::javascript(FRAMEWORK_DIR . '/javascript/GridField.js');
295
296
		$columns = $this->getColumns();
297
298
		$list = $this->getManipulatedList();
299
300
		$content = array(
301
			'before' => '',
302
			'after' => '',
303
			'header' => '',
304
			'footer' => '',
305
		);
306
307
		foreach($this->getComponents() as $item) {
308
			if($item instanceof GridField_HTMLProvider) {
309
				$fragments = $item->getHTMLFragments($this);
310
311
				if($fragments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fragments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
312
					foreach($fragments as $fragmentKey => $fragmentValue) {
313
						$fragmentKey = strtolower($fragmentKey);
314
315
						if(!isset($content[$fragmentKey])) {
316
							$content[$fragmentKey] = '';
317
						}
318
319
						$content[$fragmentKey] .= $fragmentValue . "\n";
320
					}
321
				}
322
			}
323
		}
324
325
		foreach($content as $contentKey => $contentValue) {
326
			$content[$contentKey] = trim($contentValue);
327
		}
328
329
		// Replace custom fragments and check which fragments are defined. Circular dependencies
330
		// are detected by disallowing any item to be deferred more than 5 times.
331
332
		$fragmentDefined = array(
333
			'header' => true,
334
			'footer' => true,
335
			'before' => true,
336
			'after' => true,
337
		);
338
339
		reset($content);
340
341
		while(list($contentKey, $contentValue) = each($content)) {
342
			if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $contentValue, $matches)) {
343
				foreach($matches[1] as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches[1] of type string|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
344
					$fragmentName = strtolower($match);
345
					$fragmentDefined[$fragmentName] = true;
346
347
					$fragment = '';
348
349
					if(isset($content[$fragmentName])) {
350
						$fragment = $content[$fragmentName];
351
					}
352
353
					// If the fragment still has a fragment definition in it, when we should defer
354
					// this item until later.
355
356
					if(preg_match('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $fragment, $matches)) {
357
						if(isset($fragmentDeferred[$contentKey]) && $fragmentDeferred[$contentKey] > 5) {
358
							throw new LogicException(sprintf(
359
								'GridField HTML fragment "%s" and "%s" appear to have a circular dependency.',
360
								$fragmentName,
361
								$matches[1]
362
							));
363
						}
364
365
						unset($content[$contentKey]);
366
367
						$content[$contentKey] = $contentValue;
368
369
						if(!isset($fragmentDeferred[$contentKey])) {
370
							$fragmentDeferred[$contentKey] = 0;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fragmentDeferred was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fragmentDeferred = 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...
371
						}
372
373
						$fragmentDeferred[$contentKey]++;
0 ignored issues
show
Bug introduced by
The variable $fragmentDeferred 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...
374
375
						break;
376
					} else {
377
						$content[$contentKey] = preg_replace(
378
							sprintf('/\$DefineFragment\(%s\)/i', $fragmentName),
379
							$fragment,
380
							$content[$contentKey]
381
						);
382
					}
383
				}
384
			}
385
		}
386
387
		// Check for any undefined fragments, and if so throw an exception.
388
		// While we're at it, trim whitespace off the elements.
389
390
		foreach($content as $contentKey => $contentValue) {
391
			if(empty($fragmentDefined[$contentKey])) {
392
				throw new LogicException(sprintf(
393
					'GridField HTML fragment "%s" was given content, but not defined. Perhaps there is a supporting GridField component you need to add?',
394
					$contentKey
395
				));
396
			}
397
		}
398
399
		$total = count($list);
400
401
		if($total > 0) {
402
			$rows = array();
403
404
			foreach($list as $index => $record) {
405
				if($record->hasMethod('canView') && !$record->canView()) {
406
					continue;
407
				}
408
409
				$rowContent = '';
410
411
				foreach($this->getColumns() as $column) {
412
					$colContent = $this->getColumnContent($record, $column);
413
414
					// Null means this columns should be skipped altogether.
415
416
					if($colContent === null) {
417
						continue;
418
					}
419
420
					$colAttributes = $this->getColumnAttributes($record, $column);
421
422
					$rowContent .= $this->newCell(
423
						$total,
424
						$index,
425
						$record,
426
						$colAttributes,
427
						$colContent
428
					);
429
				}
430
431
				$rowAttributes = $this->getRowAttributes($total, $index, $record);
432
433
				$rows[] = $this->newRow($total, $index, $record, $rowAttributes, $rowContent);
434
			}
435
			$content['body'] = implode("\n", $rows);
436
		}
437
438
		// Display a message when the grid field is empty.
439
440
		if(empty($content['body'])) {
441
			$cell = FormField::create_tag(
442
				'td',
443
				array(
444
					'colspan' => count($columns),
445
				),
446
				_t('GridField.NoItemsFound', 'No items found')
447
			);
448
449
			$row = FormField::create_tag(
450
				'tr',
451
				array(
452
					'class' => 'ss-gridfield-item ss-gridfield-no-items',
453
				),
454
				$cell
455
			);
456
457
			$content['body'] = $row;
458
		}
459
460
		$header = $this->getOptionalTableHeader($content);
461
		$body = $this->getOptionalTableBody($content);
462
		$footer = $this->getOptionalTableFooter($content);
463
464
		$this->addExtraClass('ss-gridfield field');
465
466
		$fieldsetAttributes = array_diff_key(
467
			$this->getAttributes(),
468
			array(
469
				'value' => false,
470
				'type' => false,
471
				'name' => false,
472
			)
473
		);
474
475
		$fieldsetAttributes['data-name'] = $this->getName();
476
477
		$tableId = null;
478
479
		if($this->id) {
480
			$tableId = $this->id;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<GridField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
481
		}
482
483
		$tableAttributes = array(
484
			'id' => $tableId,
485
			'class' => 'ss-gridfield-table',
486
			'cellpadding' => '0',
487
			'cellspacing' => '0',
488
		);
489
490
		if($this->getDescription()) {
491
			$content['after'] .= FormField::create_tag(
492
				'span',
493
				array('class' => 'description'),
494
				$this->getDescription()
495
			);
496
		}
497
498
		$table = FormField::create_tag(
499
			'table',
500
			$tableAttributes,
501
			$header . "\n" . $footer . "\n" . $body
502
		);
503
504
		$field = DBField::create_field('HTMLText', FormField::create_tag(
505
			'fieldset',
506
			$fieldsetAttributes,
507
			$content['before'] . $table . $content['after']
508
		));
509
		$field->setOptions(array('shortcodes' => false));
510
511
		return $field;
512
	}
513
514
	/**
515
	 * @param int $total
516
	 * @param int $index
517
	 * @param DataObject $record
518
	 * @param array $attributes
519
	 * @param string $content
520
	 *
521
	 * @return string
522
	 */
523
	protected function newCell($total, $index, $record, $attributes, $content) {
0 ignored issues
show
Unused Code introduced by
The parameter $total is not used and could be removed.

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

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

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

Loading history...
Unused Code introduced by
The parameter $record 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...
524
		return FormField::create_tag(
525
			'td',
526
			$attributes,
527
			$content
528
		);
529
	}
530
531
	/**
532
	 * @param int $total
533
	 * @param int $index
534
	 * @param DataObject $record
535
	 * @param array $attributes
536
	 * @param string $content
537
	 *
538
	 * @return string
539
	 */
540
	protected function newRow($total, $index, $record, $attributes, $content) {
0 ignored issues
show
Unused Code introduced by
The parameter $total is not used and could be removed.

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

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

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

Loading history...
Unused Code introduced by
The parameter $record 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...
541
		return FormField::create_tag(
542
			'tr',
543
			$attributes,
544
			$content
545
		);
546
	}
547
548
	/**
549
	 * @param int $total
550
	 * @param int $index
551
	 * @param DataObject $record
552
	 *
553
	 * @return array
554
	 */
555
	protected function getRowAttributes($total, $index, $record) {
556
		$rowClasses = $this->newRowClasses($total, $index, $record);
557
558
		return array(
559
			'class' => implode(' ', $rowClasses),
560
			'data-id' => $record->ID,
561
			'data-class' => $record->ClassName,
562
		);
563
	}
564
565
	/**
566
	 * @param int $total
567
	 * @param int $index
568
	 * @param DataObject $record
569
	 *
570
	 * @return array
571
	 */
572
	protected function newRowClasses($total, $index, $record) {
573
		$classes = array('ss-gridfield-item');
574
575
		if($index == 0) {
576
			$classes[] = 'first';
577
		}
578
579
		if($index == $total - 1) {
580
			$classes[] = 'last';
581
		}
582
583
		if($index % 2) {
584
			$classes[] = 'even';
585
		} else {
586
			$classes[] = 'odd';
587
		}
588
		
589
		$this->extend('updateNewRowClasses', $classes, $total, $index, $record);
590
591
		return $classes;
592
	}
593
594
	/**
595
	 * @param array $properties
596
	 *
597
	 * @return HTMLText
598
	 */
599
	public function Field($properties = array()) {
600
		$this->extend('onBeforeRender', $this);
601
		return $this->FieldHolder($properties);
602
	}
603
604
	/**
605
	 * {@inheritdoc}
606
	 */
607
	public function getAttributes() {
608
		return array_merge(
609
			parent::getAttributes(),
610
			array(
611
				'data-url' => $this->Link(),
612
			)
613
		);
614
	}
615
616
	/**
617
	 * Get the columns of this GridField, they are provided by attached GridField_ColumnProvider.
618
	 *
619
	 * @return array
620
	 */
621
	public function getColumns() {
622
		$columns = array();
623
624
		foreach($this->getComponents() as $item) {
625
			if($item instanceof GridField_ColumnProvider) {
626
				$item->augmentColumns($this, $columns);
0 ignored issues
show
Bug introduced by
It seems like $columns defined by array() on line 622 can also be of type array; however, GridField_ColumnProvider::augmentColumns() does only seem to accept object<arary>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
627
			}
628
		}
629
630
		return $columns;
631
	}
632
633
	/**
634
	 * Get the value from a column.
635
	 *
636
	 * @param DataObject $record
637
	 * @param string $column
638
	 *
639
	 * @return string
640
	 *
641
	 * @throws InvalidArgumentException
642
	 */
643
	public function getColumnContent($record, $column) {
644
		if(!$this->columnDispatch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->columnDispatch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
645
			$this->buildColumnDispatch();
646
		}
647
648
		if(!empty($this->columnDispatch[$column])) {
649
			$content = '';
650
651
			foreach($this->columnDispatch[$column] as $handler) {
652
				/**
653
				 * @var GridField_ColumnProvider $handler
654
				 */
655
				$content .= $handler->getColumnContent($this, $record, $column);
656
			}
657
658
			return $content;
659
		} else {
660
			throw new InvalidArgumentException(sprintf(
661
				'Bad column "%s"',
662
				$column
663
			));
664
		}
665
	}
666
667
	/**
668
	 * Add additional calculated data fields to be used on this GridField
669
	 *
670
	 * @param array $fields a map of fieldname to callback. The callback will
671
	 *                      be passed the record as an argument.
672
	 */
673
	public function addDataFields($fields) {
674
		if($this->customDataFields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customDataFields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
675
			$this->customDataFields = array_merge($this->customDataFields, $fields);
676
		} else {
677
			$this->customDataFields = $fields;
678
		}
679
	}
680
681
	/**
682
	 * Get the value of a named field  on the given record.
683
	 *
684
	 * Use of this method ensures that any special rules around the data for this gridfield are
685
	 * followed.
686
	 *
687
	 * @param DataObject $record
688
	 * @param string $fieldName
689
	 *
690
	 * @return mixed
691
	 */
692
	public function getDataFieldValue($record, $fieldName) {
693
		if(isset($this->customDataFields[$fieldName])) {
694
			$callback = $this->customDataFields[$fieldName];
695
696
			return $callback($record);
697
		}
698
699
		if($record->hasMethod('relField')) {
700
			return $record->relField($fieldName);
701
		}
702
703
		if($record->hasMethod($fieldName)) {
704
			return $record->$fieldName();
705
		}
706
707
		return $record->$fieldName;
708
	}
709
710
	/**
711
	 * Get extra columns attributes used as HTML attributes.
712
	 *
713
	 * @param DataObject $record
714
	 * @param string $column
715
	 *
716
	 * @return array
717
	 *
718
	 * @throws LogicException
719
	 * @throws InvalidArgumentException
720
	 */
721 View Code Duplication
	public function getColumnAttributes($record, $column) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
722
		if(!$this->columnDispatch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->columnDispatch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
723
			$this->buildColumnDispatch();
724
		}
725
726
		if(!empty($this->columnDispatch[$column])) {
727
			$attributes = array();
728
729
			foreach($this->columnDispatch[$column] as $handler) {
730
				/**
731
				 * @var GridField_ColumnProvider $handler
732
				 */
733
				$columnAttributes = $handler->getColumnAttributes($this, $record, $column);
734
735
				if(is_array($columnAttributes)) {
736
					$attributes = array_merge($attributes, $columnAttributes);
737
					continue;
738
				}
739
740
				throw new LogicException(sprintf(
741
					'Non-array response from %s::getColumnAttributes().',
742
					get_class($handler)
743
				));
744
			}
745
746
			return $attributes;
747
		}
748
749
		throw new InvalidArgumentException(sprintf(
750
			'Bad column "%s"',
751
			$column
752
		));
753
	}
754
755
	/**
756
	 * Get metadata for a column.
757
	 *
758
	 * @example "array('Title'=>'Email address')"
759
	 *
760
	 * @param string $column
761
	 *
762
	 * @return array
763
	 *
764
	 * @throws LogicException
765
	 * @throws InvalidArgumentException
766
	 */
767 View Code Duplication
	public function getColumnMetadata($column) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
768
		if(!$this->columnDispatch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->columnDispatch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
769
			$this->buildColumnDispatch();
770
		}
771
772
		if(!empty($this->columnDispatch[$column])) {
773
			$metaData = array();
774
775
			foreach($this->columnDispatch[$column] as $handler) {
776
				/**
777
				 * @var GridField_ColumnProvider $handler
778
				 */
779
				$columnMetaData = $handler->getColumnMetadata($this, $column);
780
781
				if(is_array($columnMetaData)) {
782
					$metaData = array_merge($metaData, $columnMetaData);
783
					continue;
784
				}
785
786
				throw new LogicException(sprintf(
787
					'Non-array response from %s::getColumnMetadata().',
788
					get_class($handler)
789
				));
790
			}
791
792
			return $metaData;
793
		}
794
795
		throw new InvalidArgumentException(sprintf(
796
			'Bad column "%s"',
797
			$column
798
		));
799
	}
800
801
	/**
802
	 * Return how many columns the grid will have.
803
	 *
804
	 * @return int
805
	 */
806
	public function getColumnCount() {
807
		if(!$this->columnDispatch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->columnDispatch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
808
			$this->buildColumnDispatch();
809
		}
810
811
		return count($this->columnDispatch);
812
	}
813
814
	/**
815
	 * Build an columnDispatch that maps a GridField_ColumnProvider to a column for reference later.
816
	 */
817
	protected function buildColumnDispatch() {
818
		$this->columnDispatch = array();
819
820
		foreach($this->getComponents() as $item) {
821
			if($item instanceof GridField_ColumnProvider) {
822
				$columns = $item->getColumnsHandled($this);
823
824
				foreach($columns as $column) {
825
					$this->columnDispatch[$column][] = $item;
826
				}
827
			}
828
		}
829
	}
830
831
	/**
832
	 * This is the action that gets executed when a GridField_AlterAction gets clicked.
833
	 *
834
	 * @param array $data
835
	 * @param Form $form
836
	 * @param SS_HTTPRequest $request
837
	 *
838
	 * @return string
839
	 */
840
	public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) {
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...
841
		$data = $request->requestVars();
842
843
		// Protection against CSRF attacks
844
		$token = $this
845
			->getForm()
846
			->getSecurityToken();
847
		if(!$token->checkRequest($request)) {
848
			$this->httpError(400, _t("Form.CSRF_FAILED_MESSAGE",
849
				"There seems to have been a technical problem. Please click the back button, ".
850
				"refresh your browser, and try again."
851
			));
852
		}
853
854
		$name = $this->getName();
855
856
		$fieldData = null;
857
858
		if(isset($data[$name])) {
859
			$fieldData = $data[$name];
860
		}
861
862
		$state = $this->getState(false);
863
864
		if(isset($fieldData['GridState'])) {
865
			$state->setValue($fieldData['GridState']);
0 ignored issues
show
Bug introduced by
The method setValue does only exist in GridState, but not in GridState_Data.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
866
		}
867
868
		foreach($data as $dataKey => $dataValue) {
869
			if(preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) {
870
				$stateChange = Session::get($matches[1]);
871
				$actionName = $stateChange['actionName'];
872
873
				$arguments = array();
874
875
				if(isset($stateChange['args'])) {
876
					$arguments = $stateChange['args'];
877
				};
878
879
				$html = $this->handleAlterAction($actionName, $arguments, $data);
880
881
				if($html) {
882
					return $html;
883
				}
884
			}
885
		}
886
887
		if($request->getHeader('X-Pjax') === 'CurrentField') {
888
			return $this->FieldHolder();
889
		}
890
891
		return $form->forTemplate();
892
	}
893
894
	/**
895
	 * Pass an action on the first GridField_ActionProvider that matches the $actionName.
896
	 *
897
	 * @param string $actionName
898
	 * @param mixed $arguments
899
	 * @param array $data
900
	 *
901
	 * @return mixed
902
	 *
903
	 * @throws InvalidArgumentException
904
	 */
905
	public function handleAlterAction($actionName, $arguments, $data) {
906
		$actionName = strtolower($actionName);
907
908
		foreach($this->getComponents() as $component) {
909
			if($component instanceof GridField_ActionProvider) {
910
				$actions = array_map('strtolower', (array) $component->getActions($this));
911
912
				if(in_array($actionName, $actions)) {
913
					return $component->handleAction($this, $actionName, $arguments, $data);
914
				}
915
			}
916
		}
917
918
		throw new InvalidArgumentException(sprintf(
919
			'Can\'t handle action "%s"',
920
			$actionName
921
		));
922
	}
923
924
	/**
925
	 * Custom request handler that will check component handlers before proceeding to the default
926
	 * implementation.
927
	 *
928
	 * @todo copy less code from RequestHandler.
929
	 *
930
	 * @param SS_HTTPRequest $request
931
	 * @param DataModel $model
932
	 *
933
	 * @return array|RequestHandler|SS_HTTPResponse|string|void
934
	 *
935
	 * @throws SS_HTTPResponse_Exception
936
	 */
937
	public function handleRequest(SS_HTTPRequest $request, DataModel $model) {
938
		if($this->brokenOnConstruct) {
939
			user_error(
940
				sprintf(
941
					"parent::__construct() needs to be called on %s::__construct()",
942
					__CLASS__
943
				),
944
				E_USER_WARNING
945
			);
946
		}
947
948
		$this->setRequest($request);
949
		$this->setDataModel($model);
950
951
		$fieldData = $this->getRequest()->requestVar($this->getName());
952
953
		if($fieldData && isset($fieldData['GridState'])) {
954
			$this->getState(false)->setValue($fieldData['GridState']);
0 ignored issues
show
Bug introduced by
The method setValue does only exist in GridState, but not in GridState_Data.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
955
		}
956
957
		foreach($this->getComponents() as $component) {
958
			if($component instanceof GridField_URLHandler && $urlHandlers = $component->getURLHandlers($this)) {
959
				foreach($urlHandlers as $rule => $action) {
960
					if($params = $request->match($rule, true)) {
961
						// Actions can reference URL parameters.
962
						// e.g. '$Action/$ID/$OtherID' → '$Action'
963
964
						if($action[0] == '$') {
965
							$action = $params[substr($action, 1)];
966
						}
967
968
						if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridField_URLHandler as the method checkAccessAction() does only exist in the following implementations of said interface: GridField_URLHandlerTest_Component.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
969
							if(!$action) {
970
								$action = "index";
971
							}
972
973
							if(!is_string($action)) {
974
								throw new LogicException(sprintf(
975
									'Non-string method name: %s',
976
									var_export($action, true)
977
								));
978
							}
979
980
							try {
981
								$result = $component->$action($this, $request);
982
							} catch(SS_HTTPResponse_Exception $responseException) {
983
								$result = $responseException->getResponse();
984
							}
985
986
							if($result instanceof SS_HTTPResponse && $result->isError()) {
987
								return $result;
988
							}
989
990
							if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result) && $result instanceof RequestHandler) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $request->isEmptyPattern($rule) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
991
								$returnValue = $result->handleRequest($request, $model);
992
993
								if(is_array($returnValue)) {
994
									throw new LogicException(
995
										'GridField_URLHandler handlers can\'t return arrays'
996
									);
997
								}
998
999
								return $returnValue;
1000
							}
1001
1002
							if($request->allParsed()) {
1003
								return $result;
1004
							}
1005
1006
							return $this->httpError(
1007
								404,
1008
								sprintf(
1009
									'I can\'t handle sub-URLs of a %s object.',
1010
									get_class($result)
1011
								)
1012
							);
1013
						}
1014
					}
1015
				}
1016
			}
1017
		}
1018
1019
		return parent::handleRequest($request, $model);
1020
	}
1021
1022
	/**
1023
	 * {@inheritdoc}
1024
	 */
1025
	public function saveInto(DataObjectInterface $record) {
1026
		foreach($this->getComponents() as $component) {
1027
			if($component instanceof GridField_SaveHandler) {
1028
				$component->handleSave($this, $record);
1029
			}
1030
		}
1031
	}
1032
1033
	/**
1034
	 * @param array $content
1035
	 *
1036
	 * @return string
1037
	 */
1038
	protected function getOptionalTableHeader(array $content) {
1039
		if($content['header']) {
1040
			return FormField::create_tag(
1041
				'thead', array(), $content['header']
1042
			);
1043
		}
1044
1045
		return '';
1046
	}
1047
1048
	/**
1049
	 * @param array $content
1050
	 *
1051
	 * @return string
1052
	 */
1053
	protected function getOptionalTableBody(array $content) {
1054
		if($content['body']) {
1055
			return FormField::create_tag(
1056
				'tbody', array('class' => 'ss-gridfield-items'), $content['body']
1057
			);
1058
		}
1059
1060
		return '';
1061
	}
1062
1063
	/**
1064
	 * @param $content
1065
	 *
1066
	 * @return string
1067
	 */
1068
	protected function getOptionalTableFooter($content) {
1069
		if($content['footer']) {
1070
			return FormField::create_tag(
1071
				'tfoot', array(), $content['footer']
1072
			);
1073
		}
1074
1075
		return '';
1076
	}
1077
1078
}
1079
1080
/**
1081
 * This class is the base class when you want to have an action that alters the state of the
1082
 * {@link GridField}, rendered as a button element.
1083
 *
1084
 * @package forms
1085
 * @subpackage fields-gridfield
1086
 */
1087
class GridField_FormAction extends FormAction {
1088
	/**
1089
	 * @var GridField
1090
	 */
1091
	protected $gridField;
1092
1093
	/**
1094
	 * @var array
1095
	 */
1096
	protected $stateValues;
1097
1098
	/**
1099
	 * @var array
1100
	 */
1101
	protected $args = array();
1102
1103
	/**
1104
	 * @var string
1105
	 */
1106
	protected $actionName;
1107
1108
	/**
1109
	 * @var boolean
1110
	 */
1111
	public $useButtonTag = true;
1112
1113
	/**
1114
	 * @param GridField $gridField
1115
	 * @param string $name
1116
	 * @param string $title
1117
	 * @param string $actionName
1118
	 * @param array $args
1119
	 */
1120
	public function __construct(GridField $gridField, $name, $title, $actionName, $args) {
1121
		$this->gridField = $gridField;
1122
		$this->actionName = $actionName;
1123
		$this->args = $args;
1124
1125
		parent::__construct($name, $title);
1126
	}
1127
1128
	/**
1129
	 * Encode all non-word characters.
1130
	 *
1131
	 * @param string $value
1132
	 *
1133
	 * @return string
1134
	 */
1135
	public function nameEncode($value) {
1136
		return (string) preg_replace_callback('/[^\w]/', array($this, '_nameEncode'), $value);
1137
	}
1138
1139
	/**
1140
	 * @param array $match
1141
	 *
1142
	 * @return string
1143
	 */
1144
	public function _nameEncode($match) {
1145
		return '%' . dechex(ord($match[0]));
1146
	}
1147
1148
	/**
1149
	 * @return array
1150
	 */
1151
	public function getAttributes() {
1152
		// Store state in session, and pass ID to client side.
1153
		$state = array(
1154
			'grid' => $this->getNameFromParent(),
1155
			'actionName' => $this->actionName,
1156
			'args' => $this->args,
1157
		);
1158
1159
		// Ensure $id doesn't contain only numeric characters
1160
		$id = 'gf_' . substr(md5(serialize($state)), 0, 8);
1161
		Session::set($id, $state);
0 ignored issues
show
Documentation introduced by
$state is of type array<string,string|arra...tring","args":"array"}>, 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...
1162
		$actionData['StateID'] = $id;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$actionData was never initialized. Although not strictly required by PHP, it is generally a good practice to add $actionData = 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...
1163
1164
		return array_merge(
1165
			parent::getAttributes(),
1166
			array(
1167
				// Note:  This field needs to be less than 65 chars, otherwise Suhosin security patch
1168
				// will strip it from the requests
1169
				'name' => 'action_gridFieldAlterAction' . '?' . http_build_query($actionData),
1170
				'data-url' => $this->gridField->Link(),
1171
			)
1172
		);
1173
	}
1174
1175
	/**
1176
	 * Calculate the name of the gridfield relative to the form.
1177
	 *
1178
	 * @return string
1179
	 */
1180
	protected function getNameFromParent() {
1181
		$base = $this->gridField;
1182
		$name = array();
1183
1184
		do {
1185
			array_unshift($name, $base->getName());
1186
			$base = $base->getForm();
1187
		} while($base && !($base instanceof Form));
1188
1189
		return implode('.', $name);
1190
	}
1191
}
1192