Completed
Push — master ( 3ce1f4...e01846 )
by Damian
10:25
created

AddToCampaignHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
use SilverStripe\Framework\Core\Injectable;
4
use SilverStripe\Model\FieldType\DBHTMLText;
5
6
7
/**
8
 * Class AddToCampaignHandler - handle the AddToCampaign action.
9
 *
10
 * This is a class designed to be delegated to by a Form action handler method in the EditForm of a LeftAndMain
11
 * child class.
12
 *
13
 * Add To Campaign can be seen as an item action like "publish" or "rollback", but unlike those actions
14
 * it needs one additional piece of information to execute, the ChangeSet ID.
15
 *
16
 * So this handler does one of two things to respond to the action request, depending on whether the ChangeSet ID
17
 * was included in the submitted data
18
 * - If it was, perform the Add To Campaign action (as per any other action)
19
 * - If it wasn't, return a form to get the ChangeSet ID and then repeat this action submission
20
 *
21
 * To use, you'd add an action to your LeftAndMain subclass, like this:
22
 *
23
 *     function addtocampaign($data, $form) {
24
 *         $handler = AddToCampaignHandler::create($form, $data);
25
 *         return $handler->handle();
26
 *     }
27
 *
28
 *  and add an AddToCampaignHandler_FormAction to the EditForm, possibly through getCMSActions
29
 */
30
class AddToCampaignHandler {
31
	use Injectable;
32
33
	/**
34
	 * The EditForm that contains the action we're being delegated to from
35
	 *
36
	 * @var Form
37
	 */
38
	protected $editForm;
39
40
	/**
41
	 * The submitted form data
42
	 *
43
	 * @var array
44
	 */
45
	protected $data;
46
47
	/**
48
	 * AddToCampaignHandler constructor.
49
	 *
50
	 * @param Form $editForm The parent form that triggered this action
51
	 * @param array $data The data submitted as part of that form
52
	 */
53
	public function __construct($editForm, $data) {
54
		$this->editForm = $editForm;
55
		$this->data = $data;
56
	}
57
58
	/**
59
	 * Perform the action. Either returns a Form or performs the action, as per the class doc
60
	 *
61
	 * @return DBHTMLText|SS_HTTPResponse
62
	 */
63
	public function handle() {
64
		$object = $this->getObject($this->data['ID'], $this->data['ClassName']);
65
66
		if (empty($this->data['Campaign'])) {
67
			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 64 can be null; however, 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...
68
		} else {
69
			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 64 can be null; however, AddToCampaignHandler::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...
70
		}
71
	}
72
73
	/**
74
	 * Get what ChangeSets are available for an item to be added to by this user
75
	 *
76
	 * @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...
77
	 */
78
	protected function getAvailableChangeSets() {
79
		return ChangeSet::get()
80
			->filter('State', ChangeSet::STATE_OPEN)
81
			->filterByCallback(function($item) {
82
				/** @var ChangeSet $item */
83
				return $item->canView();
84
			});
85
	}
86
87
	/**
88
	 * Safely get a DataObject from a client-supplied ID and ClassName, checking: argument
89
	 * validity; existence; and canView permissions.
90
	 *
91
	 * @param int $id The ID of the DataObject
92
	 * @param string $class The Class of the DataObject
93
	 * @return DataObject The referenced DataObject
94
	 * @throws SS_HTTPResponse_Exception
95
	 */
96
	protected function getObject($id, $class) {
97
		$id = (int)$id;
98
		$class = ClassInfo::class_name($class);
99
100
		if (!$class || !is_subclass_of($class, 'DataObject') || !Object::has_extension($class, 'Versioned')) {
101
			$this->editForm->httpError(400, _t(
102
				'AddToCampaign.ErrorGeneral',
103
				'We apologise, but there was an error'
104
			));
105
			return null;
106
		}
107
108
		$object = DataObject::get($class)->byID($id);
109
110
		if (!$object) {
111
			$this->editForm->httpError(404, _t(
112
				'AddToCampaign.ErrorNotFound',
113
				'That {Type} couldn\'t be found',
114
				'',
115
				['Type' => $class]
116
			));
117
			return null;
118
		}
119
120
		if (!$object->canView()) {
121
			$this->editForm->httpError(403, _t(
122
					'AddToCampaign.ErrorItemPermissionDenied',
123
					'It seems you don\'t have the necessary permissions to add {ObjectTitle} to a campaign',
124
					'',
125
					['ObjectTitle' => $object->Title]
126
				)
127
			);
128
			return null;
129
		}
130
131
		return $object;
132
	}
133
134
	/**
135
	 * Builds a Form that mirrors the parent editForm, but with an extra field to collect the ChangeSet ID
136
	 *
137
	 * @param DataObject $object The object we're going to be adding to whichever ChangeSet is chosen
138
	 * @return Form
139
	 */
140
	public function Form($object) {
141
		$inChangeSets = array_unique(ChangeSetItem::get_for_object($object)->column('ChangeSetID'));
142
		$changeSets = $this->getAvailableChangeSets()->map();
143
144
		$campaignDropdown = DropdownField::create('Campaign', '', $changeSets);
145
		$campaignDropdown->setEmptyString(_t('Campaigns.AddToCampaign', 'Select a Campaign'));
146
		$campaignDropdown->addExtraClass('noborder');
147
		$campaignDropdown->setDisabledItems($inChangeSets);
148
149
		$fields = new FieldList([
150
			$campaignDropdown,
151
			HiddenField::create('ID', null, $this->data['ID']),
152
			HiddenField::create('ClassName', null, $this->data['ClassName'])
153
		]);
154
155
		$form = new Form(
156
			$this->editForm->getController(),
0 ignored issues
show
Bug introduced by
It seems like $this->editForm->getController() can be null; however, __construct() 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...
157
			$this->editForm->getName(),
158
			new FieldList(
159
				$header = new CompositeField(
160
					new LiteralField(
161
						'Heading',
162
						sprintf('<h3>%s</h3>', _t('Campaigns.AddToCampaign', 'Add To Campaign'))
163
					)
164
				),
165
166
				$content = new CompositeField($fields)
167
			),
168
			new FieldList(
169
				$action = AddToCampaignHandler_FormAction::create()
170
			)
171
		);
172
173
		$header->addExtraClass('add-to-campaign__header');
174
		$content->addExtraClass('add-to-campaign__content');
175
		$action->addExtraClass('add-to-campaign__action');
176
177
		$form->setHTMLID('Form_EditForm_AddToCampaign');
178
179
		$form->unsetValidator();
180
		$form->loadDataFrom($this->data);
181
		$form->addExtraClass('add-to-campaign__form');
182
183
		return $form;
184
	}
185
186
	/**
187
	 * Performs the actual action of adding the object to the ChangeSet, once the ChangeSet ID is known
188
	 *
189
	 * @param DataObject $object The object to add to the ChangeSet
190
	 * @param int $campaignID The ID of the ChangeSet to add $object to
191
	 * @return SS_HTTPResponse
192
	 * @throws SS_HTTPResponse_Exception
193
	 */
194
	public function addToCampaign($object, $campaignID) {
195
		/** @var ChangeSet $changeSet */
196
		$changeSet = ChangeSet::get()->byID($campaignID);
197
198
		if (!$changeSet) {
199
			$this->editForm->httpError(404, _t(
200
				'AddToCampaign.ErrorNotFound',
201
				'That {Type} couldn\'t be found',
202
				'',
203
				['Type' => 'Campaign']
204
			));
205
			return null;
206
		}
207
208
		if (!$changeSet->canEdit()) {
209
			$this->editForm->httpError(403, _t(
210
				'AddToCampaign.ErrorCampaignPermissionDenied',
211
				'It seems you don\'t have the necessary permissions to add {ObjectTitle} to {CampaignTitle}',
212
				'',
213
				['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]
214
			));
215
			return null;
216
		}
217
218
		$changeSet->addObject($object);
219
220
		if (Director::is_ajax()) {
221
			$response = new SS_HTTPResponse(_t(
222
				'AddToCampaign.Success',
223
				'Successfully added {ObjectTitle} to {CampaignTitle}',
224
				'',
225
				['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]
226
			), 200);
227
228
			$response->addHeader('Content-Type', 'text/plain; charset=utf-8');
229
			return $response;
230
		} else {
231
			return $this->editForm->getController()->redirectBack();
232
		}
233
	}
234
}
235
236
/**
237
 * A form action to return from geCMSActions or otherwise include in a CMS Edit Form that
238
 * has the right action name and CSS classes to trigger the AddToCampaignHandler.
239
 *
240
 * See SiteTree.php and CMSMain.php for an example of it's use
241
 */
242
class AddToCampaignHandler_FormAction extends FormAction {
243
244
	function __construct() {
245
		parent::__construct('addtocampaign', _t('CAMPAIGNS.ADDTOCAMPAIGN', 'Add to campaign'));
246
		$this->addExtraClass('add-to-campaign-action');
247
		$this->setValidationExempt(true);
248
	}
249
}
250