Completed
Push — master ( bbb282...43d0b8 )
by Daniel
25s
created

AddToCampaignHandler   B

Complexity

Total Complexity 15

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 205
rs 7.8571
wmc 15
lcom 1
cbo 17

5 Methods

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