Completed
Push — master ( 382031...2e9003 )
by Damian
36s
created

CampaignAdmin::publishCampaign()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 36
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 36
rs 6.7272
cc 7
eloc 24
nc 6
nop 1
1
<?php
2
3
/**
4
 * Campaign section of the CMS
5
 *
6
 * @package framework
7
 * @subpackage admin
8
 */
9
class CampaignAdmin extends LeftAndMain implements PermissionProvider {
10
11
	private static $allowed_actions = [
12
		'set',
13
		'sets',
14
		'schema',
15
		'DetailEditForm',
16
		'readCampaigns',
17
		'createCampaign',
18
		'readCampaign',
19
		'updateCampaign',
20
		'deleteCampaign',
21
		'publishCampaign',
22
	];
23
24
	private static $menu_priority = 11;
25
26
	private static $menu_title = 'Campaigns';
27
28
	private static $url_handlers = [
29
		'GET sets' => 'readCampaigns',
30
		'POST set/$ID/publish' => 'publishCampaign',
31
		'POST set/$ID' => 'createCampaign',
32
		'GET set/$ID/$Name' => 'readCampaign',
33
		'PUT set/$ID' => 'updateCampaign',
34
		'DELETE set/$ID' => 'deleteCampaign',
35
	];
36
37
	private static $url_segment = 'campaigns';
38
39
	/**
40
	 * Size of thumbnail width
41
	 *
42
	 * @config
43
	 * @var int
44
	 */
45
	private static $thumbnail_width = 64;
46
47
	/**
48
	 * Size of thumbnail height
49
	 *
50
	 * @config
51
	 * @var int
52
	 */
53
	private static $thumbnail_height = 64;
54
55
	public function getClientConfig() {
56
		$urlSegment = Config::inst()->get($this->class, 'url_segment');
57
58
		return array_merge(parent::getClientConfig(), [
59
			'forms' => [
60
				// TODO Use schemaUrl instead
61
				'editForm' => [
62
					'schemaUrl' => $this->Link('schema/EditForm')
63
				]
64
			],
65
			'campaignViewRoute' => $urlSegment . '/:type?/:id?/:view?',
66
			'itemListViewEndpoint' => $this->Link('set/:id/show'),
67
			'publishEndpoint' => [
68
				'url' => $this->Link('set/:id/publish'),
69
				'method' => 'post'
70
			]
71
		]);
72
	}
73
74
	public function schema($request) {
75
		// TODO Hardcoding schema until we can get GridField to generate a schema dynamically
76
		$json = <<<JSON
77
{
78
	"id": "EditForm",
79
	"schema": {
80
		"name": "EditForm",
81
		"id": "EditForm",
82
		"action": "schema",
83
		"method": "GET",
84
		"schema_url": "admin\/campaigns\/schema\/EditForm",
85
		"attributes": {
86
			"id": "Form_EditForm",
87
			"action": "admin\/campaigns\/EditForm",
88
			"method": "POST",
89
			"enctype": "multipart\/form-data",
90
			"target": null,
91
			"class": "cms-edit-form CampaignAdmin LeftAndMain"
92
		},
93
		"data": [],
94
		"fields": [{
95
			"name": "ID",
96
			"id": "Form_EditForm_ID",
97
			"type": "Hidden",
98
			"component": null,
99
			"holder_id": null,
100
			"title": false,
101
			"source": null,
102
			"extraClass": "hidden nolabel",
103
			"description": null,
104
			"rightTitle": null,
105
			"leftTitle": null,
106
			"readOnly": false,
107
			"disabled": false,
108
			"customValidationMessage": "",
109
			"attributes": [],
110
			"data": []
111
		}, {
112
			"name": "ChangeSets",
113
			"id": "Form_EditForm_ChangeSets",
114
			"type": "Custom",
115
			"component": "GridField",
116
			"holder_id": null,
117
			"title": "Campaigns",
118
			"source": null,
119
			"extraClass": null,
120
			"description": null,
121
			"rightTitle": null,
122
			"leftTitle": null,
123
			"readOnly": false,
124
			"disabled": false,
125
			"customValidationMessage": "",
126
			"attributes": [],
127
			"data": {
128
				"recordType": "ChangeSet",
129
				"collectionReadEndpoint": {
130
					"url": "admin\/campaigns\/sets",
131
					"method": "GET"
132
				},
133
				"itemReadEndpoint": {
134
					"url": "admin\/campaigns\/set\/:id",
135
					"method": "GET"
136
				},
137
				"itemUpdateEndpoint": {
138
					"url": "admin\/campaigns\/set\/:id",
139
					"method": "PUT"
140
				},
141
				"itemCreateEndpoint": {
142
					"url": "admin\/campaigns\/set\/:id",
143
					"method": "POST"
144
				},
145
				"itemDeleteEndpoint": {
146
					"url": "admin\/campaigns\/set\/:id",
147
					"method": "DELETE"
148
				},
149
				"editFormSchemaEndpoint": "admin\/campaigns\/schema\/DetailEditForm",
150
				"columns": [
151
					{"name": "Title", "field": "Name"},
152
					{"name": "Changes", "field": "_embedded.ChangeSetItems.length"},
153
					{"name": "Description", "field": "Description"}
154
				]
155
			}
156
		}, {
157
			"name": "SecurityID",
158
			"id": "Form_EditForm_SecurityID",
159
			"type": "Hidden",
160
			"component": null,
161
			"holder_id": null,
162
			"title": "Security ID",
163
			"source": null,
164
			"extraClass": "hidden",
165
			"description": null,
166
			"rightTitle": null,
167
			"leftTitle": null,
168
			"readOnly": false,
169
			"disabled": false,
170
			"customValidationMessage": "",
171
			"attributes": [],
172
			"data": []
173
		}],
174
		"actions": []
175
	}
176
}
177
JSON;
178
179
		$formName = $request->param('ID');
180
		if($formName == 'EditForm') {
181
			$response = $this->getResponse();
182
			$response->addHeader('Content-Type', 'application/json');
183
			$response->setBody($json);
184
			return $response;
185
		} else {
186
			return parent::schema($request);
187
		}
188
	}
189
190
	/**
191
	 * REST endpoint to create a campaign.
192
	 *
193
	 * @param SS_HTTPRequest $request
194
	 *
195
	 * @return SS_HTTPResponse
196
	 */
197
	public function createCampaign(SS_HTTPRequest $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
198
		$response = new SS_HTTPResponse();
199
		$response->addHeader('Content-Type', 'application/json');
200
		$response->setBody(Convert::raw2json(['campaign' => 'create']));
201
202
		// TODO Implement permission check and data creation
203
204
		return $response;
205
	}
206
207
	/**
208
	 * REST endpoint to get a list of campaigns.
209
	 *
210
	 * @param SS_HTTPRequest $request
211
	 *
212
	 * @return SS_HTTPResponse
213
	 */
214
	public function readCampaigns(SS_HTTPRequest $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
215
		$response = new SS_HTTPResponse();
216
		$response->addHeader('Content-Type', 'application/json');
217
		$hal = $this->getListResource();
218
		$response->setBody(Convert::array2json($hal));
219
		return $response;
220
	}
221
222
	/**
223
	 * Get list contained as a hal wrapper
224
	 *
225
	 * @return array
226
	 */
227
	protected function getListResource() {
228
		$items = $this->getListItems();
229
		$count = $items->count();
230
		$hal = [
231
			'count' => $count,
232
			'total' => $count,
233
			'_links' => [
234
				'self' => [
235
					'href' => $this->Link('items')
236
				]
237
			],
238
			'_embedded' => ['ChangeSets' => []]
239
		];
240
		foreach($items as $item) {
241
			/** @var ChangeSet $item */
242
			$resource = $this->getChangeSetResource($item);
243
			$hal['_embedded']['ChangeSets'][] = $resource;
244
		}
245
		return $hal;
246
	}
247
248
	/**
249
	 * Build item resource from a changeset
250
	 *
251
	 * @param ChangeSet $changeSet
252
	 * @return array
253
	 */
254
	protected function getChangeSetResource(ChangeSet $changeSet) {
255
		$hal = [
256
			'_links' => [
257
				'self' => [
258
					'href' => $this->SetLink($changeSet->ID)
259
				]
260
			],
261
			'ID' => $changeSet->ID,
262
			'Name' => $changeSet->Name,
263
			'Description' => $changeSet->getDescription(),
264
			'Created' => $changeSet->Created,
265
			'LastEdited' => $changeSet->LastEdited,
266
			'State' => $changeSet->State,
267
			'canEdit' => $changeSet->canEdit(),
268
			'canPublish' => $changeSet->canPublish(),
269
			'_embedded' => ['ChangeSetItems' => []]
270
		];
271
		foreach($changeSet->Changes() as $changeSetItem) {
272
			if(!$changeSetItem) {
273
				continue;
274
			}
275
276
			/** @var ChangesetItem $changeSetItem */
277
			$resource = $this->getChangeSetItemResource($changeSetItem);
278
			$hal['_embedded']['ChangeSetItems'][] = $resource;
279
		}
280
		return $hal;
281
	}
282
283
	/**
284
	 * Build item resource from a changesetitem
285
	 *
286
	 * @param ChangeSetItem $changeSetItem
287
	 * @return array
288
	 */
289
	protected function getChangeSetItemResource(ChangeSetItem $changeSetItem) {
290
		$baseClass = ClassInfo::baseDataClass($changeSetItem->ObjectClass);
291
		$baseSingleton = DataObject::singleton($baseClass);
292
		$thumbnailWidth = (int)$this->config()->thumbnail_width;
293
		$thumbnailHeight = (int)$this->config()->thumbnail_height;
294
		$hal = [
295
			'_links' => [
296
				'self' => [
297
					'href' => $this->ItemLink($changeSetItem->ID)
298
				]
299
			],
300
			'ID' => $changeSetItem->ID,
301
			'Created' => $changeSetItem->Created,
302
			'LastEdited' => $changeSetItem->LastEdited,
303
			'Title' => $changeSetItem->getTitle(),
304
			'ChangeType' => $changeSetItem->getChangeType(),
305
			'Added' => $changeSetItem->Added,
306
			'ObjectClass' => $changeSetItem->ObjectClass,
307
			'ObjectID' => $changeSetItem->ObjectID,
308
			'BaseClass' => $baseClass,
309
			'Singular' => $baseSingleton->i18n_singular_name(),
310
			'Plural' => $baseSingleton->i18n_plural_name(),
311
			'Thumbnail' => $changeSetItem->ThumbnailURL($thumbnailWidth, $thumbnailHeight),
312
		];
313
		// Depending on whether the object was added implicitly or explicitly, set
314
		// other related objects.
315
		if($changeSetItem->Added === ChangeSetItem::IMPLICITLY) {
316
			$referencedItems = $changeSetItem->ReferencedBy();
317
			$referencedBy = [];
318
			foreach($referencedItems as $referencedItem) {
319
				$referencedBy[] = [
320
					'href' => $this->SetLink($referencedItem->ID)
321
				];
322
			}
323
			if($referencedBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $referencedBy 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...
324
				$hal['_links']['referenced_by'] = $referencedBy;
325
			}
326
		}
327
328
		return $hal;
329
	}
330
331
332
333
	/**
334
	 * Gets viewable list of campaigns
335
	 *
336
	 * @return SS_List
337
	 */
338
	protected function getListItems() {
339
		return ChangeSet::get()
340
			->filter('State', ChangeSet::STATE_OPEN)
341
			->filterByCallback(function($item) {
342
				return ($item->canView());
343
			});
344
	}
345
346
347
	/**
348
	 * REST endpoint to get a campaign.
349
	 *
350
	 * @param SS_HTTPRequest $request
351
	 *
352
	 * @return SS_HTTPResponse
353
	 */
354
	public function readCampaign(SS_HTTPRequest $request) {
355
		$response = new SS_HTTPResponse();
356
357
		if ($request->getHeader('Accept') == 'text/json') {
358
			$response->addHeader('Content-Type', 'application/json');
359
			$changeSet = ChangeSet::get()->byId($request->param('ID'));
360
361
			switch ($request->param('Name')) {
362
				case "edit":
363
					$response->setBody('{"message":"show the edit view"}');
364
					break;
365
				case "show":
366
					$response->setBody(Convert::raw2json($this->getChangeSetResource($changeSet)));
367
					break;
368
				default:
369
					$response->setBody('{"message":"404"}');
370
			}
371
372
			return $response;
373
374
		} else {
375
			return $this->index($request);
376
		}
377
	}
378
379
	/**
380
	 * REST endpoint to update a campaign.
381
	 *
382
	 * @param SS_HTTPRequest $request
383
	 *
384
	 * @return SS_HTTPResponse
385
	 */
386
	public function updateCampaign(SS_HTTPRequest $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
387
		$response = new SS_HTTPResponse();
388
		$response->addHeader('Content-Type', 'application/json');
389
		$response->setBody(Convert::raw2json(['campaign' => 'update']));
390
391
		// TODO Implement data update and permission checks
392
393
		return $response;
394
	}
395
396
	/**
397
	 * REST endpoint to delete a campaign.
398
	 *
399
	 * @param SS_HTTPRequest $request
400
	 *
401
	 * @return SS_HTTPResponse
402
	 */
403
	public function deleteCampaign(SS_HTTPRequest $request) {
404
		$id = $request->param('ID');
405
		if (!$id || !is_numeric($id)) {
406
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
407
				->addHeader('Content-Type', 'application/json');
408
		}
409
410
		$record = ChangeSet::get()->byID($id);
411
		if(!$record) {
412
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
413
				->addHeader('Content-Type', 'application/json');
414
		}
415
416
		if(!$record->canDelete()) {
417
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
418
				->addHeader('Content-Type', 'application/json');
419
		}
420
421
		$record->delete();
422
423
		return (new SS_HTTPResponse('', 204));
424
	}
425
426
	/**
427
	 * REST endpoint to publish a {@link ChangeSet} and all of its items.
428
	 *
429
	 * @param SS_HTTPRequest $request
430
	 *
431
	 * @return SS_HTTPResponse
432
	 */
433
	public function publishCampaign(SS_HTTPRequest $request) {
434
		// Protect against CSRF on destructive action
435
		if(!SecurityToken::inst()->checkRequest($request)) {
436
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
437
				->addHeader('Content-Type', 'application/json');
438
		}
439
440
		$id = $request->param('ID');
441
		if(!$id || !is_numeric($id)) {
442
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
443
				->addHeader('Content-Type', 'application/json');
444
		}
445
446
		$record = ChangeSet::get()->byID($id);
447
		if(!$record) {
448
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
449
				->addHeader('Content-Type', 'application/json');
450
		}
451
452
		if(!$record->canPublish()) {
453
			return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
454
				->addHeader('Content-Type', 'application/json');
455
		}
456
457
		try {
458
			$record->publish();
459
		} catch(LogicException $e) {
460
			return (new SS_HTTPResponse(json_encode(['status' => 'error', 'message' => $e->getMessage()]), 401))
461
				->addHeader('Content-Type', 'application/json');
462
		}
463
464
		return (new SS_HTTPResponse(
465
			Convert::raw2json($this->getChangeSetResource($record)),
0 ignored issues
show
Compatibility introduced by
$record of type object<DataObject> is not a sub-type of object<ChangeSet>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
466
			200
467
		))->addHeader('Content-Type', 'application/json');
468
	}
469
470
	/**
471
	 * @todo Use GridFieldDetailForm once it can handle structured data and form schemas
472
	 *
473
	 * @return Form
474
	 */
475
	public function getDetailEditForm() {
476
		return Form::create(
477
			$this,
478
			'DetailEditForm',
479
			ChangeSet::singleton()->getCMSFields(),
480
			FieldList::create(
481
				FormAction::create('save', 'Save')
482
			)
483
		);
484
	}
485
486
	/**
487
	 * Gets user-visible url to edit a specific {@see ChangeSet}
488
	 *
489
	 * @param $itemID
490
	 * @return string
491
	 */
492
	public function SetLink($itemID) {
493
		return Controller::join_links(
494
			$this->Link('set'),
495
			$itemID
496
		);
497
	}
498
499
	/**
500
	 * Gets user-visible url to edit a specific {@see ChangeSetItem}
501
	 *
502
	 * @param int $itemID
503
	 * @return string
504
	 */
505
	public function ItemLink($itemID) {
506
		return Controller::join_links(
507
			$this->Link('item'),
508
			$itemID
509
		);
510
	}
511
512
	/**
513
	 *
514
	 */
515
	public function FindReferencedChanges() {
516
517
	}
518
519
}
520