Completed
Push — master ( 10f429...3b71b7 )
by Ingo
11:44
created

generateValidationResponse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 2
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use SilverStripe\Framework\Core\Extensible;
4
use SilverStripe\ORM\DataModel;
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\ORM\HasManyList;
7
use SilverStripe\ORM\ManyManyList;
8
use SilverStripe\ORM\SS_List;
9
use SilverStripe\ORM\ValidationException;
10
use SilverStripe\ORM\FieldType\DBHTMLText;
11
use SilverStripe\Admin\LeftAndMain;
12
13
/**
14
 * Provides view and edit forms at GridField-specific URLs.
15
 *
16
 * These can be placed into pop-ups by an appropriate front-end.
17
 *
18
 * Usually added to a {@link GridField} alongside of a
19
 * {@link GridFieldEditButton} which takes care of linking the
20
 * individual rows to their edit view.
21
 *
22
 * The URLs provided will be off the following form:
23
 *  - <FormURL>/field/<GridFieldName>/item/<RecordID>
24
 *  - <FormURL>/field/<GridFieldName>/item/<RecordID>/edit
25
 *
26
 * @package forms
27
 * @subpackage fields-gridfield
28
 */
29
class GridFieldDetailForm implements GridField_URLHandler {
30
31
	use Extensible;
32
33
	/**
34
	 * @var string
35
	 */
36
	protected $template = 'GridFieldDetailForm';
37
38
	/**
39
	 *
40
	 * @var string
41
	 */
42
	protected $name;
43
44
	/**
45
	 * @var Validator The form validator used for both add and edit fields.
46
	 */
47
	protected $validator;
48
49
	/**
50
	 * @var FieldList Falls back to {@link DataObject->getCMSFields()} if not defined.
51
	 */
52
	protected $fields;
53
54
	/**
55
	 * @var string
56
	 */
57
	protected $itemRequestClass;
58
59
	/**
60
	 * @var callable With two parameters: $form and $component
61
	 */
62
	protected $itemEditFormCallback;
63
64
	public function getURLHandlers($gridField) {
65
		return array(
66
			'item/$ID' => 'handleItem',
67
			'autocomplete' => 'handleAutocomplete',
68
		);
69
	}
70
71
	/**
72
	 * Create a popup component. The two arguments will specify how the popup form's HTML and
73
	 * behaviour is created.  The given controller will be customised, putting the edit form into the
74
	 * template with the given name.
75
	 *
76
	 * The arguments are experimental API's to support partial content to be passed back to whatever
77
	 * controller who wants to display the getCMSFields
78
	 *
79
	 * @param string $name The name of the edit form to place into the pop-up form
80
	 */
81
	public function __construct($name = 'DetailForm') {
82
		$this->name = $name;
83
		$this->constructExtensions();
84
	}
85
86
	/**
87
	 *
88
	 * @param GridField $gridField
89
	 * @param SS_HTTPRequest $request
90
	 * @return GridFieldDetailForm_ItemRequest
91
	 */
92
	public function handleItem($gridField, $request) {
93
		// Our getController could either give us a true Controller, if this is the top-level GridField.
94
		// It could also give us a RequestHandler in the form of GridFieldDetailForm_ItemRequest if this is a
95
		// nested GridField.
96
		$requestHandler = $gridField->getForm()->getController();
97
98
		if(is_numeric($request->param('ID'))) {
99
			$record = $gridField->getList()->byID($request->param("ID"));
100
		} else {
101
			$record = Object::create($gridField->getModelClass());
102
		}
103
104
		$handler = $this->getItemRequestHandler($gridField, $record, $requestHandler);
0 ignored issues
show
Bug introduced by
It seems like $requestHandler defined by $gridField->getForm()->getController() on line 96 can be null; however, GridFieldDetailForm::getItemRequestHandler() 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...
105
106
		// if no validator has been set on the GridField and the record has a
107
		// CMS validator, use that.
108
		if(!$this->getValidator() && (method_exists($record, 'getCMSValidator') || $record instanceof Object && $record->hasMethod('getCMSValidator'))) {
109
			$this->setValidator($record->getCMSValidator());
110
		}
111
112
		return $handler->handleRequest($request, DataModel::inst());
113
	}
114
115
	/**
116
	 * Build a request handler for the given record
117
	 *
118
	 * @param GridField $gridField
119
	 * @param DataObject $record
120
	 * @param Controller $requestHandler
121
	 * @return GridFieldDetailForm_ItemRequest
122
	 */
123
	protected function getItemRequestHandler($gridField, $record, $requestHandler) {
124
		$class = $this->getItemRequestClass();
125
		$this->extend('updateItemRequestClass', $class, $gridField, $record, $requestHandler);
126
		$handler = \Injector::inst()->createWithArgs(
127
			$class,
128
			array($gridField, $this, $record, $requestHandler, $this->name)
129
		);
130
		$handler->setTemplate($this->getTemplate());
131
		$this->extend('updateItemRequestHandler', $handler);
132
		return $handler;
133
	}
134
135
	/**
136
	 * @param string $template
137
	 * @return $this
138
	 */
139
	public function setTemplate($template) {
140
		$this->template = $template;
141
		return $this;
142
	}
143
144
	/**
145
	 * @return String
146
	 */
147
	public function getTemplate() {
148
		return $this->template;
149
	}
150
151
	/**
152
	 * @param string $name
153
	 * @return $this
154
	 */
155
	public function setName($name) {
156
		$this->name = $name;
157
		return $this;
158
	}
159
160
	/**
161
	 * @return String
162
	 */
163
	public function getName() {
164
		return $this->name;
165
	}
166
167
	/**
168
	 * @param Validator $validator
169
	 * @return $this
170
	 */
171
	public function setValidator(Validator $validator) {
172
		$this->validator = $validator;
173
		return $this;
174
	}
175
176
	/**
177
	 * @return Validator
178
	 */
179
	public function getValidator() {
180
		return $this->validator;
181
	}
182
183
	/**
184
	 * @param FieldList $fields
185
	 * @return $this
186
	 */
187
	public function setFields(FieldList $fields) {
188
		$this->fields = $fields;
189
		return $this;
190
	}
191
192
	/**
193
	 * @return FieldList
194
	 */
195
	public function getFields() {
196
		return $this->fields;
197
	}
198
199
	/**
200
	 * @param string $class
201
	 * @return $this
202
	 */
203
	public function setItemRequestClass($class) {
204
		$this->itemRequestClass = $class;
205
		return $this;
206
	}
207
208
	/**
209
	 * @return String
210
	 */
211
	public function getItemRequestClass() {
212
		if($this->itemRequestClass) {
213
			return $this->itemRequestClass;
214
		} else if(ClassInfo::exists(get_class($this) . "_ItemRequest")) {
215
			return get_class($this) . "_ItemRequest";
216
		} else {
217
			return __CLASS__ . '_ItemRequest';
218
		}
219
	}
220
221
	/**
222
	 * @param Closure $cb Make changes on the edit form after constructing it.
223
	 * @return $this
224
	 */
225
	public function setItemEditFormCallback(Closure $cb) {
226
		$this->itemEditFormCallback = $cb;
227
		return $this;
228
	}
229
230
	/**
231
	 * @return Closure
232
	 */
233
	public function getItemEditFormCallback() {
234
		return $this->itemEditFormCallback;
235
	}
236
237
}
238
239
/**
240
 * @package forms
241
 * @subpackage fields-gridfield
242
 */
243
class GridFieldDetailForm_ItemRequest extends RequestHandler {
244
245
	private static $allowed_actions = array(
246
		'edit',
247
		'view',
248
		'ItemEditForm'
249
	);
250
251
	/**
252
	 *
253
	 * @var GridField
254
	 */
255
	protected $gridField;
256
257
	/**
258
	 *
259
	 * @var GridFieldDetailForm
260
	 */
261
	protected $component;
262
263
	/**
264
	 *
265
	 * @var DataObject
266
	 */
267
	protected $record;
268
269
	/**
270
	 * This represents the current parent RequestHandler (which does not necessarily need to be a Controller).
271
	 * It allows us to traverse the RequestHandler chain upwards to reach the Controller stack.
272
	 *
273
	 * @var RequestHandler
274
	 */
275
	protected $popupController;
276
277
	/**
278
	 *
279
	 * @var string
280
	 */
281
	protected $popupFormName;
282
283
	/**
284
	 * @var String
285
	 */
286
	protected $template = null;
287
288
	private static $url_handlers = array(
289
		'$Action!' => '$Action',
290
		'' => 'edit',
291
	);
292
293
	/**
294
	 *
295
	 * @param GridFIeld $gridField
296
	 * @param GridFieldDetailForm $component
297
	 * @param DataObject $record
298
	 * @param RequestHandler $requestHandler
299
	 * @param string $popupFormName
300
	 */
301
	public function __construct($gridField, $component, $record, $requestHandler, $popupFormName) {
302
		$this->gridField = $gridField;
303
		$this->component = $component;
304
		$this->record = $record;
305
		$this->popupController = $requestHandler;
306
		$this->popupFormName = $popupFormName;
307
		parent::__construct();
308
	}
309
310
	public function Link($action = null) {
311
		return Controller::join_links($this->gridField->Link('item'),
312
			$this->record->ID ? $this->record->ID : 'new', $action);
313
	}
314
315
	public function view($request) {
316
		if(!$this->record->canView()) {
317
			$this->httpError(403);
318
		}
319
320
		$controller = $this->getToplevelController();
321
322
		$form = $this->ItemEditForm();
323
		$form->makeReadonly();
324
325
		$data = new ArrayData(array(
326
			'Backlink'     => $controller->Link(),
327
			'ItemEditForm' => $form
328
		));
329
		$return = $data->renderWith($this->getTemplates());
330
331
		if($request->isAjax()) {
332
			return $return;
333
		} else {
334
			return $controller->customise(array('Content' => $return));
335
		}
336
	}
337
338
	public function edit($request) {
339
		$controller = $this->getToplevelController();
340
		$form = $this->ItemEditForm();
341
342
		$return = $this->customise(array(
343
			'Backlink' => $controller->hasMethod('Backlink') ? $controller->Backlink() : $controller->Link(),
344
			'ItemEditForm' => $form,
345
		))->renderWith($this->getTemplates());
346
347
		if($request->isAjax()) {
348
			return $return;
349
		} else {
350
			// If not requested by ajax, we need to render it within the controller context+template
351
			return $controller->customise(array(
352
				// TODO CMS coupling
353
				'Content' => $return,
354
			));
355
		}
356
	}
357
358
	/**
359
	 * Builds an item edit form.  The arguments to getCMSFields() are the popupController and
360
	 * popupFormName, however this is an experimental API and may change.
361
	 *
362
	 * @todo In the future, we will probably need to come up with a tigher object representing a partially
363
	 * complete controller with gaps for extra functionality.  This, for example, would be a better way
364
	 * of letting Security/login put its log-in form inside a UI specified elsewhere.
365
	 *
366
	 * @return Form
367
	 */
368
	public function ItemEditForm() {
369
		$list = $this->gridField->getList();
370
371
		if (empty($this->record)) {
372
			$controller = $this->getToplevelController();
373
			$url = $controller->getRequest()->getURL();
374
			$noActionURL = $controller->removeAction($url);
375
			$controller->getResponse()->removeHeader('Location');   //clear the existing redirect
376
			return $controller->redirect($noActionURL, 302);
377
		}
378
379
		$canView = $this->record->canView();
380
		$canEdit = $this->record->canEdit();
381
		$canDelete = $this->record->canDelete();
382
		$canCreate = $this->record->canCreate();
383
384
		if(!$canView) {
385
			$controller = $this->getToplevelController();
386
			// TODO More friendly error
387
			return $controller->httpError(403);
388
		}
389
390
		// Build actions
391
		$actions = $this->getFormActions();
392
393
		// If we are creating a new record in a has-many list, then
394
		// pre-populate the record's foreign key.
395
		if($list instanceof HasManyList && !$this->record->isInDB()) {
396
			$key = $list->getForeignKey();
397
			$id = $list->getForeignID();
398
			$this->record->$key = $id;
399
		}
400
401
		$fields = $this->component->getFields();
402
		if(!$fields) $fields = $this->record->getCMSFields();
403
404
		// If we are creating a new record in a has-many list, then
405
		// Disable the form field as it has no effect.
406
		if($list instanceof HasManyList) {
407
			$key = $list->getForeignKey();
408
409
			if($field = $fields->dataFieldByName($key)) {
410
				$fields->makeFieldReadonly($field);
411
			}
412
		}
413
414
		// Caution: API violation. Form expects a Controller, but we are giving it a RequestHandler instead.
415
		// Thanks to this however, we are able to nest GridFields, and also access the initial Controller by
416
		// dereferencing GridFieldDetailForm_ItemRequest->getController() multiple times. See getToplevelController
417
		// below.
418
		$form = new Form(
419
			$this,
420
			'ItemEditForm',
421
			$fields,
422
			$actions,
423
			$this->component->getValidator()
424
		);
425
426
		$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
427
428
		if($this->record->ID && !$canEdit) {
429
			// Restrict editing of existing records
430
			$form->makeReadonly();
431
			// Hack to re-enable delete button if user can delete
432
			if ($canDelete) {
433
				$form->Actions()->fieldByName('action_doDelete')->setReadonly(false);
434
			}
435
		} elseif(!$this->record->ID && !$canCreate) {
436
			// Restrict creation of new records
437
			$form->makeReadonly();
438
		}
439
440
		// Load many_many extraData for record.
441
		// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
442
		if($list instanceof ManyManyList) {
443
			$extraData = $list->getExtraData('', $this->record->ID);
444
			$form->loadDataFrom(array('ManyMany' => $extraData));
445
		}
446
447
		// TODO Coupling with CMS
448
		$toplevelController = $this->getToplevelController();
449
		if($toplevelController && $toplevelController instanceof LeftAndMain) {
450
			// Always show with base template (full width, no other panels),
451
			// regardless of overloaded CMS controller templates.
452
			// TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller
453
			$form->setTemplate([
454
				'type' => 'Includes',
455
				'SilverStripe\\Admin\\LeftAndMain_EditForm',
456
			]);
457
			$form->addExtraClass('cms-content cms-edit-form center');
458
			$form->setAttribute('data-pjax-fragment', 'CurrentForm Content');
459
			if($form->Fields()->hasTabset()) {
460
				$form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
461
				$form->addExtraClass('cms-tabset');
462
			}
463
464
			$form->Backlink = $this->getBackLink();
0 ignored issues
show
Documentation introduced by
The property Backlink does not exist on object<Form>. 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...
465
		}
466
467
		$cb = $this->component->getItemEditFormCallback();
468
		if($cb) $cb($form, $this);
469
		$this->extend("updateItemEditForm", $form);
470
		return $form;
471
	}
472
473
	/**
474
	 * Build the set of form field actions for this DataObject
475
	 *
476
	 * @return FieldList
477
	 */
478
	protected function getFormActions() {
479
		$canEdit = $this->record->canEdit();
480
		$canDelete = $this->record->canDelete();
481
		$actions = new FieldList();
482
		if($this->record->ID !== 0) {
483
			if($canEdit) {
484
				$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
485
					->setUseButtonTag(true)
486
					->addExtraClass('ss-ui-action-constructive')
487
					->setAttribute('data-icon', 'accept'));
488
			}
489
490
			if($canDelete) {
491
				$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
492
					->setUseButtonTag(true)
493
					->addExtraClass('ss-ui-action-destructive action-delete'));
494
			}
495
496
		} else { // adding new record
497
			//Change the Save label to 'Create'
498
			$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Create', 'Create'))
499
				->setUseButtonTag(true)
500
				->addExtraClass('ss-ui-action-constructive')
501
				->setAttribute('data-icon', 'add'));
502
503
			// Add a Cancel link which is a button-like link and link back to one level up.
504
			$crumbs = $this->Breadcrumbs();
505
			if($crumbs && $crumbs->count() >= 2){
506
				$oneLevelUp = $crumbs->offsetGet($crumbs->count() - 2);
507
				$text = sprintf(
508
					"<a class=\"%s\" href=\"%s\">%s</a>",
509
					"crumb ss-ui-button ss-ui-action-destructive cms-panel-link ui-corner-all", // CSS classes
510
					$oneLevelUp->Link, // url
511
					_t('GridFieldDetailForm.CancelBtn', 'Cancel') // label
512
				);
513
				$actions->push(new LiteralField('cancelbutton', $text));
514
			}
515
		}
516
		$this->extend('updateFormActions', $actions);
517
		return $actions;
518
	}
519
520
	/**
521
	 * Traverse the nested RequestHandlers until we reach something that's not GridFieldDetailForm_ItemRequest.
522
	 * This allows us to access the Controller responsible for invoking the top-level GridField.
523
	 * This should be equivalent to getting the controller off the top of the controller stack via Controller::curr(),
524
	 * but allows us to avoid accessing the global state.
525
	 *
526
	 * GridFieldDetailForm_ItemRequests are RequestHandlers, and as such they are not part of the controller stack.
527
	 *
528
	 * @return Controller
529
	 */
530
	protected function getToplevelController() {
531
		$c = $this->popupController;
532
		while($c && $c instanceof GridFieldDetailForm_ItemRequest) {
533
			$c = $c->getController();
534
		}
535
		return $c;
536
	}
537
538
	protected function getBackLink(){
539
		// TODO Coupling with CMS
540
		$backlink = '';
541
		$toplevelController = $this->getToplevelController();
542
		if($toplevelController && $toplevelController instanceof LeftAndMain) {
543
			if($toplevelController->hasMethod('Backlink')) {
544
				$backlink = $toplevelController->Backlink();
545
			} elseif($this->popupController->hasMethod('Breadcrumbs')) {
546
				$parents = $this->popupController->Breadcrumbs(false)->items;
547
				$backlink = array_pop($parents)->Link;
548
			}
549
		}
550
		if(!$backlink) $backlink = $toplevelController->Link();
551
552
		return $backlink;
553
	}
554
555
	/**
556
	 * Get the list of extra data from the $record as saved into it by
557
	 * {@see Form::saveInto()}
558
	 *
559
	 * Handles detection of falsey values explicitly saved into the
560
	 * DataObject by formfields
561
	 *
562
	 * @param DataObject $record
563
	 * @param SS_List $list
564
	 * @return array List of data to write to the relation
565
	 */
566
	protected function getExtraSavedData($record, $list) {
567
		// Skip extra data if not ManyManyList
568
		if(!($list instanceof ManyManyList)) {
569
			return null;
570
		}
571
572
		$data = array();
573
		foreach($list->getExtraFields() as $field => $dbSpec) {
574
			$savedField = "ManyMany[{$field}]";
575
			if($record->hasField($savedField)) {
576
				$data[$field] = $record->getField($savedField);
577
			}
578
		}
579
		return $data;
580
	}
581
582
	public function doSave($data, $form) {
583
		$isNewRecord = $this->record->ID == 0;
584
585
		// Check permission
586
		if (!$this->record->canEdit()) {
587
			return $this->httpError(403);
588
		}
589
590
		// Save from form data
591
		try {
592
			$this->saveFormIntoRecord($data, $form);
593
		} catch (ValidationException $e) {
594
			return $this->generateValidationResponse($form, $e);
595
		}
596
597
		$link = '<a href="' . $this->Link('edit') . '">"'
598
			. htmlspecialchars($this->record->Title, ENT_QUOTES)
599
			. '"</a>';
600
		$message = _t(
601
			'GridFieldDetailForm.Saved',
602
			'Saved {name} {link}',
603
			array(
604
				'name' => $this->record->i18n_singular_name(),
605
				'link' => $link
606
			)
607
		);
608
609
		$form->sessionMessage($message, 'good', false);
610
611
		// Redirect after save
612
		return $this->redirectAfterSave($isNewRecord);
613
	}
614
615
	/**
616
	 * Response object for this request after a successful save
617
	 *
618
	 * @param bool $isNewRecord True if this record was just created
619
	 * @return SS_HTTPResponse|DBHTMLText
620
	 */
621
	protected function redirectAfterSave($isNewRecord) {
622
		$controller = $this->getToplevelController();
623
		if($isNewRecord) {
624
			return $controller->redirect($this->Link());
625
		} elseif($this->gridField->getList()->byID($this->record->ID)) {
626
			// Return new view, as we can't do a "virtual redirect" via the CMS Ajax
627
			// to the same URL (it assumes that its content is already current, and doesn't reload)
628
			return $this->edit($controller->getRequest());
629
		} else {
630
			// Changes to the record properties might've excluded the record from
631
			// a filtered list, so return back to the main view if it can't be found
632
			$url = $controller->getRequest()->getURL();
633
			$noActionURL = $controller->removeAction($url);
634
			$controller->getRequest()->addHeader('X-Pjax', 'Content');
635
			return $controller->redirect($noActionURL, 302);
636
		}
637
	}
638
639
	public function httpError($errorCode, $errorMessage = null) {
640
		$controller = $this->getToplevelController();
641
		return $controller->httpError($errorCode, $errorMessage);
642
	}
643
644
	/**
645
	 * Loads the given form data into the underlying dataobject and relation
646
	 *
647
	 * @param array $data
648
	 * @param Form $form
649
	 * @throws ValidationException On error
650
	 * @return DataObject Saved record
651
	 */
652
	protected function saveFormIntoRecord($data, $form) {
653
		$list = $this->gridField->getList();
654
655
		// Check object matches the correct classname
656
		if (isset($data['ClassName']) && $data['ClassName'] != $this->record->ClassName) {
657
			$newClassName = $data['ClassName'];
658
			// The records originally saved attribute was overwritten by $form->saveInto($record) before.
659
			// This is necessary for newClassInstance() to work as expected, and trigger change detection
660
			// on the ClassName attribute
661
			$this->record->setClassName($this->record->ClassName);
662
			// Replace $record with a new instance
663
			$this->record = $this->record->newClassInstance($newClassName);
664
		}
665
666
		// Save form and any extra saved data into this dataobject
667
		$form->saveInto($this->record);
668
		$this->record->write();
669
		$extraData = $this->getExtraSavedData($this->record, $list);
670
		$list->add($this->record, $extraData);
0 ignored issues
show
Unused Code introduced by
The call to SS_List::add() has too many arguments starting with $extraData.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
671
672
		return $this->record;
673
	}
674
675
	/**
676
	 * Generate a response object for a form validation error
677
	 *
678
	 * @param Form $form The source form
679
	 * @param ValidationException $e The validation error message
680
	 * @return SS_HTTPResponse
681
	 * @throws SS_HTTPResponse_Exception
682
	 */
683
	protected function generateValidationResponse($form, $e) {
684
		$controller = $this->getToplevelController();
685
686
		$form->sessionMessage($e->getResult()->message(), 'bad', false);
687
		$responseNegotiator = new PjaxResponseNegotiator(array(
688
			'CurrentForm' => function() use(&$form) {
689
				return $form->forTemplate();
690
			},
691
			'default' => function() use(&$controller) {
692
				return $controller->redirectBack();
693
			}
694
		));
695
		if($controller->getRequest()->isAjax()){
696
			$controller->getRequest()->addHeader('X-Pjax', 'CurrentForm');
697
		}
698
		return $responseNegotiator->respond($controller->getRequest());
699
	}
700
701
702
	public function doDelete($data, $form) {
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...
703
		$title = $this->record->Title;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\DataObject>. 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...
704
		$backLink = $this->getBacklink();
705
		try {
706
			if (!$this->record->canDelete()) {
707
				throw new ValidationException(
708
					_t('GridFieldDetailForm.DeletePermissionsFailure',"No delete permissions"),0);
709
			}
710
711
			$this->record->delete();
712
		} catch(ValidationException $e) {
713
			$form->sessionMessage($e->getResult()->message(), 'bad', false);
714
			return $this->getToplevelController()->redirectBack();
715
		}
716
717
		$message = sprintf(
718
			_t('GridFieldDetailForm.Deleted', 'Deleted %s %s'),
719
			$this->record->i18n_singular_name(),
720
			htmlspecialchars($title, ENT_QUOTES)
721
		);
722
723
		$toplevelController = $this->getToplevelController();
724
		if($toplevelController && $toplevelController instanceof LeftAndMain) {
725
			$backForm = $toplevelController->getEditForm();
726
			$backForm->sessionMessage($message, 'good', false);
727
		} else {
728
			$form->sessionMessage($message, 'good', false);
729
		}
730
731
		//when an item is deleted, redirect to the parent controller
732
		$controller = $this->getToplevelController();
733
		$controller->getRequest()->addHeader('X-Pjax', 'Content'); // Force a content refresh
734
735
		return $controller->redirect($backLink, 302); //redirect back to admin section
736
	}
737
738
	/**
739
	 * @param string $template
740
	 * @return $this
741
	 */
742
	public function setTemplate($template) {
743
		$this->template = $template;
744
		return $this;
745
	}
746
747
	/**
748
	 * @return String
749
	 */
750
	public function getTemplate() {
751
		return $this->template;
752
	}
753
754
	/**
755
	 * Get list of templates to use
756
	 *
757
	 * @return array
758
	 */
759
	public function getTemplates()
760
	{
761
		$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
762
		// Prefer any custom template
763
		if($this->getTemplate()) {
764
			array_unshift($templates, $this->getTemplate());
765
		}
766
		return $templates;
767
	}
768
769
	/**
770
	 * @return Controller
771
	 */
772
	public function getController() {
773
		return $this->popupController;
774
	}
775
776
	/**
777
	 * @return GridField
778
	 */
779
	public function getGridField() {
780
		return $this->gridField;
781
	}
782
783
	/**
784
	 * @return DataObject
785
	 */
786
	public function getRecord() {
787
		return $this->record;
788
	}
789
790
	/**
791
	 * CMS-specific functionality: Passes through navigation breadcrumbs
792
	 * to the template, and includes the currently edited record (if any).
793
	 * see {@link LeftAndMain->Breadcrumbs()} for details.
794
	 *
795
	 * @param boolean $unlinked
796
	 * @return ArrayData
797
	 */
798
	public function Breadcrumbs($unlinked = false) {
799
		if(!$this->popupController->hasMethod('Breadcrumbs')) return;
800
801
		$items = $this->popupController->Breadcrumbs($unlinked);
802
		if($this->record && $this->record->ID) {
803
			$title = ($this->record->Title) ? $this->record->Title : "#{$this->record->ID}";
804
			$items->push(new ArrayData(array(
805
				'Title' => $title,
806
				'Link' => $this->Link()
807
			)));
808
		} else {
809
			$items->push(new ArrayData(array(
810
				'Title' => sprintf(_t('GridField.NewRecord', 'New %s'), $this->record->i18n_singular_name()),
811
				'Link' => false
812
			)));
813
		}
814
815
		return $items;
816
	}
817
}
818