Completed
Push — master ( daaff8...44ccfa )
by Ingo
22:24 queued 11:43
created

AddToCampaignHandler::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 8
rs 9.4285
1
<?php
2
3
namespace SilverStripe\Admin;
4
5
use SilverStripe\Framework\Core\Injectable;
6
use SilverStripe\ORM\ArrayList;
7
use SilverStripe\ORM\FieldType\DBHTMLText;
8
use SilverStripe\ORM\Versioning\ChangeSet;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\ORM\Versioning\ChangeSetItem;
11
use ClassInfo;
12
use Object;
13
use DropdownField;
14
use FieldList;
15
use HiddenField;
16
use Form;
17
use CompositeField;
18
use LiteralField;
19
use Director;
20
use SS_HTTPResponse;
21
use FormAction;
22
use SS_HTTPResponse_Exception;
23
use HeaderField;
24
25
/**
26
 * Class AddToCampaignHandler - handle the AddToCampaign action.
27
 *
28
 * This is a class designed to be delegated to by a Form action handler method in the EditForm of a LeftAndMain
29
 * child class.
30
 *
31
 * Add To Campaign can be seen as an item action like "publish" or "rollback", but unlike those actions
32
 * it needs one additional piece of information to execute, the ChangeSet ID.
33
 *
34
 * So this handler does one of two things to respond to the action request, depending on whether the ChangeSet ID
35
 * was included in the submitted data
36
 * - If it was, perform the Add To Campaign action (as per any other action)
37
 * - If it wasn't, return a form to get the ChangeSet ID and then repeat this action submission
38
 *
39
 * To use, you'd add an action to your LeftAndMain subclass, like this:
40
 *
41
 *     function addtocampaign($data, $form) {
42
 *         $handler = AddToCampaignHandler::create($form, $data);
43
 *         return $handler->handle();
44
 *     }
45
 *
46
 *  and add an AddToCampaignHandler_FormAction to the EditForm, possibly through getCMSActions
47
 */
48
class AddToCampaignHandler {
49
	use Injectable;
50
51
	/**
52
	 * Parent controller for this form
53
	 *
54
	 * @var Controller
55
	 */
56
	protected $controller;
57
58
	/**
59
	 * The submitted form data
60
	 *
61
	 * @var array
62
	 */
63
	protected $data;
64
65
	/**
66
	 * Form name to use
67
	 *
68
	 * @var string
69
	 */
70
	protected $name;
71
72
	/**
73
	 * AddToCampaignHandler constructor.
74
	 *
75
	 * @param Controller $parentController Controller for this form
0 ignored issues
show
Documentation introduced by
There is no parameter named $parentController. Did you maybe mean $controller?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
76
	 * @param array|DataObject $data The data submitted as part of that form
77
	 * @param string $name Form name
78
	 */
79
	public function __construct($controller = null, $data = [], $name = 'AddToCampaignForm') {
80
		$this->controller = $controller;
81
		if ($data instanceof DataObject) {
82
			$data = $data->toMap();
83
		}
84
		$this->data = $data;
85
		$this->name = $name;
86
	}
87
88
	/**
89
	 * Perform the action. Either returns a Form or performs the action, as per the class doc
90
	 *
91
	 * @return DBHTMLText|SS_HTTPResponse
92
	 */
93
	public function handle() {
94
		$object = $this->getObject($this->data['ID'], $this->data['ClassName']);
95
96
		if (empty($this->data['Campaign'])) {
97
			return $this->Form($object)->forTemplate();
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getObject($this->...his->data['ClassName']) on line 94 can be null; however, SilverStripe\Admin\AddToCampaignHandler::Form() 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...
98
		} else {
99
			return $this->addToCampaign($object, $this->data['Campaign']);
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->getObject($this->...his->data['ClassName']) on line 94 can be null; however, SilverStripe\Admin\AddTo...andler::addToCampaign() 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...
100
		}
101
	}
102
103
	/**
104
	 * Get what ChangeSets are available for an item to be added to by this user
105
	 *
106
	 * @return ArrayList[ChangeSet]
0 ignored issues
show
Documentation introduced by
The doc-type ArrayList[ChangeSet] could not be parsed: Expected "]" at position 2, but found "ChangeSet". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
107
	 */
108
	protected function getAvailableChangeSets() {
109
		return ChangeSet::get()
110
			->filter('State', ChangeSet::STATE_OPEN)
111
			->filterByCallback(function($item) {
112
				/** @var ChangeSet $item */
113
				return $item->canView();
114
			});
115
	}
116
117
	/**
118
	 * Safely get a DataObject from a client-supplied ID and ClassName, checking: argument
119
	 * validity; existence; and canView permissions.
120
	 *
121
	 * @param int $id The ID of the DataObject
122
	 * @param string $class The Class of the DataObject
123
	 * @return DataObject The referenced DataObject
124
	 * @throws SS_HTTPResponse_Exception
125
	 */
126
	protected function getObject($id, $class) {
127
		$id = (int)$id;
128
		$class = ClassInfo::class_name($class);
129
130
		if (!$class || !is_subclass_of($class, 'SilverStripe\\ORM\\DataObject') || !Object::has_extension($class, 'SilverStripe\\ORM\\Versioning\\Versioned')) {
131
			$this->controller->httpError(400, _t(
132
				'AddToCampaign.ErrorGeneral',
133
				'We apologise, but there was an error'
134
			));
135
			return null;
136
		}
137
138
		$object = DataObject::get($class)->byID($id);
139
140
		if (!$object) {
141
			$this->controller->httpError(404, _t(
142
				'AddToCampaign.ErrorNotFound',
143
				'That {Type} couldn\'t be found',
144
				'',
145
				['Type' => $class]
146
			));
147
			return null;
148
		}
149
150
		if (!$object->canView()) {
151
			$this->controller->httpError(403, _t(
152
					'AddToCampaign.ErrorItemPermissionDenied',
153
					'It seems you don\'t have the necessary permissions to add {ObjectTitle} to a campaign',
154
					'',
155
					['ObjectTitle' => $object->Title]
156
				)
157
			);
158
			return null;
159
		}
160
161
		return $object;
162
	}
163
164
	/**
165
	 * Builds a Form that mirrors the parent editForm, but with an extra field to collect the ChangeSet ID
166
	 *
167
	 * @param DataObject $object The object we're going to be adding to whichever ChangeSet is chosen
168
	 * @return Form
169
	 */
170
	public function Form($object) {
171
		$inChangeSets = array_unique(ChangeSetItem::get_for_object($object)->column('ChangeSetID'));
172
		$changeSets = $this->getAvailableChangeSets()->map();
173
174
		$campaignDropdown = DropdownField::create('Campaign', '', $changeSets);
175
		$campaignDropdown->setEmptyString(_t('Campaigns.AddToCampaignFormFieldLabel', 'Select a Campaign'));
176
		$campaignDropdown->addExtraClass('noborder');
177
		$campaignDropdown->addExtraClass('no-chosen');
178
		$campaignDropdown->setDisabledItems($inChangeSets);
179
180
		$fields = new FieldList([
181
			$campaignDropdown,
182
			HiddenField::create('ID', null, $this->data['ID']),
183
			HiddenField::create('ClassName', null, $this->data['ClassName'])
184
		]);
185
186
187
		$form = new Form(
188
			$this->controller,
189
			$this->name,
190
			$fields,
191
			new FieldList(
192
				$action = AddToCampaignHandler_FormAction::create()
193
			)
194
		);
195
196
		$action->addExtraClass('add-to-campaign__action');
197
198
		$form->setHTMLID('Form_EditForm_AddToCampaign');
199
200
		$form->unsetValidator();
201
		$form->loadDataFrom($this->data);
202
		$form->addExtraClass('form--no-dividers add-to-campaign__form');
203
204
		return $form;
205
	}
206
207
	/**
208
	 * Performs the actual action of adding the object to the ChangeSet, once the ChangeSet ID is known
209
	 *
210
	 * @param DataObject $object The object to add to the ChangeSet
211
	 * @param int $campaignID The ID of the ChangeSet to add $object to
212
	 * @return SS_HTTPResponse
213
	 * @throws SS_HTTPResponse_Exception
214
	 */
215
	public function addToCampaign($object, $campaignID) {
216
		/** @var ChangeSet $changeSet */
217
		$changeSet = ChangeSet::get()->byID($campaignID);
218
219
		if (!$changeSet) {
220
			$this->controller->httpError(404, _t(
221
				'AddToCampaign.ErrorNotFound',
222
				'That {Type} couldn\'t be found',
223
				'',
224
				['Type' => 'Campaign']
225
			));
226
			return null;
227
		}
228
229
		if (!$changeSet->canEdit()) {
230
			$this->controller->httpError(403, _t(
231
				'AddToCampaign.ErrorCampaignPermissionDenied',
232
				'It seems you don\'t have the necessary permissions to add {ObjectTitle} to {CampaignTitle}',
233
				'',
234
				['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]
235
			));
236
			return null;
237
		}
238
239
		$changeSet->addObject($object);
240
241
		$request = $this->controller->getRequest();
242
		$message = _t(
243
			'AddToCampaign.Success',
244
			'Successfully added {ObjectTitle} to {CampaignTitle}',
245
			'',
246
			['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]
247
		);
248
		if ($request->getHeader('X-Formschema-Request')) {
249
			return $message;
250
		} elseif (Director::is_ajax()) {
251
			$response = new SS_HTTPResponse($message, 200);
252
253
			$response->addHeader('Content-Type', 'text/plain; charset=utf-8');
254
			return $response;
255
		} else {
256
			return $this->controller->getController()->redirectBack();
257
		}
258
	}
259
}
260