Completed
Push — master ( f7919c...871ad6 )
by Sean
04:15
created

DNRoot::get_logged_in_links()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
/**
4
 * God controller for the deploynaut interface
5
 *
6
 * @package deploynaut
7
 * @subpackage control
8
 */
9
class DNRoot extends Controller implements PermissionProvider, TemplateGlobalProvider {
0 ignored issues
show
Coding Style introduced by
The property $_project_cache is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $allowed_actions is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $url_handlers is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $support_links is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $platform_specific_strings is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $action_types is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $logged_in_links is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
10
11
	/**
12
	 * @const string - action type for actions that perform deployments
13
	 */
14
	const ACTION_DEPLOY = 'deploy';
15
16
	/**
17
	 * @const string - action type for actions that manipulate snapshots
18
	 */
19
	const ACTION_SNAPSHOT = 'snapshot';
20
21
	const ACTION_ENVIRONMENTS = 'createenv';
22
23
	const PROJECT_OVERVIEW = 'overview';
24
25
	/**
26
	 * Allow advanced options on deployments
27
	 */
28
	const DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS = 'DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS';
29
30
	const ALLOW_PROD_DEPLOYMENT = 'ALLOW_PROD_DEPLOYMENT';
31
32
	const ALLOW_NON_PROD_DEPLOYMENT = 'ALLOW_NON_PROD_DEPLOYMENT';
33
34
	const ALLOW_PROD_SNAPSHOT = 'ALLOW_PROD_SNAPSHOT';
35
36
	const ALLOW_NON_PROD_SNAPSHOT = 'ALLOW_NON_PROD_SNAPSHOT';
37
38
	const ALLOW_CREATE_ENVIRONMENT = 'ALLOW_CREATE_ENVIRONMENT';
39
40
	/**
41
	 * @var array
42
	 */
43
	protected static $_project_cache = [];
44
45
	/**
46
	 * @var DNData
47
	 */
48
	protected $data;
49
50
	/**
51
	 * @var string
52
	 */
53
	private $actionType = self::ACTION_DEPLOY;
54
55
	/**
56
	 * @var array
57
	 */
58
	private static $allowed_actions = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
59
		'projects',
60
		'nav',
61
		'update',
62
		'project',
63
		'toggleprojectstar',
64
		'branch',
65
		'environment',
66
		'createenvlog',
67
		'createenv',
68
		'getDeployForm',
69
		'doDeploy',
70
		'deploy',
71
		'deploylog',
72
		'abortDeploy',
73
		'getDataTransferForm',
74
		'transfer',
75
		'transferlog',
76
		'snapshots',
77
		'createsnapshot',
78
		'snapshotslog',
79
		'uploadsnapshot',
80
		'getCreateEnvironmentForm',
81
		'getUploadSnapshotForm',
82
		'getPostSnapshotForm',
83
		'getDataTransferRestoreForm',
84
		'getDeleteForm',
85
		'getMoveForm',
86
		'restoresnapshot',
87
		'deletesnapshot',
88
		'movesnapshot',
89
		'postsnapshotsuccess',
90
		'gitRevisions',
91
		'deploySummary',
92
		'startDeploy'
93
	];
94
95
	/**
96
	 * URL handlers pretending that we have a deep URL structure.
97
	 */
98
	private static $url_handlers = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
99
		'project/$Project/environment/$Environment/DeployForm' => 'getDeployForm',
100
		'project/$Project/createsnapshot/DataTransferForm' => 'getDataTransferForm',
101
		'project/$Project/DataTransferForm' => 'getDataTransferForm',
102
		'project/$Project/DataTransferRestoreForm' => 'getDataTransferRestoreForm',
103
		'project/$Project/DeleteForm' => 'getDeleteForm',
104
		'project/$Project/MoveForm' => 'getMoveForm',
105
		'project/$Project/UploadSnapshotForm' => 'getUploadSnapshotForm',
106
		'project/$Project/PostSnapshotForm' => 'getPostSnapshotForm',
107
		'project/$Project/environment/$Environment/deploy_summary' => 'deploySummary',
108
		'project/$Project/environment/$Environment/git_revisions' => 'gitRevisions',
109
		'project/$Project/environment/$Environment/start-deploy' => 'startDeploy',
110
		'project/$Project/environment/$Environment/deploy/$Identifier/log' => 'deploylog',
111
		'project/$Project/environment/$Environment/deploy/$Identifier/abort-deploy' => 'abortDeploy',
112
		'project/$Project/environment/$Environment/deploy/$Identifier' => 'deploy',
113
		'project/$Project/transfer/$Identifier/log' => 'transferlog',
114
		'project/$Project/transfer/$Identifier' => 'transfer',
115
		'project/$Project/environment/$Environment' => 'environment',
116
		'project/$Project/createenv/$Identifier/log' => 'createenvlog',
117
		'project/$Project/createenv/$Identifier' => 'createenv',
118
		'project/$Project/CreateEnvironmentForm' => 'getCreateEnvironmentForm',
119
		'project/$Project/branch' => 'branch',
120
		'project/$Project/build/$Build' => 'build',
121
		'project/$Project/restoresnapshot/$DataArchiveID' => 'restoresnapshot',
122
		'project/$Project/deletesnapshot/$DataArchiveID' => 'deletesnapshot',
123
		'project/$Project/movesnapshot/$DataArchiveID' => 'movesnapshot',
124
		'project/$Project/update' => 'update',
125
		'project/$Project/snapshots' => 'snapshots',
126
		'project/$Project/createsnapshot' => 'createsnapshot',
127
		'project/$Project/uploadsnapshot' => 'uploadsnapshot',
128
		'project/$Project/snapshotslog' => 'snapshotslog',
129
		'project/$Project/postsnapshotsuccess/$DataArchiveID' => 'postsnapshotsuccess',
130
		'project/$Project/star' => 'toggleprojectstar',
131
		'project/$Project' => 'project',
132
		'nav/$Project' => 'nav',
133
		'projects' => 'projects',
134
	];
135
136
	/**
137
	 * @var array
138
	 */
139
	private static $support_links = [];
0 ignored issues
show
Unused Code introduced by
The property $support_links is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
140
141
	/**
142
	 * @var array
143
	 */
144
	private static $logged_in_links = [];
0 ignored issues
show
Unused Code introduced by
The property $logged_in_links is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
145
146
	/**
147
	 * @var array
148
	 */
149
	private static $platform_specific_strings = [];
0 ignored issues
show
Unused Code introduced by
The property $platform_specific_strings is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
150
151
	/**
152
	 * @var array
153
	 */
154
	private static $action_types = [
0 ignored issues
show
Unused Code introduced by
The property $action_types is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
155
		self::ACTION_DEPLOY,
156
		self::ACTION_SNAPSHOT,
157
		self::PROJECT_OVERVIEW
158
	];
159
160
	/**
161
	 * Include requirements that deploynaut needs, such as javascript.
162
	 */
163
	public static function include_requirements() {
164
165
		// JS should always go to the bottom, otherwise there's the risk that Requirements
166
		// puts them halfway through the page to the nearest <script> tag. We don't want that.
167
		Requirements::set_force_js_to_bottom(true);
168
169
		// todo these should be bundled into the same JS as the others in "static" below.
170
		// We've deliberately not used combined_files as it can mess with some of the JS used
171
		// here and cause sporadic errors.
172
		Requirements::javascript('deploynaut/javascript/jquery.js');
173
		Requirements::javascript('deploynaut/javascript/bootstrap.js');
174
		Requirements::javascript('deploynaut/javascript/q.js');
175
		Requirements::javascript('deploynaut/javascript/tablefilter.js');
176
		Requirements::javascript('deploynaut/javascript/deploynaut.js');
177
178
		Requirements::javascript('deploynaut/javascript/bootstrap.file-input.js');
179
		Requirements::javascript('deploynaut/thirdparty/select2/dist/js/select2.min.js');
180
		Requirements::javascript('deploynaut/javascript/selectize.js');
181
		Requirements::javascript('deploynaut/thirdparty/bootstrap-switch/dist/js/bootstrap-switch.min.js');
182
		Requirements::javascript('deploynaut/javascript/material.js');
183
184
		// Load the buildable dependencies only if not loaded centrally.
185
		if (!is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'static')) {
186
			if (\Director::isDev()) {
187
				\Requirements::javascript('deploynaut/static/bundle-debug.js');
188
			} else {
189
				\Requirements::javascript('deploynaut/static/bundle.js');
190
			}
191
		}
192
193
		Requirements::css('deploynaut/static/style.css');
194
	}
195
196
	/**
197
	 * @return ArrayList
198
	 */
199
	public static function get_support_links() {
200
		$supportLinks = self::config()->support_links;
201
		if ($supportLinks) {
202
			return new ArrayList($supportLinks);
203
		}
204
	}
205
206
	/**
207
	 * @return ArrayList
208
	 */
209
	public static function get_logged_in_links() {
210
		$loggedInLinks = self::config()->logged_in_links;
211
		if ($loggedInLinks) {
212
			return new ArrayList($loggedInLinks);
213
		}
214
	}
215
216
	/**
217
	 * @return array
218
	 */
219
	public static function get_template_global_variables() {
220
		return [
221
			'RedisUnavailable' => 'RedisUnavailable',
222
			'RedisWorkersCount' => 'RedisWorkersCount',
223
			'SidebarLinks' => 'SidebarLinks',
224
			'SupportLinks' => 'get_support_links',
225
			'LoggedInLinks' => 'get_logged_in_links',
226
		];
227
	}
228
229
	/**
230
	 */
231
	public function init() {
232
		parent::init();
233
234
		if (!Member::currentUser() && !Session::get('AutoLoginHash')) {
235
			return Security::permissionFailure();
236
		}
237
238
		// Block framework jquery
239
		Requirements::block(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
240
241
		self::include_requirements();
242
	}
243
244
	/**
245
	 * @return string
246
	 */
247
	public function Link() {
248
		return "naut/";
249
	}
250
251
	/**
252
	 * Actions
253
	 *
254
	 * @param \SS_HTTPRequest $request
255
	 * @return \SS_HTTPResponse
256
	 */
257
	public function index(\SS_HTTPRequest $request) {
258
		return $this->redirect($this->Link() . 'projects/');
259
	}
260
261
	/**
262
	 * Action
263
	 *
264
	 * @param \SS_HTTPRequest $request
265
	 * @return string - HTML
266
	 */
267
	public function projects(\SS_HTTPRequest $request) {
268
		// Performs canView permission check by limiting visible projects in DNProjectsList() call.
269
		return $this->customise([
270
			'Title' => 'Projects',
271
		])->render();
272
	}
273
274
	/**
275
	 * @param \SS_HTTPRequest $request
276
	 * @return HTMLText
277
	 */
278
	public function nav(\SS_HTTPRequest $request) {
279
		return $this->renderWith('Nav');
280
	}
281
282
	/**
283
	 * Return a link to the navigation template used for AJAX requests.
284
	 * @return string
285
	 */
286
	public function NavLink() {
287
		$currentProject = $this->getCurrentProject();
288
		$projectName = $currentProject ? $currentProject->Name : null;
289
		return Controller::join_links(Director::absoluteBaseURL(), 'naut', 'nav', $projectName);
290
	}
291
292
	/**
293
	 * Action
294
	 *
295
	 * @param \SS_HTTPRequest $request
296
	 * @return SS_HTTPResponse - HTML
297
	 */
298
	public function snapshots(\SS_HTTPRequest $request) {
299
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
300
		return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots');
301
	}
302
303
	/**
304
	 * Action
305
	 *
306
	 * @param \SS_HTTPRequest $request
307
	 * @return string - HTML
308
	 */
309 View Code Duplication
	public function createsnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
311
312
		// Performs canView permission check by limiting visible projects
313
		$project = $this->getCurrentProject();
314
		if (!$project) {
315
			return $this->project404Response();
316
		}
317
318
		if (!$project->canBackup()) {
319
			return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
320
		}
321
322
		return $this->customise([
323
			'Title' => 'Create Data Snapshot',
324
			'SnapshotsSection' => 1,
325
			'DataTransferForm' => $this->getDataTransferForm($request)
326
		])->render();
327
	}
328
329
	/**
330
	 * Action
331
	 *
332
	 * @param \SS_HTTPRequest $request
333
	 * @return string - HTML
334
	 */
335 View Code Duplication
	public function uploadsnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
336
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
337
338
		// Performs canView permission check by limiting visible projects
339
		$project = $this->getCurrentProject();
340
		if (!$project) {
341
			return $this->project404Response();
342
		}
343
344
		if (!$project->canUploadArchive()) {
345
			return new SS_HTTPResponse("Not allowed to upload", 401);
346
		}
347
348
		return $this->customise([
349
			'SnapshotsSection' => 1,
350
			'UploadSnapshotForm' => $this->getUploadSnapshotForm($request),
351
			'PostSnapshotForm' => $this->getPostSnapshotForm($request)
352
		])->render();
353
	}
354
355
	/**
356
	 * Return the upload limit for snapshot uploads
357
	 * @return string
358
	 */
359
	public function UploadLimit() {
360
		return File::format_size(min(
361
			File::ini2bytes(ini_get('upload_max_filesize')),
362
			File::ini2bytes(ini_get('post_max_size'))
363
		));
364
	}
365
366
	/**
367
	 * Construct the upload form.
368
	 *
369
	 * @param \SS_HTTPRequest $request
370
	 * @return Form
371
	 */
372
	public function getUploadSnapshotForm(\SS_HTTPRequest $request) {
373
		// Performs canView permission check by limiting visible projects
374
		$project = $this->getCurrentProject();
375
		if (!$project) {
376
			return $this->project404Response();
377
		}
378
379
		if (!$project->canUploadArchive()) {
380
			return new SS_HTTPResponse("Not allowed to upload", 401);
381
		}
382
383
		// Framing an environment as a "group of people with download access"
384
		// makes more sense to the user here, while still allowing us to enforce
385
		// environment specific restrictions on downloading the file later on.
386
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
387
			return $item->canUploadArchive();
388
		});
389
		$envsMap = [];
390
		foreach ($envs as $env) {
391
			$envsMap[$env->ID] = $env->Name;
392
		}
393
394
		$maxSize = min(File::ini2bytes(ini_get('upload_max_filesize')), File::ini2bytes(ini_get('post_max_size')));
395
		$fileField = DataArchiveFileField::create('ArchiveFile', 'File');
396
		$fileField->getValidator()->setAllowedExtensions(['sspak']);
397
		$fileField->getValidator()->setAllowedMaxFileSize(['*' => $maxSize]);
398
399
		$form = Form::create(
400
			$this,
401
			'UploadSnapshotForm',
402
			FieldList::create(
403
				$fileField,
404
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
405
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
406
					->setEmptyString('Select an environment')
407
			),
408
			FieldList::create(
409
				FormAction::create('doUploadSnapshot', 'Upload File')
410
					->addExtraClass('btn')
411
			),
412
			RequiredFields::create('ArchiveFile')
413
		);
414
415
		$form->disableSecurityToken();
416
		$form->addExtraClass('fields-wide');
417
		// Tweak the action so it plays well with our fake URL structure.
418
		$form->setFormAction($project->Link() . '/UploadSnapshotForm');
419
420
		return $form;
421
	}
422
423
	/**
424
	 * @param array $data
425
	 * @param Form $form
426
	 *
427
	 * @return bool|HTMLText|SS_HTTPResponse
428
	 */
429
	public function doUploadSnapshot($data, \Form $form) {
430
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
431
432
		// Performs canView permission check by limiting visible projects
433
		$project = $this->getCurrentProject();
434
		if (!$project) {
435
			return $this->project404Response();
436
		}
437
438
		$validEnvs = $project->DNEnvironmentList()
439
			->filterByCallback(function ($item) {
440
				return $item->canUploadArchive();
441
			});
442
443
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
444
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
445
		if (!$environment) {
446
			throw new LogicException('Invalid environment');
447
		}
448
449
		$this->validateSnapshotMode($data['Mode']);
450
451
		$dataArchive = DNDataArchive::create([
452
			'AuthorID' => Member::currentUserID(),
453
			'EnvironmentID' => $data['EnvironmentID'],
454
			'IsManualUpload' => true,
455
		]);
456
		// needs an ID and transfer to determine upload path
457
		$dataArchive->write();
458
		$dataTransfer = DNDataTransfer::create([
459
			'AuthorID' => Member::currentUserID(),
460
			'Mode' => $data['Mode'],
461
			'Origin' => 'ManualUpload',
462
			'EnvironmentID' => $data['EnvironmentID']
463
		]);
464
		$dataTransfer->write();
465
		$dataArchive->DataTransfers()->add($dataTransfer);
466
		$form->saveInto($dataArchive);
467
		$dataArchive->write();
468
		$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
469
470 View Code Duplication
		$cleanupFn = function () use ($workingDir, $dataTransfer, $dataArchive) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
471
			$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
472
			$process->setTimeout(120);
473
			$process->run();
474
			$dataTransfer->delete();
475
			$dataArchive->delete();
476
		};
477
478
		// extract the sspak contents so we can inspect them
479
		try {
480
			$dataArchive->extractArchive($workingDir);
481
		} catch (Exception $e) {
482
			$cleanupFn();
483
			$form->sessionMessage(
484
				'There was a problem trying to open your snapshot for processing. Please try uploading again',
485
				'bad'
486
			);
487
			return $this->redirectBack();
488
		}
489
490
		// validate that the sspak contents match the declared contents
491
		$result = $dataArchive->validateArchiveContents();
492
		if (!$result->valid()) {
493
			$cleanupFn();
494
			$form->sessionMessage($result->message(), 'bad');
495
			return $this->redirectBack();
496
		}
497
498
		// fix file permissions of extracted sspak files then re-build the sspak
499
		try {
500
			$dataArchive->fixArchivePermissions($workingDir);
501
			$dataArchive->setArchiveFromFiles($workingDir);
502
		} catch (Exception $e) {
503
			$cleanupFn();
504
			$form->sessionMessage(
505
				'There was a problem processing your snapshot. Please try uploading again',
506
				'bad'
507
			);
508
			return $this->redirectBack();
509
		}
510
511
		// cleanup any extracted sspak contents lying around
512
		$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
513
		$process->setTimeout(120);
514
		$process->run();
515
516
		return $this->customise([
517
			'Project' => $project,
518
			'CurrentProject' => $project,
519
			'SnapshotsSection' => 1,
520
			'DataArchive' => $dataArchive,
521
			'DataTransferRestoreForm' => $this->getDataTransferRestoreForm($this->request, $dataArchive),
522
			'BackURL' => $project->Link('snapshots')
523
		])->renderWith(['DNRoot_uploadsnapshot', 'DNRoot']);
524
	}
525
526
	/**
527
	 * @param \SS_HTTPRequest $request
528
	 * @return Form
529
	 */
530
	public function getPostSnapshotForm(\SS_HTTPRequest $request) {
531
		// Performs canView permission check by limiting visible projects
532
		$project = $this->getCurrentProject();
533
		if (!$project) {
534
			return $this->project404Response();
535
		}
536
537
		if (!$project->canUploadArchive()) {
538
			return new SS_HTTPResponse("Not allowed to upload", 401);
539
		}
540
541
		// Framing an environment as a "group of people with download access"
542
		// makes more sense to the user here, while still allowing us to enforce
543
		// environment specific restrictions on downloading the file later on.
544
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
545
			return $item->canUploadArchive();
546
		});
547
		$envsMap = [];
548
		foreach ($envs as $env) {
549
			$envsMap[$env->ID] = $env->Name;
550
		}
551
552
		$form = Form::create(
553
			$this,
554
			'PostSnapshotForm',
555
			FieldList::create(
556
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
557
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
558
					->setEmptyString('Select an environment')
559
			),
560
			FieldList::create(
561
				FormAction::create('doPostSnapshot', 'Submit request')
562
					->addExtraClass('btn')
563
			),
564
			RequiredFields::create('File')
565
		);
566
567
		$form->disableSecurityToken();
568
		$form->addExtraClass('fields-wide');
569
		// Tweak the action so it plays well with our fake URL structure.
570
		$form->setFormAction($project->Link() . '/PostSnapshotForm');
571
572
		return $form;
573
	}
574
575
	/**
576
	 * @param array $data
577
	 * @param Form $form
578
	 *
579
	 * @return SS_HTTPResponse
580
	 */
581
	public function doPostSnapshot($data, $form) {
582
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
583
584
		$project = $this->getCurrentProject();
585
		if (!$project) {
586
			return $this->project404Response();
587
		}
588
589
		$validEnvs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
590
			return $item->canUploadArchive();
591
		});
592
593
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
594
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
595
		if (!$environment) {
596
			throw new LogicException('Invalid environment');
597
		}
598
599
		$dataArchive = DNDataArchive::create([
600
			'UploadToken' => DNDataArchive::generate_upload_token(),
601
		]);
602
		$form->saveInto($dataArchive);
603
		$dataArchive->write();
604
605
		return $this->redirect(Controller::join_links(
606
			$project->Link(),
607
			'postsnapshotsuccess',
608
			$dataArchive->ID
609
		));
610
	}
611
612
	/**
613
	 * Action
614
	 *
615
	 * @param \SS_HTTPRequest $request
616
	 * @return SS_HTTPResponse - HTML
617
	 */
618
	public function snapshotslog(\SS_HTTPRequest $request) {
619
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
620
		return $this->getCustomisedViewSection('SnapshotsSection', 'Snapshots log');
621
	}
622
623
	/**
624
	 * @param \SS_HTTPRequest $request
625
	 * @return SS_HTTPResponse|string
626
	 * @throws SS_HTTPResponse_Exception
627
	 */
628
	public function postsnapshotsuccess(\SS_HTTPRequest $request) {
629
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
630
631
		// Performs canView permission check by limiting visible projects
632
		$project = $this->getCurrentProject();
633
		if (!$project) {
634
			return $this->project404Response();
635
		}
636
637
		if (!$project->canUploadArchive()) {
638
			return new SS_HTTPResponse("Not allowed to upload", 401);
639
		}
640
641
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
642
		if (!$dataArchive) {
643
			return new SS_HTTPResponse("Archive not found.", 404);
644
		}
645
646
		if (!$dataArchive->canRestore()) {
647
			throw new SS_HTTPResponse_Exception('Not allowed to restore archive', 403);
648
		}
649
650
		return $this->render([
651
			'Title' => 'How to send us your Data Snapshot by post',
652
			'DataArchive' => $dataArchive,
653
			'Address' => Config::inst()->get('Deploynaut', 'snapshot_post_address'),
654
			'BackURL' => $project->Link(),
655
		]);
656
	}
657
658
	/**
659
	 * @param \SS_HTTPRequest $request
660
	 * @return \SS_HTTPResponse
661
	 */
662
	public function project(\SS_HTTPRequest $request) {
663
		$this->setCurrentActionType(self::PROJECT_OVERVIEW);
664
		return $this->getCustomisedViewSection('ProjectOverview', '', ['IsAdmin' => Permission::check('ADMIN')]);
665
	}
666
667
	/**
668
	 * This action will star / unstar a project for the current member
669
	 *
670
	 * @param \SS_HTTPRequest $request
671
	 *
672
	 * @return SS_HTTPResponse
673
	 */
674
	public function toggleprojectstar(\SS_HTTPRequest $request) {
675
		$project = $this->getCurrentProject();
676
		if (!$project) {
677
			return $this->project404Response();
678
		}
679
680
		$member = Member::currentUser();
681
		if ($member === null) {
682
			return $this->project404Response();
683
		}
684
		$favProject = $member->StarredProjects()
685
			->filter('DNProjectID', $project->ID)
686
			->first();
687
688
		if ($favProject) {
689
			$member->StarredProjects()->remove($favProject);
690
		} else {
691
			$member->StarredProjects()->add($project);
692
		}
693
694
		if (!$request->isAjax()) {
695
			return $this->redirectBack();
696
		}
697
	}
698
699
	/**
700
	 * @param \SS_HTTPRequest $request
701
	 * @return \SS_HTTPResponse
702
	 */
703
	public function branch(\SS_HTTPRequest $request) {
704
		$project = $this->getCurrentProject();
705
		if (!$project) {
706
			return $this->project404Response();
707
		}
708
709
		$branchName = $request->getVar('name');
710
		$branch = $project->DNBranchList()->byName($branchName);
711
		if (!$branch) {
712
			return new SS_HTTPResponse("Branch '" . Convert::raw2xml($branchName) . "' not found.", 404);
713
		}
714
715
		return $this->render([
716
			'CurrentBranch' => $branch,
717
		]);
718
	}
719
720
	/**
721
	 * @param \SS_HTTPRequest $request
722
	 * @return \SS_HTTPResponse
723
	 */
724
	public function environment(\SS_HTTPRequest $request) {
725
		// Performs canView permission check by limiting visible projects
726
		$project = $this->getCurrentProject();
727
		if (!$project) {
728
			return $this->project404Response();
729
		}
730
731
		// Performs canView permission check by limiting visible projects
732
		$env = $this->getCurrentEnvironment($project);
733
		if (!$env) {
734
			return $this->environment404Response();
735
		}
736
737
		return $this->render([
738
			'DNEnvironmentList' => $this->getCurrentProject()->DNEnvironmentList(),
739
			'Redeploy' => (bool) $request->getVar('redeploy')
740
		]);
741
	}
742
743
	/**
744
	 * Shows the creation log.
745
	 *
746
	 * @param \SS_HTTPRequest $request
747
	 * @return string
748
	 */
749
	public function createenv(\SS_HTTPRequest $request) {
750
		$params = $request->params();
751
		if ($params['Identifier']) {
752
			$record = DNCreateEnvironment::get()->byId($params['Identifier']);
753
754
			if (!$record || !$record->ID) {
755
				throw new SS_HTTPResponse_Exception('Create environment not found', 404);
756
			}
757
			if (!$record->canView()) {
758
				return Security::permissionFailure();
759
			}
760
761
			$project = $this->getCurrentProject();
762
			if (!$project) {
763
				return $this->project404Response();
764
			}
765
766
			if ($project->Name != $params['Project']) {
767
				throw new LogicException("Project in URL doesn't match this creation");
768
			}
769
770
			return $this->render([
771
				'CreateEnvironment' => $record,
772
			]);
773
		}
774
		return $this->render(['CurrentTitle' => 'Create an environment']);
775
	}
776
777
	public function createenvlog(\SS_HTTPRequest $request) {
778
		$params = $request->params();
779
		$env = DNCreateEnvironment::get()->byId($params['Identifier']);
780
781
		if (!$env || !$env->ID) {
782
			throw new SS_HTTPResponse_Exception('Log not found', 404);
783
		}
784
		if (!$env->canView()) {
785
			return Security::permissionFailure();
786
		}
787
788
		$project = $env->Project();
789
790
		if ($project->Name != $params['Project']) {
791
			throw new LogicException("Project in URL doesn't match this deploy");
792
		}
793
794
		$log = $env->log();
795
		if ($log->exists()) {
796
			$content = $log->content();
797
		} else {
798
			$content = 'Waiting for action to start';
799
		}
800
801
		return $this->sendResponse($env->ResqueStatus(), $content);
802
	}
803
804
	/**
805
	 * @param \SS_HTTPRequest $request
806
	 * @return Form
807
	 */
808
	public function getCreateEnvironmentForm(\SS_HTTPRequest $request = null) {
809
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
810
811
		$project = $this->getCurrentProject();
812
		if (!$project) {
813
			return $this->project404Response();
814
		}
815
816
		$envType = $project->AllowedEnvironmentType;
0 ignored issues
show
Documentation introduced by
The property AllowedEnvironmentType does not exist on object<DNProject>. 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...
817
		if (!$envType || !class_exists($envType)) {
818
			return null;
819
		}
820
821
		$backend = Injector::inst()->get($envType);
822
		if (!($backend instanceof EnvironmentCreateBackend)) {
823
			// Only allow this for supported backends.
824
			return null;
825
		}
826
827
		$fields = $backend->getCreateEnvironmentFields($project);
828
		if (!$fields) {
829
			return null;
830
		}
831
832
		if (!$project->canCreateEnvironments()) {
833
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
834
		}
835
836
		$form = Form::create(
837
			$this,
838
			'CreateEnvironmentForm',
839
			$fields,
840
			FieldList::create(
841
				FormAction::create('doCreateEnvironment', 'Create')
842
					->addExtraClass('btn')
843
			),
844
			$backend->getCreateEnvironmentValidator()
845
		);
846
847
		// Tweak the action so it plays well with our fake URL structure.
848
		$form->setFormAction($project->Link() . '/CreateEnvironmentForm');
849
850
		return $form;
851
	}
852
853
	/**
854
	 * @param array $data
855
	 * @param Form $form
856
	 *
857
	 * @return bool|HTMLText|SS_HTTPResponse
858
	 */
859
	public function doCreateEnvironment($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form 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...
860
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
861
862
		$project = $this->getCurrentProject();
863
		if (!$project) {
864
			return $this->project404Response();
865
		}
866
867
		if (!$project->canCreateEnvironments()) {
868
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
869
		}
870
871
		// Set the environment type so we know what we're creating.
872
		$data['EnvironmentType'] = $project->AllowedEnvironmentType;
0 ignored issues
show
Documentation introduced by
The property AllowedEnvironmentType does not exist on object<DNProject>. 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...
873
874
		$job = DNCreateEnvironment::create();
875
876
		$job->Data = serialize($data);
877
		$job->ProjectID = $project->ID;
878
		$job->write();
879
		$job->start();
880
881
		return $this->redirect($project->Link('createenv') . '/' . $job->ID);
882
	}
883
884
	/**
885
	 * Get the DNData object.
886
	 *
887
	 * @return DNData
888
	 */
889
	public function DNData() {
890
		return DNData::inst();
891
	}
892
893
	/**
894
	 * Provide a list of all projects.
895
	 *
896
	 * @return SS_List
897
	 */
898
	public function DNProjectList() {
899
		$memberId = Member::currentUserID();
900
		if (!$memberId) {
901
			return new ArrayList();
902
		}
903
904
		if (Permission::check('ADMIN')) {
905
			return DNProject::get();
906
		}
907
908
		$projects = Member::get()->filter('ID', $memberId)
909
			->relation('Groups')
910
			->relation('Projects');
911
912
		$this->extend('updateDNProjectList', $projects);
913
		return $projects;
914
	}
915
916
	/**
917
	 * @return ArrayList
918
	 */
919
	public function getPlatformSpecificStrings() {
920
		$strings = $this->config()->platform_specific_strings;
921
		if ($strings) {
922
			return new ArrayList($strings);
923
		}
924
	}
925
926
	/**
927
	 * Provide a list of all starred projects for the currently logged in member
928
	 *
929
	 * @return SS_List
930
	 */
931
	public function getStarredProjects() {
932
		$member = Member::currentUser();
933
		if ($member === null) {
934
			return new ArrayList();
935
		}
936
937
		$favProjects = $member->StarredProjects();
938
939
		$list = new ArrayList();
940
		foreach ($favProjects as $project) {
941
			if ($project->canView($member)) {
942
				$list->add($project);
943
			}
944
		}
945
		return $list;
946
	}
947
948
	/**
949
	 * Returns top level navigation of projects.
950
	 *
951
	 * @param int $limit
952
	 *
953
	 * @return ArrayList
954
	 */
955
	public function Navigation($limit = 5) {
956
		$navigation = new ArrayList();
957
958
		$currentProject = $this->getCurrentProject();
959
		$currentEnvironment = $this->getCurrentEnvironment();
960
		$actionType = $this->getCurrentActionType();
961
962
		$projects = $this->getStarredProjects();
963
		if ($projects->count() < 1) {
964
			$projects = $this->DNProjectList();
965
		} else {
966
			$limit = -1;
967
		}
968
969
		if ($projects->count() > 0) {
970
			$activeProject = false;
971
972
			if ($limit > 0) {
973
				$limitedProjects = $projects->limit($limit);
974
			} else {
975
				$limitedProjects = $projects;
976
			}
977
978
			foreach ($limitedProjects as $project) {
979
				$isActive = $currentProject && $currentProject->ID == $project->ID;
980
				if ($isActive) {
981
					$activeProject = true;
982
				}
983
984
				$isCurrentEnvironment = false;
985
				if ($project && $currentEnvironment) {
986
					$isCurrentEnvironment = (bool) $project->DNEnvironmentList()->find('ID', $currentEnvironment->ID);
987
				}
988
989
				$navigation->push([
990
					'Project' => $project,
991
					'IsCurrentEnvironment' => $isCurrentEnvironment,
992
					'IsActive' => $currentProject && $currentProject->ID == $project->ID,
993
					'IsOverview' => $actionType == self::PROJECT_OVERVIEW && !$isCurrentEnvironment && $currentProject->ID == $project->ID
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
994
				]);
995
			}
996
997
			// Ensure the current project is in the list
998
			if (!$activeProject && $currentProject) {
999
				$navigation->unshift([
1000
					'Project' => $currentProject,
1001
					'IsActive' => true,
1002
					'IsCurrentEnvironment' => $currentEnvironment,
1003
					'IsOverview' => $actionType == self::PROJECT_OVERVIEW && !$currentEnvironment
1004
				]);
1005
				if ($limit > 0 && $navigation->count() > $limit) {
1006
					$navigation->pop();
1007
				}
1008
			}
1009
		}
1010
1011
		return $navigation;
1012
	}
1013
1014
	/**
1015
	 * Construct the deployment form
1016
	 *
1017
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1018
	 *
1019
	 * @return Form
1020
	 */
1021
	public function getDeployForm($request = null) {
1022
1023
		// Performs canView permission check by limiting visible projects
1024
		$project = $this->getCurrentProject();
1025
		if (!$project) {
1026
			return $this->project404Response();
1027
		}
1028
1029
		// Performs canView permission check by limiting visible projects
1030
		$environment = $this->getCurrentEnvironment($project);
1031
		if (!$environment) {
1032
			return $this->environment404Response();
1033
		}
1034
1035
		if (!$environment->canDeploy()) {
1036
			return new SS_HTTPResponse("Not allowed to deploy", 401);
1037
		}
1038
1039
		// Generate the form
1040
		$form = new DeployForm($this, 'DeployForm', $environment, $project);
0 ignored issues
show
Deprecated Code introduced by
The class DeployForm has been deprecated with message: 2.0.0 - moved to Dispatchers and frontend Form for generating deployments from a specified commit

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
1041
1042
		// If this is an ajax request we don't want to submit the form - we just want to retrieve the markup.
1043
		if (
1044
			$request &&
1045
			!$request->requestVar('action_showDeploySummary') &&
1046
			$this->getRequest()->isAjax() &&
1047
			$this->getRequest()->isGET()
1048
		) {
1049
			// We can just use the URL we're accessing
1050
			$form->setFormAction($this->getRequest()->getURL());
1051
1052
			$body = json_encode(['Content' => $form->forAjaxTemplate()->forTemplate()]);
1053
			$this->getResponse()->addHeader('Content-Type', 'application/json');
1054
			$this->getResponse()->setBody($body);
1055
			return $body;
1056
		}
1057
1058
		$form->setFormAction($this->getRequest()->getURL() . '/DeployForm');
1059
		return $form;
1060
	}
1061
1062
	/**
1063
	 * @deprecated 2.0.0 - moved to GitDispatcher
1064
	 *
1065
	 * @param \SS_HTTPRequest $request
1066
	 *
1067
	 * @return SS_HTTPResponse|string
1068
	 */
1069
	public function gitRevisions(\SS_HTTPRequest $request) {
1070
1071
		// Performs canView permission check by limiting visible projects
1072
		$project = $this->getCurrentProject();
1073
		if (!$project) {
1074
			return $this->project404Response();
1075
		}
1076
1077
		// Performs canView permission check by limiting visible projects
1078
		$env = $this->getCurrentEnvironment($project);
1079
		if (!$env) {
1080
			return $this->environment404Response();
1081
		}
1082
1083
		$options = [];
1084
		foreach ($env->getSupportedOptions() as $option) {
1085
			$options[] = [
1086
				'name' => $option->getName(),
1087
				'title' => $option->getTitle(),
1088
				'defaultValue' => $option->getDefaultValue()
1089
			];
1090
		}
1091
1092
		$tabs = [];
1093
		$id = 0;
1094
		$data = [
1095
			'id' => ++$id,
1096
			'name' => 'Deploy the latest version of a branch',
1097
			'field_type' => 'dropdown',
1098
			'field_label' => 'Choose a branch',
1099
			'field_id' => 'branch',
1100
			'field_data' => [],
1101
			'options' => $options
1102
		];
1103 View Code Duplication
		foreach ($project->DNBranchList() as $branch) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1104
			$sha = $branch->SHA();
1105
			$name = $branch->Name();
1106
			$branchValue = sprintf("%s (%s, %s old)",
1107
				$name,
1108
				substr($sha, 0, 8),
1109
				$branch->LastUpdated()->TimeDiff()
1110
			);
1111
			$data['field_data'][] = [
1112
				'id' => $sha,
1113
				'text' => $branchValue,
1114
				'branch_name' => $name // the raw branch name, not including the time etc
1115
			];
1116
		}
1117
		$tabs[] = $data;
1118
1119
		$data = [
1120
			'id' => ++$id,
1121
			'name' => 'Deploy a tagged release',
1122
			'field_type' => 'dropdown',
1123
			'field_label' => 'Choose a tag',
1124
			'field_id' => 'tag',
1125
			'field_data' => [],
1126
			'options' => $options
1127
		];
1128
1129 View Code Duplication
		foreach ($project->DNTagList()->setLimit(null) as $tag) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1130
			$name = $tag->Name();
1131
			$data['field_data'][] = [
1132
				'id' => $tag->SHA(),
1133
				'text' => sprintf("%s", $name)
1134
			];
1135
		}
1136
1137
		// show newest tags first.
1138
		$data['field_data'] = array_reverse($data['field_data']);
1139
1140
		$tabs[] = $data;
1141
1142
		// Past deployments
1143
		$data = [
1144
			'id' => ++$id,
1145
			'name' => 'Redeploy a release that was previously deployed (to any environment)',
1146
			'field_type' => 'dropdown',
1147
			'field_label' => 'Choose a previously deployed release',
1148
			'field_id' => 'release',
1149
			'field_data' => [],
1150
			'options' => $options
1151
		];
1152
		// We are aiming at the format:
1153
		// [{text: 'optgroup text', children: [{id: '<sha>', text: '<inner text>'}]}]
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1154
		$redeploy = [];
1155 View Code Duplication
		foreach ($project->DNEnvironmentList() as $dnEnvironment) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1156
			$envName = $dnEnvironment->Name;
1157
			$perEnvDeploys = [];
1158
1159
			foreach ($dnEnvironment->DeployHistory()->filter('State', \DNDeployment::STATE_COMPLETED) as $deploy) {
1160
				$sha = $deploy->SHA;
1161
1162
				// Check if exists to make sure the newest deployment date is used.
1163
				if (!isset($perEnvDeploys[$sha])) {
1164
					$pastValue = sprintf("%s (deployed %s)",
1165
						substr($sha, 0, 8),
1166
						$deploy->obj('LastEdited')->Ago()
1167
					);
1168
					$perEnvDeploys[$sha] = [
1169
						'id' => $sha,
1170
						'text' => $pastValue
1171
					];
1172
				}
1173
			}
1174
1175
			if (!empty($perEnvDeploys)) {
1176
				$redeploy[$envName] = array_values($perEnvDeploys);
1177
			}
1178
		}
1179
		// Convert the array to the frontend format (i.e. keyed to regular array)
1180
		foreach ($redeploy as $name => $descr) {
1181
			$data['field_data'][] = ['text' => $name, 'children' => $descr];
1182
		}
1183
		$tabs[] = $data;
1184
1185
		$data = [
1186
			'id' => ++$id,
1187
			'name' => 'Deploy a specific SHA',
1188
			'field_type' => 'textfield',
1189
			'field_label' => 'Choose a SHA',
1190
			'field_id' => 'SHA',
1191
			'field_data' => [],
1192
			'options' => $options
1193
		];
1194
		$tabs[] = $data;
1195
1196
		// get the last time git fetch was run
1197
		$lastFetched = 'never';
1198
		$fetch = DNGitFetch::get()
1199
			->filter([
1200
				'ProjectID' => $project->ID,
1201
				'Status' => 'Finished'
1202
			])
1203
			->sort('LastEdited', 'DESC')
1204
			->first();
1205
		if ($fetch) {
1206
			$lastFetched = $fetch->dbObject('LastEdited')->Ago();
1207
		}
1208
1209
		$data = [
1210
			'Tabs' => $tabs,
1211
			'last_fetched' => $lastFetched
1212
		];
1213
1214
		$this->applyRedeploy($request, $data);
1215
1216
		return json_encode($data, JSON_PRETTY_PRINT);
1217
	}
1218
1219
	/**
1220
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1221
	 *
1222
	 * @param \SS_HTTPRequest $request
1223
	 *
1224
	 * @return string
1225
	 */
1226
	public function deploySummary(\SS_HTTPRequest $request) {
1227
1228
		// Performs canView permission check by limiting visible projects
1229
		$project = $this->getCurrentProject();
1230
		if (!$project) {
1231
			return $this->project404Response();
1232
		}
1233
1234
		// Performs canView permission check by limiting visible projects
1235
		$environment = $this->getCurrentEnvironment($project);
1236
		if (!$environment) {
1237
			return $this->environment404Response();
1238
		}
1239
1240
		// Plan the deployment.
1241
		$strategy = $environment->getDeployStrategy($request);
1242
		$data = $strategy->toArray();
1243
1244
		// Add in a URL for comparing from->to code changes. Ensure that we have
1245
		// two proper 40 character SHAs, otherwise we can't show the compare link.
1246
		$interface = $project->getRepositoryInterface();
1247
		if (
1248
			!empty($interface) && !empty($interface->URL)
1249
			&& !empty($data['changes']['Code version']['from'])
1250
			&& strlen($data['changes']['Code version']['from']) == '40'
1251
			&& !empty($data['changes']['Code version']['to'])
1252
			&& strlen($data['changes']['Code version']['to']) == '40'
1253
		) {
1254
			$compareurl = sprintf(
1255
				'%s/compare/%s...%s',
1256
				$interface->URL,
1257
				$data['changes']['Code version']['from'],
1258
				$data['changes']['Code version']['to']
1259
			);
1260
			$data['changes']['Code version']['compareUrl'] = $compareurl;
1261
		}
1262
1263
		// Append json to response
1264
		$token = SecurityToken::inst();
1265
		$data['SecurityID'] = $token->getValue();
1266
1267
		$this->extend('updateDeploySummary', $data);
1268
1269
		return json_encode($data);
1270
	}
1271
1272
	/**
1273
	 * Deployment form submission handler.
1274
	 *
1275
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1276
	 *
1277
	 * Initiate a DNDeployment record and redirect to it for status polling
1278
	 *
1279
	 * @param \SS_HTTPRequest $request
1280
	 *
1281
	 * @return SS_HTTPResponse
1282
	 * @throws ValidationException
1283
	 * @throws null
1284
	 */
1285
	public function startDeploy(\SS_HTTPRequest $request) {
1286
1287
		$token = SecurityToken::inst();
1288
1289
		// Ensure the submitted token has a value
1290
		$submittedToken = $request->postVar(\Dispatcher::SECURITY_TOKEN_NAME);
1291
		if (!$submittedToken) {
1292
			return false;
1293
		}
1294
		// Do the actual check.
1295
		$check = $token->check($submittedToken);
1296
		// Ensure the CSRF Token is correct
1297
		if (!$check) {
1298
			// CSRF token didn't match
1299
			return $this->httpError(400, 'Bad Request');
1300
		}
1301
1302
		// Performs canView permission check by limiting visible projects
1303
		$project = $this->getCurrentProject();
1304
		if (!$project) {
1305
			return $this->project404Response();
1306
		}
1307
1308
		// Performs canView permission check by limiting visible projects
1309
		$environment = $this->getCurrentEnvironment($project);
1310
		if (!$environment) {
1311
			return $this->environment404Response();
1312
		}
1313
1314
		// Initiate the deployment
1315
		// The extension point should pass in: Project, Environment, SelectRelease, buildName
1316
		$this->extend('doDeploy', $project, $environment, $buildName, $data);
1317
1318
		// Start the deployment based on the approved strategy.
1319
		$strategy = new DeploymentStrategy($environment);
1320
		$strategy->fromArray($request->requestVar('strategy'));
1321
		$deployment = $strategy->createDeployment();
1322
		// Bypass approval by going straight to Queued.
1323
		$deployment->getMachine()->apply(DNDeployment::TR_QUEUE);
1324
1325
		return json_encode([
1326
			'url' => Director::absoluteBaseURL() . $deployment->Link()
1327
		], JSON_PRETTY_PRINT);
1328
	}
1329
1330
	/**
1331
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1332
	 *
1333
	 * Action - Do the actual deploy
1334
	 *
1335
	 * @param \SS_HTTPRequest $request
1336
	 *
1337
	 * @return SS_HTTPResponse|string
1338
	 * @throws SS_HTTPResponse_Exception
1339
	 */
1340
	public function deploy(\SS_HTTPRequest $request) {
1341
		$params = $request->params();
1342
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1343
1344
		if (!$deployment || !$deployment->ID) {
1345
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1346
		}
1347
		if (!$deployment->canView()) {
1348
			return Security::permissionFailure();
1349
		}
1350
1351
		$environment = $deployment->Environment();
1352
		$project = $environment->Project();
1353
1354
		if ($environment->Name != $params['Environment']) {
1355
			throw new LogicException("Environment in URL doesn't match this deploy");
1356
		}
1357
		if ($project->Name != $params['Project']) {
1358
			throw new LogicException("Project in URL doesn't match this deploy");
1359
		}
1360
1361
		return $this->render([
1362
			'Deployment' => $deployment,
1363
		]);
1364
	}
1365
1366
	/**
1367
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1368
	 *
1369
	 * Action - Get the latest deploy log
1370
	 *
1371
	 * @param \SS_HTTPRequest $request
1372
	 *
1373
	 * @return string
1374
	 * @throws SS_HTTPResponse_Exception
1375
	 */
1376
	public function deploylog(\SS_HTTPRequest $request) {
1377
		$params = $request->params();
1378
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1379
1380
		if (!$deployment || !$deployment->ID) {
1381
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1382
		}
1383
		if (!$deployment->canView()) {
1384
			return Security::permissionFailure();
1385
		}
1386
1387
		$environment = $deployment->Environment();
1388
		$project = $environment->Project();
1389
1390
		if ($environment->Name != $params['Environment']) {
1391
			throw new LogicException("Environment in URL doesn't match this deploy");
1392
		}
1393
		if ($project->Name != $params['Project']) {
1394
			throw new LogicException("Project in URL doesn't match this deploy");
1395
		}
1396
1397
		$log = $deployment->log();
1398
		if ($log->exists()) {
1399
			$content = $log->content();
1400
		} else {
1401
			$content = 'Waiting for action to start';
1402
		}
1403
1404
		return $this->sendResponse($deployment->ResqueStatus(), $content);
1405
	}
1406
1407
	/**
1408
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1409
	 *
1410
	 * @param \SS_HTTPRequest $request
1411
	 *
1412
	 * @return string
1413
	 * @throws SS_HTTPResponse_Exception
1414
	 */
1415
	public function abortDeploy(\SS_HTTPRequest $request) {
1416
		$params = $request->params();
1417
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1418
1419
		if (!$deployment || !$deployment->ID) {
1420
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1421
		}
1422
		if (!$deployment->canView()) {
1423
			return Security::permissionFailure();
1424
		}
1425
1426
		// For now restrict to ADMINs only.
1427
		if (!Permission::check('ADMIN')) {
1428
			return Security::permissionFailure();
1429
		}
1430
1431
		$environment = $deployment->Environment();
1432
		$project = $environment->Project();
1433
1434
		if ($environment->Name != $params['Environment']) {
1435
			throw new LogicException("Environment in URL doesn't match this deploy");
1436
		}
1437
		if ($project->Name != $params['Project']) {
1438
			throw new LogicException("Project in URL doesn't match this deploy");
1439
		}
1440
1441
		if (!in_array($deployment->Status, ['Queued', 'Deploying', 'Aborting'])) {
1442
			throw new LogicException(sprintf("Cannot abort from %s state.", $deployment->Status));
1443
		}
1444
1445
		$deployment->getMachine()->apply(DNDeployment::TR_ABORT);
1446
1447
		return $this->sendResponse($deployment->ResqueStatus(), []);
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1448
	}
1449
1450
	/**
1451
	 * @param \SS_HTTPRequest|null $request
1452
	 *
1453
	 * @return Form
1454
	 */
1455
	public function getDataTransferForm(\SS_HTTPRequest $request = null) {
1456
		// Performs canView permission check by limiting visible projects
1457
		$envs = $this->getCurrentProject()->DNEnvironmentList()->filterByCallback(function ($item) {
1458
			return $item->canBackup();
1459
		});
1460
1461
		if (!$envs) {
1462
			return $this->environment404Response();
1463
		}
1464
1465
		$items = [];
1466
		$disabledEnvironments = [];
1467 View Code Duplication
		foreach($envs as $env) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1468
			$items[$env->ID] = $env->Title;
1469
			if ($env->CurrentBuild() === false) {
1470
				$items[$env->ID] = sprintf("%s - requires initial deployment", $env->Title);
1471
				$disabledEnvironments[] = $env->ID;
1472
			}
1473
		}
1474
1475
		$envsField = DropdownField::create('EnvironmentID', 'Environment', $items)
1476
			->setEmptyString('Select an environment');
1477
		$envsField->setDisabledItems($disabledEnvironments);
1478
1479
		$formAction = FormAction::create('doDataTransfer', 'Create')
1480
			->addExtraClass('btn');
1481
1482
		if (count($disabledEnvironments) === $envs->count()) {
1483
			$formAction->setDisabled(true);
1484
		}
1485
1486
		// Allow the _resampled dir to be included if we are a Rainforest Env
1487
		if ($this->getCurrentProject()->DNEnvironmentList()->first() instanceof RainforestEnvironment) {
0 ignored issues
show
Bug introduced by
The class RainforestEnvironment does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1488
			$fields = FieldList::create(
1489
				HiddenField::create('Direction', null, 'get'),
1490
				$envsField,
1491
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map()),
1492
				CheckboxField::create('IncludeResampled', 'Include Resampled Images Directory? (e.g. for total content migration)')
1493
			);
1494
		} else {
1495
			$fields = FieldList::create(
1496
				HiddenField::create('Direction', null, 'get'),
1497
				$envsField,
1498
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map())
1499
			);
1500
		}
1501
1502
		$form = Form::create(
1503
			$this,
1504
			'DataTransferForm',
1505
			$fields,
1506
			FieldList::create($formAction)
1507
		);
1508
		$form->setFormAction($this->getRequest()->getURL() . '/DataTransferForm');
1509
1510
		return $form;
1511
	}
1512
1513
	/**
1514
	 * @param array $data
1515
	 * @param Form $form
1516
	 *
1517
	 * @return SS_HTTPResponse
1518
	 * @throws SS_HTTPResponse_Exception
1519
	 */
1520
	public function doDataTransfer($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form 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...
1521
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1522
1523
		// Performs canView permission check by limiting visible projects
1524
		$project = $this->getCurrentProject();
1525
		if (!$project) {
1526
			return $this->project404Response();
1527
		}
1528
1529
		$dataArchive = null;
1530
1531
		// Validate direction.
1532
		if ($data['Direction'] == 'get') {
1533
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1534
				->filterByCallback(function ($item) {
1535
					return $item->canBackup();
1536
				});
1537
		} else if ($data['Direction'] == 'push') {
1538
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1539
				->filterByCallback(function ($item) {
1540
					return $item->canRestore();
1541
				});
1542
		} else {
1543
			throw new LogicException('Invalid direction');
1544
		}
1545
1546
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1547
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1548
		if (!$environment) {
1549
			throw new LogicException('Invalid environment');
1550
		}
1551
1552
		$this->validateSnapshotMode($data['Mode']);
1553
1554
		// Only 'push' direction is allowed an association with an existing archive.
1555
		if (
1556
			$data['Direction'] == 'push'
1557
			&& isset($data['DataArchiveID'])
1558
			&& is_numeric($data['DataArchiveID'])
1559
		) {
1560
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1561
			if (!$dataArchive) {
1562
				throw new LogicException('Invalid data archive');
1563
			}
1564
1565
			if (!$dataArchive->canDownload()) {
1566
				throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1567
			}
1568
		}
1569
1570
		$transfer = DNDataTransfer::create();
1571
		$transfer->EnvironmentID = $environment->ID;
1572
		$transfer->Direction = $data['Direction'];
1573
		$transfer->Mode = $data['Mode'];
1574
		$transfer->DataArchiveID = $dataArchive ? $dataArchive->ID : null;
1575
		$transfer->IncludeResampled = $data['IncludeResampled'];
0 ignored issues
show
Documentation introduced by
The property IncludeResampled does not exist on object<DNDataTransfer>. 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...
1576
		if ($data['Direction'] == 'push') {
1577
			$transfer->setBackupBeforePush(!empty($data['BackupBeforePush']));
1578
		}
1579
		$transfer->write();
1580
		$transfer->start();
1581
1582
		return $this->redirect($transfer->Link());
1583
	}
1584
1585
	/**
1586
	 * View into the log for a {@link DNDataTransfer}.
1587
	 *
1588
	 * @param \SS_HTTPRequest $request
1589
	 *
1590
	 * @return SS_HTTPResponse|string
1591
	 * @throws SS_HTTPResponse_Exception
1592
	 */
1593
	public function transfer(\SS_HTTPRequest $request) {
1594
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1595
1596
		$params = $request->params();
1597
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1598
1599
		if (!$transfer || !$transfer->ID) {
1600
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1601
		}
1602
		if (!$transfer->canView()) {
1603
			return Security::permissionFailure();
1604
		}
1605
1606
		$environment = $transfer->Environment();
1607
		$project = $environment->Project();
1608
1609
		if ($project->Name != $params['Project']) {
1610
			throw new LogicException("Project in URL doesn't match this deploy");
1611
		}
1612
1613
		return $this->render([
1614
			'CurrentTransfer' => $transfer,
1615
			'SnapshotsSection' => 1,
1616
		]);
1617
	}
1618
1619
	/**
1620
	 * Action - Get the latest deploy log
1621
	 *
1622
	 * @param \SS_HTTPRequest $request
1623
	 *
1624
	 * @return string
1625
	 * @throws SS_HTTPResponse_Exception
1626
	 */
1627
	public function transferlog(\SS_HTTPRequest $request) {
1628
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1629
1630
		$params = $request->params();
1631
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1632
1633
		if (!$transfer || !$transfer->ID) {
1634
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1635
		}
1636
		if (!$transfer->canView()) {
1637
			return Security::permissionFailure();
1638
		}
1639
1640
		$environment = $transfer->Environment();
1641
		$project = $environment->Project();
1642
1643
		if ($project->Name != $params['Project']) {
1644
			throw new LogicException("Project in URL doesn't match this deploy");
1645
		}
1646
1647
		$log = $transfer->log();
1648
		if ($log->exists()) {
1649
			$content = $log->content();
1650
		} else {
1651
			$content = 'Waiting for action to start';
1652
		}
1653
1654
		return $this->sendResponse($transfer->ResqueStatus(), $content);
1655
	}
1656
1657
	/**
1658
	 * Note: Submits to the same action as {@link getDataTransferForm()},
1659
	 * but with a Direction=push and an archive reference.
1660
	 *
1661
	 * @param \SS_HTTPRequest $request
1662
	 * @param \DNDataArchive|null $dataArchive Only set when method is called manually in {@link restore()},
1663
	 *                            otherwise the state is inferred from the request data.
1664
	 * @return Form
1665
	 */
1666
	public function getDataTransferRestoreForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1667
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1668
1669
		// Performs canView permission check by limiting visible projects
1670
		$project = $this->getCurrentProject();
1671
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
1672
			return $item->canRestore();
1673
		});
1674
1675
		if (!$envs) {
1676
			return $this->environment404Response();
1677
		}
1678
1679
		$modesMap = [];
1680
		if (in_array($dataArchive->Mode, ['all'])) {
1681
			$modesMap['all'] = 'Database and Assets';
1682
		};
1683
		if (in_array($dataArchive->Mode, ['all', 'db'])) {
1684
			$modesMap['db'] = 'Database only';
1685
		};
1686
		if (in_array($dataArchive->Mode, ['all', 'assets'])) {
1687
			$modesMap['assets'] = 'Assets only';
1688
		};
1689
1690
		$alertMessage = '<div class="alert alert-warning"><strong>Warning:</strong> '
1691
			. 'This restore will overwrite the data on the chosen environment below</div>';
1692
1693
1694
		$items = [];
1695
		$disabledEnvironments = [];
1696 View Code Duplication
		foreach($envs as $env) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1697
			$items[$env->ID] = $env->Title;
1698
			if ($env->CurrentBuild() === false) {
1699
				$items[$env->ID] = sprintf("%s - requires initial deployment", $env->Title);
1700
				$disabledEnvironments[] = $env->ID;
1701
			}
1702
		}
1703
1704
		$envsField = DropdownField::create('EnvironmentID', 'Environment', $items)
1705
			->setEmptyString('Select an environment');
1706
		$envsField->setDisabledItems($disabledEnvironments);
1707
		$formAction = FormAction::create('doDataTransfer', 'Restore Data')->addExtraClass('btn');
1708
1709
		if (count($disabledEnvironments) == $envs->count()) {
1710
			$formAction->setDisabled(true);
1711
		}
1712
1713
		$form = Form::create(
1714
			$this,
1715
			'DataTransferRestoreForm',
1716
			FieldList::create(
1717
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1718
				HiddenField::create('Direction', null, 'push'),
1719
				LiteralField::create('Warning', $alertMessage),
1720
				$envsField,
1721
				DropdownField::create('Mode', 'Transfer', $modesMap),
1722
				CheckboxField::create('BackupBeforePush', 'Backup existing data', '1')
1723
			),
1724
			FieldList::create($formAction)
1725
		);
1726
		$form->setFormAction($project->Link() . '/DataTransferRestoreForm');
1727
1728
		return $form;
1729
	}
1730
1731
	/**
1732
	 * View a form to restore a specific {@link DataArchive}.
1733
	 * Permission checks are handled in {@link DataArchives()}.
1734
	 * Submissions are handled through {@link doDataTransfer()}, same as backup operations.
1735
	 *
1736
	 * @param \SS_HTTPRequest $request
1737
	 *
1738
	 * @return HTMLText
1739
	 * @throws SS_HTTPResponse_Exception
1740
	 */
1741 View Code Duplication
	public function restoresnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1742
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1743
1744
		/** @var DNDataArchive $dataArchive */
1745
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1746
1747
		if (!$dataArchive) {
1748
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1749
		}
1750
1751
		// We check for canDownload because that implies access to the data.
1752
		// canRestore is later checked on the actual restore action per environment.
1753
		if (!$dataArchive->canDownload()) {
1754
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1755
		}
1756
1757
		$form = $this->getDataTransferRestoreForm($this->request, $dataArchive);
1758
1759
		// View currently only available via ajax
1760
		return $form->forTemplate();
1761
	}
1762
1763
	/**
1764
	 * View a form to delete a specific {@link DataArchive}.
1765
	 * Permission checks are handled in {@link DataArchives()}.
1766
	 * Submissions are handled through {@link doDelete()}.
1767
	 *
1768
	 * @param \SS_HTTPRequest $request
1769
	 *
1770
	 * @return HTMLText
1771
	 * @throws SS_HTTPResponse_Exception
1772
	 */
1773 View Code Duplication
	public function deletesnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1774
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1775
1776
		/** @var DNDataArchive $dataArchive */
1777
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1778
1779
		if (!$dataArchive) {
1780
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1781
		}
1782
1783
		if (!$dataArchive->canDelete()) {
1784
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1785
		}
1786
1787
		$form = $this->getDeleteForm($this->request, $dataArchive);
1788
1789
		// View currently only available via ajax
1790
		return $form->forTemplate();
1791
	}
1792
1793
	/**
1794
	 * @param \SS_HTTPRequest $request
1795
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually, otherwise the state is inferred
1796
	 *        from the request data.
1797
	 * @return Form
1798
	 */
1799
	public function getDeleteForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1800
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1801
1802
		// Performs canView permission check by limiting visible projects
1803
		$project = $this->getCurrentProject();
1804
		if (!$project) {
1805
			return $this->project404Response();
1806
		}
1807
1808
		$snapshotDeleteWarning = '<div class="alert alert-warning">'
1809
			. 'Are you sure you want to permanently delete this snapshot from this archive area?'
1810
			. '</div>';
1811
1812
		$form = Form::create(
1813
			$this,
1814
			'DeleteForm',
1815
			FieldList::create(
1816
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1817
				LiteralField::create('Warning', $snapshotDeleteWarning)
1818
			),
1819
			FieldList::create(
1820
				FormAction::create('doDelete', 'Delete')
1821
					->addExtraClass('btn')
1822
			)
1823
		);
1824
		$form->setFormAction($project->Link() . '/DeleteForm');
1825
1826
		return $form;
1827
	}
1828
1829
	/**
1830
	 * @param array $data
1831
	 * @param Form $form
1832
	 *
1833
	 * @return bool|SS_HTTPResponse
1834
	 * @throws SS_HTTPResponse_Exception
1835
	 */
1836
	public function doDelete($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form 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...
1837
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1838
1839
		// Performs canView permission check by limiting visible projects
1840
		$project = $this->getCurrentProject();
1841
		if (!$project) {
1842
			return $this->project404Response();
1843
		}
1844
1845
		$dataArchive = null;
1846
1847
		if (
1848
			isset($data['DataArchiveID'])
1849
			&& is_numeric($data['DataArchiveID'])
1850
		) {
1851
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1852
		}
1853
1854
		if (!$dataArchive) {
1855
			throw new LogicException('Invalid data archive');
1856
		}
1857
1858
		if (!$dataArchive->canDelete()) {
1859
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1860
		}
1861
1862
		$dataArchive->delete();
1863
1864
		return $this->redirectBack();
1865
	}
1866
1867
	/**
1868
	 * View a form to move a specific {@link DataArchive}.
1869
	 *
1870
	 * @param \SS_HTTPRequest $request
1871
	 *
1872
	 * @return HTMLText
1873
	 * @throws SS_HTTPResponse_Exception
1874
	 */
1875 View Code Duplication
	public function movesnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1876
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1877
1878
		/** @var DNDataArchive $dataArchive */
1879
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1880
1881
		if (!$dataArchive) {
1882
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1883
		}
1884
1885
		// We check for canDownload because that implies access to the data.
1886
		if (!$dataArchive->canDownload()) {
1887
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1888
		}
1889
1890
		$form = $this->getMoveForm($this->request, $dataArchive);
1891
1892
		// View currently only available via ajax
1893
		return $form->forTemplate();
0 ignored issues
show
Bug introduced by
The method forTemplate does only exist in Form, but not in SS_HTTPResponse.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1894
	}
1895
1896
	/**
1897
	 * Build snapshot move form.
1898
	 *
1899
	 * @param \SS_HTTPRequest $request
1900
	 * @param DNDataArchive|null $dataArchive
1901
	 *
1902
	 * @return Form|SS_HTTPResponse
1903
	 */
1904
	public function getMoveForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1905
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1906
1907
		$envs = $dataArchive->validTargetEnvironments();
1908
		if (!$envs) {
1909
			return $this->environment404Response();
1910
		}
1911
1912
		$warningMessage = '<div class="alert alert-warning"><strong>Warning:</strong> This will make the snapshot '
1913
			. 'available to people with access to the target environment.<br>By pressing "Change ownership" you '
1914
			. 'confirm that you have considered data confidentiality regulations.</div>';
1915
1916
		$form = Form::create(
1917
			$this,
1918
			'MoveForm',
1919
			FieldList::create(
1920
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1921
				LiteralField::create('Warning', $warningMessage),
1922
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1923
					->setEmptyString('Select an environment')
1924
			),
1925
			FieldList::create(
1926
				FormAction::create('doMove', 'Change ownership')
1927
					->addExtraClass('btn')
1928
			)
1929
		);
1930
		$form->setFormAction($this->getCurrentProject()->Link() . '/MoveForm');
1931
1932
		return $form;
1933
	}
1934
1935
	/**
1936
	 * @param array $data
1937
	 * @param Form $form
1938
	 *
1939
	 * @return bool|SS_HTTPResponse
1940
	 * @throws SS_HTTPResponse_Exception
1941
	 * @throws ValidationException
1942
	 * @throws null
1943
	 */
1944
	public function doMove($data, \Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form 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...
1945
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1946
1947
		// Performs canView permission check by limiting visible projects
1948
		$project = $this->getCurrentProject();
1949
		if (!$project) {
1950
			return $this->project404Response();
1951
		}
1952
1953
		/** @var DNDataArchive $dataArchive */
1954
		$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1955
		if (!$dataArchive) {
1956
			throw new LogicException('Invalid data archive');
1957
		}
1958
1959
		// We check for canDownload because that implies access to the data.
1960
		if (!$dataArchive->canDownload()) {
1961
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1962
		}
1963
1964
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1965
		$validEnvs = $dataArchive->validTargetEnvironments();
1966
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1967
		if (!$environment) {
1968
			throw new LogicException('Invalid environment');
1969
		}
1970
1971
		$dataArchive->EnvironmentID = $environment->ID;
1972
		$dataArchive->write();
1973
1974
		return $this->redirectBack();
1975
	}
1976
1977
	/**
1978
	 * Returns an error message if redis is unavailable
1979
	 *
1980
	 * @return string
1981
	 */
1982
	public static function RedisUnavailable() {
1983
		try {
1984
			Resque::queues();
1985
		} catch (Exception $e) {
1986
			return $e->getMessage();
1987
		}
1988
		return '';
1989
	}
1990
1991
	/**
1992
	 * Returns the number of connected Redis workers
1993
	 *
1994
	 * @return int
1995
	 */
1996
	public static function RedisWorkersCount() {
1997
		return count(Resque_Worker::all());
1998
	}
1999
2000
	/**
2001
	 * @return array
2002
	 */
2003
	public function providePermissions() {
2004
		return [
2005
			self::DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS => [
2006
				'name' => "Access to advanced deploy options",
2007
				'category' => "Deploynaut",
2008
			],
2009
2010
			// Permissions that are intended to be added to the roles.
2011
			self::ALLOW_PROD_DEPLOYMENT => [
2012
				'name' => "Ability to deploy to production environments",
2013
				'category' => "Deploynaut",
2014
			],
2015
			self::ALLOW_NON_PROD_DEPLOYMENT => [
2016
				'name' => "Ability to deploy to non-production environments",
2017
				'category' => "Deploynaut",
2018
			],
2019
			self::ALLOW_PROD_SNAPSHOT => [
2020
				'name' => "Ability to make production snapshots",
2021
				'category' => "Deploynaut",
2022
			],
2023
			self::ALLOW_NON_PROD_SNAPSHOT => [
2024
				'name' => "Ability to make non-production snapshots",
2025
				'category' => "Deploynaut",
2026
			],
2027
			self::ALLOW_CREATE_ENVIRONMENT => [
2028
				'name' => "Ability to create environments",
2029
				'category' => "Deploynaut",
2030
			],
2031
		];
2032
	}
2033
2034
	/**
2035
	 * @return DNProject|null
2036
	 */
2037
	public function getCurrentProject() {
2038
		$projectName = trim($this->getRequest()->param('Project'));
2039
		if (!$projectName) {
2040
			return null;
2041
		}
2042
		if (empty(self::$_project_cache[$projectName])) {
2043
			self::$_project_cache[$projectName] = $this->DNProjectList()->filter('Name', $projectName)->First();
2044
		}
2045
		return self::$_project_cache[$projectName];
2046
	}
2047
2048
	/**
2049
	 * @param \DNProject|null $project
2050
	 * @return \DNEnvironment|null
2051
	 */
2052
	public function getCurrentEnvironment(\DNProject $project = null) {
2053
		if ($this->getRequest()->param('Environment') === null) {
2054
			return null;
2055
		}
2056
		if ($project === null) {
2057
			$project = $this->getCurrentProject();
2058
		}
2059
		// project can still be null
2060
		if ($project === null) {
2061
			return null;
2062
		}
2063
		return $project->DNEnvironmentList()->filter('Name', $this->getRequest()->param('Environment'))->First();
2064
	}
2065
2066
	/**
2067
	 * This will return a const that indicates the class of action currently being performed
2068
	 *
2069
	 * Until DNRoot is de-godded, it does a bunch of different actions all in the same class.
2070
	 * So we just have each action handler calll setCurrentActionType to define what sort of
2071
	 * action it is.
2072
	 *
2073
	 * @return string - one of the consts from self::$action_types
2074
	 */
2075
	public function getCurrentActionType() {
2076
		return $this->actionType;
2077
	}
2078
2079
	/**
2080
	 * Sets the current action type
2081
	 *
2082
	 * @param string $actionType string - one of the consts from self::$action_types
2083
	 */
2084
	public function setCurrentActionType($actionType) {
2085
		$this->actionType = $actionType;
2086
	}
2087
2088
	/**
2089
	 * Helper method to allow templates to know whether they should show the 'Archive List' include or not.
2090
	 * The actual permissions are set on a per-environment level, so we need to find out if this $member can upload to
2091
	 * or download from *any* {@link DNEnvironment} that (s)he has access to.
2092
	 *
2093
	 * TODO To be replaced with a method that just returns the list of archives this {@link Member} has access to.
2094
	 *
2095
	 * @param Member|null $member The {@link Member} to check (or null to check the currently logged in Member)
2096
	 * @return boolean|null true if $member has access to upload or download to at least one {@link DNEnvironment}.
2097
	 */
2098
	public function CanViewArchives(\Member $member = null) {
2099
		if ($member === null) {
2100
			$member = Member::currentUser();
2101
		}
2102
2103
		if (Permission::checkMember($member, 'ADMIN')) {
2104
			return true;
2105
		}
2106
2107
		$allProjects = $this->DNProjectList();
2108
		if (!$allProjects) {
2109
			return false;
2110
		}
2111
2112
		foreach ($allProjects as $project) {
2113
			if ($project->Environments()) {
2114
				foreach ($project->Environments() as $environment) {
2115
					if (
2116
						$environment->canRestore($member) ||
2117
						$environment->canBackup($member) ||
2118
						$environment->canUploadArchive($member) ||
2119
						$environment->canDownloadArchive($member)
2120
					) {
2121
						// We can return early as we only need to know that we can access one environment
2122
						return true;
2123
					}
2124
				}
2125
			}
2126
		}
2127
	}
2128
2129
	/**
2130
	 * Returns a list of attempted environment creations.
2131
	 *
2132
	 * @return PaginatedList
2133
	 */
2134
	public function CreateEnvironmentList() {
2135
		$project = $this->getCurrentProject();
2136
		if ($project) {
2137
			$dataList = $project->CreateEnvironments();
0 ignored issues
show
Bug introduced by
The method CreateEnvironments() does not exist on DNProject. Did you maybe mean canCreateEnvironments()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2138
		} else {
2139
			$dataList = new ArrayList();
2140
		}
2141
2142
		$this->extend('updateCreateEnvironmentList', $dataList);
2143
		return new PaginatedList($dataList->sort('Created DESC'), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2144
	}
2145
2146
	/**
2147
	 * Returns a list of all archive files that can be accessed by the currently logged-in {@link Member}
2148
	 *
2149
	 * @return PaginatedList
2150
	 */
2151
	public function CompleteDataArchives() {
2152
		$project = $this->getCurrentProject();
2153
		$archives = new ArrayList();
2154
2155
		$archiveList = $project->Environments()->relation("DataArchives");
2156
		if ($archiveList->count() > 0) {
2157
			foreach ($archiveList as $archive) {
2158
				if (!$archive->isPending()) {
2159
					$archives->push($archive);
2160
				}
2161
			}
2162
		}
2163
		return new PaginatedList($archives->sort("Created", "DESC"), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2164
	}
2165
2166
	/**
2167
	 * @return PaginatedList The list of "pending" data archives which are waiting for a file
2168
	 * to be delivered offline by post, and manually uploaded into the system.
2169
	 */
2170
	public function PendingDataArchives() {
2171
		$project = $this->getCurrentProject();
2172
		$archives = new ArrayList();
2173
		foreach ($project->DNEnvironmentList() as $env) {
2174
			foreach ($env->DataArchives() as $archive) {
2175
				if ($archive->isPending()) {
2176
					$archives->push($archive);
2177
				}
2178
			}
2179
		}
2180
		return new PaginatedList($archives->sort("Created", "DESC"), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2181
	}
2182
2183
	/**
2184
	 * @return PaginatedList
2185
	 */
2186
	public function DataTransferLogs() {
2187
		$environments = $this->getCurrentProject()->Environments()->column('ID');
2188
		$transfers = DNDataTransfer::get()
2189
			->filter('EnvironmentID', $environments)
2190
			->filterByCallback(
2191
				function ($record) {
2192
					return
2193
						$record->Environment()->canRestore() || // Ensure member can perform an action on the transfers env
2194
						$record->Environment()->canBackup() ||
2195
						$record->Environment()->canUploadArchive() ||
2196
						$record->Environment()->canDownloadArchive();
2197
				});
2198
2199
		return new PaginatedList($transfers->sort("Created", "DESC"), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2200
	}
2201
2202
	/**
2203
	 * @deprecated 2.0.0 - moved to DeployDispatcher
2204
	 *
2205
	 * @return null|PaginatedList
2206
	 */
2207
	public function DeployHistory() {
2208
		if ($env = $this->getCurrentEnvironment()) {
2209
			$history = $env->DeployHistory();
2210
			if ($history->count() > 0) {
2211
				$pagination = new PaginatedList($history, $this->getRequest());
0 ignored issues
show
Documentation introduced by
$this->getRequest() is of type object<SS_HTTPRequest>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2212
				$pagination->setPageLength(4);
2213
				return $pagination;
2214
			}
2215
		}
2216
		return null;
2217
	}
2218
2219
	/**
2220
	 * @param string $status
2221
	 * @param string $content
2222
	 *
2223
	 * @return string
2224
	 */
2225
	public function sendResponse($status, $content) {
2226
		// strip excessive newlines
2227
		$content = preg_replace('/(?:(?:\r\n|\r|\n)\s*){2}/s', "\n", $content);
2228
2229
		$sendJSON = (strpos($this->getRequest()->getHeader('Accept'), 'application/json') !== false)
2230
			|| $this->getRequest()->getExtension() == 'json';
2231
2232
		if (!$sendJSON) {
2233
			$this->response->addHeader("Content-type", "text/plain");
2234
			return $content;
2235
		}
2236
		$this->response->addHeader("Content-type", "application/json");
2237
		return json_encode([
2238
			'status' => $status,
2239
			'content' => $content,
2240
		]);
2241
	}
2242
2243
	/**
2244
	 * Get items for the ambient menu that should be accessible from all pages.
2245
	 *
2246
	 * @return ArrayList
2247
	 */
2248
	public function AmbientMenu() {
2249
		$list = new ArrayList();
2250
2251
		if (Member::currentUserID()) {
2252
			$list->push(new ArrayData([
2253
				'Classes' => 'logout',
2254
				'FaIcon' => 'sign-out',
2255
				'Link' => 'Security/logout',
2256
				'Title' => 'Log out',
2257
				'IsCurrent' => false,
2258
				'IsSection' => false
2259
			]));
2260
		}
2261
2262
		$this->extend('updateAmbientMenu', $list);
2263
		return $list;
2264
	}
2265
2266
	/**
2267
	 * Checks whether the user can create a project.
2268
	 *
2269
	 * @return bool
2270
	 */
2271
	public function canCreateProjects($member = null) {
2272
		if (!$member) {
2273
			$member = Member::currentUser();
2274
		}
2275
		if (!$member) {
2276
			return false;
2277
		}
2278
2279
		return singleton('DNProject')->canCreate($member);
2280
	}
2281
2282
	protected function applyRedeploy(\SS_HTTPRequest $request, &$data) {
2283
		if (!$request->getVar('redeploy')) {
2284
			return;
2285
		}
2286
2287
		$project = $this->getCurrentProject();
2288
		if (!$project) {
2289
			return $this->project404Response();
2290
		}
2291
2292
		// Performs canView permission check by limiting visible projects
2293
		$env = $this->getCurrentEnvironment($project);
2294
		if (!$env) {
2295
			return $this->environment404Response();
2296
		}
2297
2298
		$current = $env->CurrentBuild();
2299
		if ($current && $current->exists()) {
2300
			$data['preselect_tab'] = 3;
2301
			$data['preselect_sha'] = $current->SHA;
2302
		} else {
2303
			$master = $project->DNBranchList()->byName('master');
2304
			if ($master) {
2305
				$data['preselect_tab'] = 1;
2306
				$data['preselect_sha'] = $master->SHA();
0 ignored issues
show
Bug introduced by
The method SHA cannot be called on $master (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2307
			}
2308
		}
2309
	}
2310
2311
	/**
2312
	 * @return SS_HTTPResponse
2313
	 */
2314
	protected function project404Response() {
2315
		return new SS_HTTPResponse(
2316
			"Project '" . Convert::raw2xml($this->getRequest()->param('Project')) . "' not found.",
2317
			404
2318
		);
2319
	}
2320
2321
	/**
2322
	 * @return SS_HTTPResponse
2323
	 */
2324
	protected function environment404Response() {
2325
		$envName = Convert::raw2xml($this->getRequest()->param('Environment'));
2326
		return new SS_HTTPResponse("Environment '" . $envName . "' not found.", 404);
2327
	}
2328
2329
	/**
2330
	 * Validate the snapshot mode
2331
	 *
2332
	 * @param string $mode
2333
	 */
2334
	protected function validateSnapshotMode($mode) {
2335
		if (!in_array($mode, ['all', 'assets', 'db'])) {
2336
			throw new LogicException('Invalid mode');
2337
		}
2338
	}
2339
2340
	/**
2341
	 * @param string $sectionName
2342
	 * @param string $title
2343
	 *
2344
	 * @return SS_HTTPResponse
2345
	 */
2346
	protected function getCustomisedViewSection($sectionName, $title = '', $data = []) {
2347
		// Performs canView permission check by limiting visible projects
2348
		$project = $this->getCurrentProject();
2349
		if (!$project) {
2350
			return $this->project404Response();
2351
		}
2352
		$data[$sectionName] = 1;
2353
2354
		if ($this !== '') {
2355
			$data['Title'] = $title;
2356
		}
2357
2358
		return $this->render($data);
2359
	}
2360
2361
}
2362
2363