Completed
Push — master ( 9c6c66...523912 )
by Stig
08:10 queued 03:19
created

DNRoot   F

Complexity

Total Complexity 288

Size/Duplication

Total Lines 2310
Duplicated Lines 6.19 %

Coupling/Cohesion

Components 1
Dependencies 51

Importance

Changes 0
Metric Value
wmc 288
lcom 1
cbo 51
dl 143
loc 2310
rs 0.5217
c 0
b 0
f 0

72 Methods

Rating   Name   Duplication   Size   Complexity  
B include_requirements() 0 32 3
A get_support_links() 0 6 2
A get_template_global_variables() 0 8 1
A init() 0 12 3
A Link() 0 3 1
A index() 0 3 1
A projects() 0 6 1
A nav() 0 3 1
A NavLink() 0 5 2
A snapshots() 0 4 1
A createsnapshot() 19 19 3
A uploadsnapshot() 19 19 3
A UploadLimit() 0 6 1
B getUploadSnapshotForm() 0 50 4
B doUploadSnapshot() 7 96 6
B getPostSnapshotForm() 0 44 4
B doPostSnapshot() 0 30 3
A snapshotslog() 0 4 1
B postsnapshotsuccess() 0 29 5
A project() 0 4 1
A toggleprojectstar() 0 21 4
A branch() 0 16 3
A environment() 0 18 3
C createenv() 0 27 7
B createenvlog() 0 26 6
C getCreateEnvironmentForm() 0 44 7
B doCreateEnvironment() 0 24 3
A DNData() 0 3 1
A DNProjectList() 0 17 3
A getPlatformSpecificStrings() 0 6 2
A getStarredProjects() 0 16 4
C getDeployForm() 0 40 8
D gitRevisions() 24 146 12
D deploySummary() 0 45 9
B startDeploy() 0 44 5
B deploy() 0 25 6
C deploylog() 0 30 7
C abortDeploy() 0 34 8
B getDataTransferForm() 7 45 5
C doDataTransfer() 0 63 12
B transfer() 0 25 5
B transferlog() 0 29 6
C getDataTransferRestoreForm() 7 64 9
A restoresnapshot() 21 21 3
A deletesnapshot() 19 19 3
B getDeleteForm() 0 29 3
B doDelete() 0 30 6
A movesnapshot() 20 20 3
B getMoveForm() 0 30 3
B doMove() 0 32 5
A RedisUnavailable() 0 8 2
A RedisWorkersCount() 0 3 1
B providePermissions() 0 30 1
A getCurrentProject() 0 10 3
A getCurrentEnvironment() 0 13 4
A getCurrentActionType() 0 3 1
A setCurrentActionType() 0 3 1
C CanViewArchives() 0 30 11
A CreateEnvironmentList() 0 11 2
A CompleteDataArchives() 0 14 4
A PendingDataArchives() 0 12 4
A DataTransferLogs() 0 15 4
A DeployHistory() 0 11 3
A sendResponse() 0 17 3
A AmbientMenu() 0 17 2
A canCreateProjects() 0 10 3
C applyRedeploy() 0 28 7
A project404Response() 0 6 1
A environment404Response() 0 4 1
A validateSnapshotMode() 0 5 2
A getCustomisedViewSection() 0 14 3
C Navigation() 0 58 17

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DNRoot often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DNRoot, and based on these observations, apply Extract Interface, too.

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...
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 $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...
145
146
	/**
147
	 * @var array
148
	 */
149
	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...
150
		self::ACTION_DEPLOY,
151
		self::ACTION_SNAPSHOT,
152
		self::PROJECT_OVERVIEW
153
	];
154
155
	/**
156
	 * Include requirements that deploynaut needs, such as javascript.
157
	 */
158
	public static function include_requirements() {
159
160
		// JS should always go to the bottom, otherwise there's the risk that Requirements
161
		// puts them halfway through the page to the nearest <script> tag. We don't want that.
162
		Requirements::set_force_js_to_bottom(true);
163
164
		// todo these should be bundled into the same JS as the others in "static" below.
165
		// We've deliberately not used combined_files as it can mess with some of the JS used
166
		// here and cause sporadic errors.
167
		Requirements::javascript('deploynaut/javascript/jquery.js');
168
		Requirements::javascript('deploynaut/javascript/bootstrap.js');
169
		Requirements::javascript('deploynaut/javascript/q.js');
170
		Requirements::javascript('deploynaut/javascript/tablefilter.js');
171
		Requirements::javascript('deploynaut/javascript/deploynaut.js');
172
173
		Requirements::javascript('deploynaut/javascript/bootstrap.file-input.js');
174
		Requirements::javascript('deploynaut/thirdparty/select2/dist/js/select2.min.js');
175
		Requirements::javascript('deploynaut/javascript/selectize.js');
176
		Requirements::javascript('deploynaut/thirdparty/bootstrap-switch/dist/js/bootstrap-switch.min.js');
177
		Requirements::javascript('deploynaut/javascript/material.js');
178
179
		// Load the buildable dependencies only if not loaded centrally.
180
		if (!is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'static')) {
181
			if (\Director::isDev()) {
182
				\Requirements::javascript('deploynaut/static/bundle-debug.js');
183
			} else {
184
				\Requirements::javascript('deploynaut/static/bundle.js');
185
			}
186
		}
187
188
		Requirements::css('deploynaut/static/style.css');
189
	}
190
191
	/**
192
	 * @return ArrayList
193
	 */
194
	public static function get_support_links() {
195
		$supportLinks = self::config()->support_links;
196
		if ($supportLinks) {
197
			return new ArrayList($supportLinks);
198
		}
199
	}
200
201
	/**
202
	 * @return array
203
	 */
204
	public static function get_template_global_variables() {
205
		return [
206
			'RedisUnavailable' => 'RedisUnavailable',
207
			'RedisWorkersCount' => 'RedisWorkersCount',
208
			'SidebarLinks' => 'SidebarLinks',
209
			"SupportLinks" => 'get_support_links'
210
		];
211
	}
212
213
	/**
214
	 */
215
	public function init() {
216
		parent::init();
217
218
		if (!Member::currentUser() && !Session::get('AutoLoginHash')) {
219
			return Security::permissionFailure();
220
		}
221
222
		// Block framework jquery
223
		Requirements::block(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
224
225
		self::include_requirements();
226
	}
227
228
	/**
229
	 * @return string
230
	 */
231
	public function Link() {
232
		return "naut/";
233
	}
234
235
	/**
236
	 * Actions
237
	 *
238
	 * @param \SS_HTTPRequest $request
239
	 * @return \SS_HTTPResponse
240
	 */
241
	public function index(\SS_HTTPRequest $request) {
242
		return $this->redirect($this->Link() . 'projects/');
243
	}
244
245
	/**
246
	 * Action
247
	 *
248
	 * @param \SS_HTTPRequest $request
249
	 * @return string - HTML
250
	 */
251
	public function projects(\SS_HTTPRequest $request) {
252
		// Performs canView permission check by limiting visible projects in DNProjectsList() call.
253
		return $this->customise([
254
			'Title' => 'Projects',
255
		])->render();
256
	}
257
258
	/**
259
	 * @param \SS_HTTPRequest $request
260
	 * @return HTMLText
261
	 */
262
	public function nav(\SS_HTTPRequest $request) {
263
		return $this->renderWith('Nav');
264
	}
265
266
	/**
267
	 * Return a link to the navigation template used for AJAX requests.
268
	 * @return string
269
	 */
270
	public function NavLink() {
271
		$currentProject = $this->getCurrentProject();
272
		$projectName = $currentProject ? $currentProject->Name : null;
273
		return Controller::join_links(Director::absoluteBaseURL(), 'naut', 'nav', $projectName);
274
	}
275
276
	/**
277
	 * Action
278
	 *
279
	 * @param \SS_HTTPRequest $request
280
	 * @return SS_HTTPResponse - HTML
281
	 */
282
	public function snapshots(\SS_HTTPRequest $request) {
283
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
284
		return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots');
285
	}
286
287
	/**
288
	 * Action
289
	 *
290
	 * @param \SS_HTTPRequest $request
291
	 * @return string - HTML
292
	 */
293 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...
294
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
295
296
		// Performs canView permission check by limiting visible projects
297
		$project = $this->getCurrentProject();
298
		if (!$project) {
299
			return $this->project404Response();
300
		}
301
302
		if (!$project->canBackup()) {
303
			return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
304
		}
305
306
		return $this->customise([
307
			'Title' => 'Create Data Snapshot',
308
			'SnapshotsSection' => 1,
309
			'DataTransferForm' => $this->getDataTransferForm($request)
310
		])->render();
311
	}
312
313
	/**
314
	 * Action
315
	 *
316
	 * @param \SS_HTTPRequest $request
317
	 * @return string - HTML
318
	 */
319 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...
320
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
321
322
		// Performs canView permission check by limiting visible projects
323
		$project = $this->getCurrentProject();
324
		if (!$project) {
325
			return $this->project404Response();
326
		}
327
328
		if (!$project->canUploadArchive()) {
329
			return new SS_HTTPResponse("Not allowed to upload", 401);
330
		}
331
332
		return $this->customise([
333
			'SnapshotsSection' => 1,
334
			'UploadSnapshotForm' => $this->getUploadSnapshotForm($request),
335
			'PostSnapshotForm' => $this->getPostSnapshotForm($request)
336
		])->render();
337
	}
338
339
	/**
340
	 * Return the upload limit for snapshot uploads
341
	 * @return string
342
	 */
343
	public function UploadLimit() {
344
		return File::format_size(min(
345
			File::ini2bytes(ini_get('upload_max_filesize')),
346
			File::ini2bytes(ini_get('post_max_size'))
347
		));
348
	}
349
350
	/**
351
	 * Construct the upload form.
352
	 *
353
	 * @param \SS_HTTPRequest $request
354
	 * @return Form
355
	 */
356
	public function getUploadSnapshotForm(\SS_HTTPRequest $request) {
357
		// Performs canView permission check by limiting visible projects
358
		$project = $this->getCurrentProject();
359
		if (!$project) {
360
			return $this->project404Response();
361
		}
362
363
		if (!$project->canUploadArchive()) {
364
			return new SS_HTTPResponse("Not allowed to upload", 401);
365
		}
366
367
		// Framing an environment as a "group of people with download access"
368
		// makes more sense to the user here, while still allowing us to enforce
369
		// environment specific restrictions on downloading the file later on.
370
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
371
			return $item->canUploadArchive();
372
		});
373
		$envsMap = [];
374
		foreach ($envs as $env) {
375
			$envsMap[$env->ID] = $env->Name;
376
		}
377
378
		$maxSize = min(File::ini2bytes(ini_get('upload_max_filesize')), File::ini2bytes(ini_get('post_max_size')));
379
		$fileField = DataArchiveFileField::create('ArchiveFile', 'File');
380
		$fileField->getValidator()->setAllowedExtensions(['sspak']);
381
		$fileField->getValidator()->setAllowedMaxFileSize(['*' => $maxSize]);
382
383
		$form = Form::create(
384
			$this,
385
			'UploadSnapshotForm',
386
			FieldList::create(
387
				$fileField,
388
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
389
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
390
					->setEmptyString('Select an environment')
391
			),
392
			FieldList::create(
393
				FormAction::create('doUploadSnapshot', 'Upload File')
394
					->addExtraClass('btn')
395
			),
396
			RequiredFields::create('ArchiveFile')
397
		);
398
399
		$form->disableSecurityToken();
400
		$form->addExtraClass('fields-wide');
401
		// Tweak the action so it plays well with our fake URL structure.
402
		$form->setFormAction($project->Link() . '/UploadSnapshotForm');
403
404
		return $form;
405
	}
406
407
	/**
408
	 * @param array $data
409
	 * @param Form $form
410
	 *
411
	 * @return bool|HTMLText|SS_HTTPResponse
412
	 */
413
	public function doUploadSnapshot($data, \Form $form) {
414
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
415
416
		// Performs canView permission check by limiting visible projects
417
		$project = $this->getCurrentProject();
418
		if (!$project) {
419
			return $this->project404Response();
420
		}
421
422
		$validEnvs = $project->DNEnvironmentList()
423
			->filterByCallback(function ($item) {
424
				return $item->canUploadArchive();
425
			});
426
427
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
428
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
429
		if (!$environment) {
430
			throw new LogicException('Invalid environment');
431
		}
432
433
		$this->validateSnapshotMode($data['Mode']);
434
435
		$dataArchive = DNDataArchive::create([
436
			'AuthorID' => Member::currentUserID(),
437
			'EnvironmentID' => $data['EnvironmentID'],
438
			'IsManualUpload' => true,
439
		]);
440
		// needs an ID and transfer to determine upload path
441
		$dataArchive->write();
442
		$dataTransfer = DNDataTransfer::create([
443
			'AuthorID' => Member::currentUserID(),
444
			'Mode' => $data['Mode'],
445
			'Origin' => 'ManualUpload',
446
			'EnvironmentID' => $data['EnvironmentID']
447
		]);
448
		$dataTransfer->write();
449
		$dataArchive->DataTransfers()->add($dataTransfer);
450
		$form->saveInto($dataArchive);
451
		$dataArchive->write();
452
		$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
453
454 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...
455
			$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
456
			$process->setTimeout(120);
457
			$process->run();
458
			$dataTransfer->delete();
459
			$dataArchive->delete();
460
		};
461
462
		// extract the sspak contents so we can inspect them
463
		try {
464
			$dataArchive->extractArchive($workingDir);
465
		} catch (Exception $e) {
466
			$cleanupFn();
467
			$form->sessionMessage(
468
				'There was a problem trying to open your snapshot for processing. Please try uploading again',
469
				'bad'
470
			);
471
			return $this->redirectBack();
472
		}
473
474
		// validate that the sspak contents match the declared contents
475
		$result = $dataArchive->validateArchiveContents();
476
		if (!$result->valid()) {
477
			$cleanupFn();
478
			$form->sessionMessage($result->message(), 'bad');
479
			return $this->redirectBack();
480
		}
481
482
		// fix file permissions of extracted sspak files then re-build the sspak
483
		try {
484
			$dataArchive->fixArchivePermissions($workingDir);
485
			$dataArchive->setArchiveFromFiles($workingDir);
486
		} catch (Exception $e) {
487
			$cleanupFn();
488
			$form->sessionMessage(
489
				'There was a problem processing your snapshot. Please try uploading again',
490
				'bad'
491
			);
492
			return $this->redirectBack();
493
		}
494
495
		// cleanup any extracted sspak contents lying around
496
		$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
497
		$process->setTimeout(120);
498
		$process->run();
499
500
		return $this->customise([
501
			'Project' => $project,
502
			'CurrentProject' => $project,
503
			'SnapshotsSection' => 1,
504
			'DataArchive' => $dataArchive,
505
			'DataTransferRestoreForm' => $this->getDataTransferRestoreForm($this->request, $dataArchive),
506
			'BackURL' => $project->Link('snapshots')
507
		])->renderWith(['DNRoot_uploadsnapshot', 'DNRoot']);
508
	}
509
510
	/**
511
	 * @param \SS_HTTPRequest $request
512
	 * @return Form
513
	 */
514
	public function getPostSnapshotForm(\SS_HTTPRequest $request) {
515
		// Performs canView permission check by limiting visible projects
516
		$project = $this->getCurrentProject();
517
		if (!$project) {
518
			return $this->project404Response();
519
		}
520
521
		if (!$project->canUploadArchive()) {
522
			return new SS_HTTPResponse("Not allowed to upload", 401);
523
		}
524
525
		// Framing an environment as a "group of people with download access"
526
		// makes more sense to the user here, while still allowing us to enforce
527
		// environment specific restrictions on downloading the file later on.
528
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
529
			return $item->canUploadArchive();
530
		});
531
		$envsMap = [];
532
		foreach ($envs as $env) {
533
			$envsMap[$env->ID] = $env->Name;
534
		}
535
536
		$form = Form::create(
537
			$this,
538
			'PostSnapshotForm',
539
			FieldList::create(
540
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
541
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
542
					->setEmptyString('Select an environment')
543
			),
544
			FieldList::create(
545
				FormAction::create('doPostSnapshot', 'Submit request')
546
					->addExtraClass('btn')
547
			),
548
			RequiredFields::create('File')
549
		);
550
551
		$form->disableSecurityToken();
552
		$form->addExtraClass('fields-wide');
553
		// Tweak the action so it plays well with our fake URL structure.
554
		$form->setFormAction($project->Link() . '/PostSnapshotForm');
555
556
		return $form;
557
	}
558
559
	/**
560
	 * @param array $data
561
	 * @param Form $form
562
	 *
563
	 * @return SS_HTTPResponse
564
	 */
565
	public function doPostSnapshot($data, $form) {
566
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
567
568
		$project = $this->getCurrentProject();
569
		if (!$project) {
570
			return $this->project404Response();
571
		}
572
573
		$validEnvs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
574
			return $item->canUploadArchive();
575
		});
576
577
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
578
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
579
		if (!$environment) {
580
			throw new LogicException('Invalid environment');
581
		}
582
583
		$dataArchive = DNDataArchive::create([
584
			'UploadToken' => DNDataArchive::generate_upload_token(),
585
		]);
586
		$form->saveInto($dataArchive);
587
		$dataArchive->write();
588
589
		return $this->redirect(Controller::join_links(
590
			$project->Link(),
591
			'postsnapshotsuccess',
592
			$dataArchive->ID
593
		));
594
	}
595
596
	/**
597
	 * Action
598
	 *
599
	 * @param \SS_HTTPRequest $request
600
	 * @return SS_HTTPResponse - HTML
601
	 */
602
	public function snapshotslog(\SS_HTTPRequest $request) {
603
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
604
		return $this->getCustomisedViewSection('SnapshotsSection', 'Snapshots log');
605
	}
606
607
	/**
608
	 * @param \SS_HTTPRequest $request
609
	 * @return SS_HTTPResponse|string
610
	 * @throws SS_HTTPResponse_Exception
611
	 */
612
	public function postsnapshotsuccess(\SS_HTTPRequest $request) {
613
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
614
615
		// Performs canView permission check by limiting visible projects
616
		$project = $this->getCurrentProject();
617
		if (!$project) {
618
			return $this->project404Response();
619
		}
620
621
		if (!$project->canUploadArchive()) {
622
			return new SS_HTTPResponse("Not allowed to upload", 401);
623
		}
624
625
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
626
		if (!$dataArchive) {
627
			return new SS_HTTPResponse("Archive not found.", 404);
628
		}
629
630
		if (!$dataArchive->canRestore()) {
631
			throw new SS_HTTPResponse_Exception('Not allowed to restore archive', 403);
632
		}
633
634
		return $this->render([
635
			'Title' => 'How to send us your Data Snapshot by post',
636
			'DataArchive' => $dataArchive,
637
			'Address' => Config::inst()->get('Deploynaut', 'snapshot_post_address'),
638
			'BackURL' => $project->Link(),
639
		]);
640
	}
641
642
	/**
643
	 * @param \SS_HTTPRequest $request
644
	 * @return \SS_HTTPResponse
645
	 */
646
	public function project(\SS_HTTPRequest $request) {
647
		$this->setCurrentActionType(self::PROJECT_OVERVIEW);
648
		return $this->getCustomisedViewSection('ProjectOverview', '', ['IsAdmin' => Permission::check('ADMIN')]);
649
	}
650
651
	/**
652
	 * This action will star / unstar a project for the current member
653
	 *
654
	 * @param \SS_HTTPRequest $request
655
	 *
656
	 * @return SS_HTTPResponse
657
	 */
658
	public function toggleprojectstar(\SS_HTTPRequest $request) {
659
		$project = $this->getCurrentProject();
660
		if (!$project) {
661
			return $this->project404Response();
662
		}
663
664
		$member = Member::currentUser();
665
		if ($member === null) {
666
			return $this->project404Response();
667
		}
668
		$favProject = $member->StarredProjects()
669
			->filter('DNProjectID', $project->ID)
670
			->first();
671
672
		if ($favProject) {
673
			$member->StarredProjects()->remove($favProject);
674
		} else {
675
			$member->StarredProjects()->add($project);
676
		}
677
		return $this->redirectBack();
678
	}
679
680
	/**
681
	 * @param \SS_HTTPRequest $request
682
	 * @return \SS_HTTPResponse
683
	 */
684
	public function branch(\SS_HTTPRequest $request) {
685
		$project = $this->getCurrentProject();
686
		if (!$project) {
687
			return $this->project404Response();
688
		}
689
690
		$branchName = $request->getVar('name');
691
		$branch = $project->DNBranchList()->byName($branchName);
692
		if (!$branch) {
693
			return new SS_HTTPResponse("Branch '" . Convert::raw2xml($branchName) . "' not found.", 404);
694
		}
695
696
		return $this->render([
697
			'CurrentBranch' => $branch,
698
		]);
699
	}
700
701
	/**
702
	 * @param \SS_HTTPRequest $request
703
	 * @return \SS_HTTPResponse
704
	 */
705
	public function environment(\SS_HTTPRequest $request) {
706
		// Performs canView permission check by limiting visible projects
707
		$project = $this->getCurrentProject();
708
		if (!$project) {
709
			return $this->project404Response();
710
		}
711
712
		// Performs canView permission check by limiting visible projects
713
		$env = $this->getCurrentEnvironment($project);
714
		if (!$env) {
715
			return $this->environment404Response();
716
		}
717
718
		return $this->render([
719
			'DNEnvironmentList' => $this->getCurrentProject()->DNEnvironmentList(),
720
			'Redeploy' => (bool) $request->getVar('redeploy')
721
		]);
722
	}
723
724
	/**
725
	 * Shows the creation log.
726
	 *
727
	 * @param \SS_HTTPRequest $request
728
	 * @return string
729
	 */
730
	public function createenv(\SS_HTTPRequest $request) {
731
		$params = $request->params();
732
		if ($params['Identifier']) {
733
			$record = DNCreateEnvironment::get()->byId($params['Identifier']);
734
735
			if (!$record || !$record->ID) {
736
				throw new SS_HTTPResponse_Exception('Create environment not found', 404);
737
			}
738
			if (!$record->canView()) {
739
				return Security::permissionFailure();
740
			}
741
742
			$project = $this->getCurrentProject();
743
			if (!$project) {
744
				return $this->project404Response();
745
			}
746
747
			if ($project->Name != $params['Project']) {
748
				throw new LogicException("Project in URL doesn't match this creation");
749
			}
750
751
			return $this->render([
752
				'CreateEnvironment' => $record,
753
			]);
754
		}
755
		return $this->render(['CurrentTitle' => 'Create an environment']);
756
	}
757
758
	public function createenvlog(\SS_HTTPRequest $request) {
759
		$params = $request->params();
760
		$env = DNCreateEnvironment::get()->byId($params['Identifier']);
761
762
		if (!$env || !$env->ID) {
763
			throw new SS_HTTPResponse_Exception('Log not found', 404);
764
		}
765
		if (!$env->canView()) {
766
			return Security::permissionFailure();
767
		}
768
769
		$project = $env->Project();
770
771
		if ($project->Name != $params['Project']) {
772
			throw new LogicException("Project in URL doesn't match this deploy");
773
		}
774
775
		$log = $env->log();
776
		if ($log->exists()) {
777
			$content = $log->content();
778
		} else {
779
			$content = 'Waiting for action to start';
780
		}
781
782
		return $this->sendResponse($env->ResqueStatus(), $content);
783
	}
784
785
	/**
786
	 * @param \SS_HTTPRequest $request
787
	 * @return Form
788
	 */
789
	public function getCreateEnvironmentForm(\SS_HTTPRequest $request) {
790
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
791
792
		$project = $this->getCurrentProject();
793
		if (!$project) {
794
			return $this->project404Response();
795
		}
796
797
		$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...
798
		if (!$envType || !class_exists($envType)) {
799
			return null;
800
		}
801
802
		$backend = Injector::inst()->get($envType);
803
		if (!($backend instanceof EnvironmentCreateBackend)) {
804
			// Only allow this for supported backends.
805
			return null;
806
		}
807
808
		$fields = $backend->getCreateEnvironmentFields($project);
809
		if (!$fields) {
810
			return null;
811
		}
812
813
		if (!$project->canCreateEnvironments()) {
814
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
815
		}
816
817
		$form = Form::create(
818
			$this,
819
			'CreateEnvironmentForm',
820
			$fields,
821
			FieldList::create(
822
				FormAction::create('doCreateEnvironment', 'Create')
823
					->addExtraClass('btn')
824
			),
825
			$backend->getCreateEnvironmentValidator()
826
		);
827
828
		// Tweak the action so it plays well with our fake URL structure.
829
		$form->setFormAction($project->Link() . '/CreateEnvironmentForm');
830
831
		return $form;
832
	}
833
834
	/**
835
	 * @param array $data
836
	 * @param Form $form
837
	 *
838
	 * @return bool|HTMLText|SS_HTTPResponse
839
	 */
840
	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...
841
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
842
843
		$project = $this->getCurrentProject();
844
		if (!$project) {
845
			return $this->project404Response();
846
		}
847
848
		if (!$project->canCreateEnvironments()) {
849
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
850
		}
851
852
		// Set the environment type so we know what we're creating.
853
		$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...
854
855
		$job = DNCreateEnvironment::create();
856
857
		$job->Data = serialize($data);
858
		$job->ProjectID = $project->ID;
859
		$job->write();
860
		$job->start();
861
862
		return $this->redirect($project->Link('createenv') . '/' . $job->ID);
863
	}
864
865
	/**
866
	 * Get the DNData object.
867
	 *
868
	 * @return DNData
869
	 */
870
	public function DNData() {
871
		return DNData::inst();
872
	}
873
874
	/**
875
	 * Provide a list of all projects.
876
	 *
877
	 * @return SS_List
878
	 */
879
	public function DNProjectList() {
880
		$memberId = Member::currentUserID();
881
		if (!$memberId) {
882
			return new ArrayList();
883
		}
884
885
		if (Permission::check('ADMIN')) {
886
			return DNProject::get();
887
		}
888
889
		$projects = Member::get()->filter('ID', $memberId)
890
			->relation('Groups')
891
			->relation('Projects');
892
893
		$this->extend('updateDNProjectList', $projects);
894
		return $projects;
895
	}
896
897
	/**
898
	 * @return ArrayList
899
	 */
900
	public function getPlatformSpecificStrings() {
901
		$strings = $this->config()->platform_specific_strings;
902
		if ($strings) {
903
			return new ArrayList($strings);
904
		}
905
	}
906
907
	/**
908
	 * Provide a list of all starred projects for the currently logged in member
909
	 *
910
	 * @return SS_List
911
	 */
912
	public function getStarredProjects() {
913
		$member = Member::currentUser();
914
		if ($member === null) {
915
			return new ArrayList();
916
		}
917
918
		$favProjects = $member->StarredProjects();
919
920
		$list = new ArrayList();
921
		foreach ($favProjects as $project) {
922
			if ($project->canView($member)) {
923
				$list->add($project);
924
			}
925
		}
926
		return $list;
927
	}
928
929
	/**
930
	 * Returns top level navigation of projects.
931
	 *
932
	 * @param int $limit
933
	 *
934
	 * @return ArrayList
935
	 */
936
	public function Navigation($limit = 5) {
937
		$navigation = new ArrayList();
938
939
		$currentProject = $this->getCurrentProject();
940
		$currentEnvironment = $this->getCurrentEnvironment();
941
		$actionType = $this->getCurrentActionType();
942
943
		$projects = $this->getStarredProjects();
944
		if ($projects->count() < 1) {
945
			$projects = $this->DNProjectList();
946
		} else {
947
			$limit = -1;
948
		}
949
950
		if ($projects->count() > 0) {
951
			$activeProject = false;
952
953
			if ($limit > 0) {
954
				$limitedProjects = $projects->limit($limit);
955
			} else {
956
				$limitedProjects = $projects;
957
			}
958
959
			foreach ($limitedProjects as $project) {
960
				$isActive = $currentProject && $currentProject->ID == $project->ID;
961
				if ($isActive) {
962
					$activeProject = true;
963
				}
964
965
				$isCurrentEnvironment = false;
966
				if ($project && $currentEnvironment) {
967
					$isCurrentEnvironment = (bool) $project->DNEnvironmentList()->find('ID', $currentEnvironment->ID);
968
				}
969
970
				$navigation->push([
971
					'Project' => $project,
972
					'IsCurrentEnvironment' => $isCurrentEnvironment,
973
					'IsActive' => $currentProject && $currentProject->ID == $project->ID,
974
					'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...
975
				]);
976
			}
977
978
			// Ensure the current project is in the list
979
			if (!$activeProject && $currentProject) {
980
				$navigation->unshift([
981
					'Project' => $currentProject,
982
					'IsActive' => true,
983
					'IsCurrentEnvironment' => $currentEnvironment,
984
					'IsOverview' => $actionType == self::PROJECT_OVERVIEW && !$currentEnvironment
985
				]);
986
				if ($limit > 0 && $navigation->count() > $limit) {
987
					$navigation->pop();
988
				}
989
			}
990
		}
991
992
		return $navigation;
993
	}
994
995
	/**
996
	 * Construct the deployment form
997
	 *
998
	 * @deprecated 2.0.0 - moved to DeployDispatcher
999
	 *
1000
	 * @return Form
1001
	 */
1002
	public function getDeployForm($request = null) {
1003
1004
		// Performs canView permission check by limiting visible projects
1005
		$project = $this->getCurrentProject();
1006
		if (!$project) {
1007
			return $this->project404Response();
1008
		}
1009
1010
		// Performs canView permission check by limiting visible projects
1011
		$environment = $this->getCurrentEnvironment($project);
1012
		if (!$environment) {
1013
			return $this->environment404Response();
1014
		}
1015
1016
		if (!$environment->canDeploy()) {
1017
			return new SS_HTTPResponse("Not allowed to deploy", 401);
1018
		}
1019
1020
		// Generate the form
1021
		$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...
1022
1023
		// If this is an ajax request we don't want to submit the form - we just want to retrieve the markup.
1024
		if (
1025
			$request &&
1026
			!$request->requestVar('action_showDeploySummary') &&
1027
			$this->getRequest()->isAjax() &&
1028
			$this->getRequest()->isGET()
1029
		) {
1030
			// We can just use the URL we're accessing
1031
			$form->setFormAction($this->getRequest()->getURL());
1032
1033
			$body = json_encode(['Content' => $form->forAjaxTemplate()->forTemplate()]);
1034
			$this->getResponse()->addHeader('Content-Type', 'application/json');
1035
			$this->getResponse()->setBody($body);
1036
			return $body;
1037
		}
1038
1039
		$form->setFormAction($this->getRequest()->getURL() . '/DeployForm');
1040
		return $form;
1041
	}
1042
1043
	/**
1044
	 * @deprecated 2.0.0 - moved to GitDispatcher
1045
	 *
1046
	 * @param \SS_HTTPRequest $request
1047
	 *
1048
	 * @return SS_HTTPResponse|string
1049
	 */
1050
	public function gitRevisions(\SS_HTTPRequest $request) {
1051
1052
		// Performs canView permission check by limiting visible projects
1053
		$project = $this->getCurrentProject();
1054
		if (!$project) {
1055
			return $this->project404Response();
1056
		}
1057
1058
		// Performs canView permission check by limiting visible projects
1059
		$env = $this->getCurrentEnvironment($project);
1060
		if (!$env) {
1061
			return $this->environment404Response();
1062
		}
1063
1064
		$options = [];
1065
		foreach ($env->getSupportedOptions() as $option) {
1066
			$options[] = [
1067
				'name' => $option->getName(),
1068
				'title' => $option->getTitle(),
1069
				'defaultValue' => $option->getDefaultValue()
1070
			];
1071
		}
1072
1073
		$tabs = [];
1074
		$id = 0;
1075
		$data = [
1076
			'id' => ++$id,
1077
			'name' => 'Deploy the latest version of a branch',
1078
			'field_type' => 'dropdown',
1079
			'field_label' => 'Choose a branch',
1080
			'field_id' => 'branch',
1081
			'field_data' => [],
1082
			'options' => $options
1083
		];
1084
		foreach ($project->DNBranchList() as $branch) {
1085
			$sha = $branch->SHA();
1086
			$name = $branch->Name();
1087
			$branchValue = sprintf("%s (%s, %s old)",
1088
				$name,
1089
				substr($sha, 0, 8),
1090
				$branch->LastUpdated()->TimeDiff()
1091
			);
1092
			$data['field_data'][] = [
1093
				'id' => $sha,
1094
				'text' => $branchValue,
1095
				'branch_name' => $name // the raw branch name, not including the time etc
1096
			];
1097
		}
1098
		$tabs[] = $data;
1099
1100
		$data = [
1101
			'id' => ++$id,
1102
			'name' => 'Deploy a tagged release',
1103
			'field_type' => 'dropdown',
1104
			'field_label' => 'Choose a tag',
1105
			'field_id' => 'tag',
1106
			'field_data' => [],
1107
			'options' => $options
1108
		];
1109
1110
		foreach ($project->DNTagList()->setLimit(null) as $tag) {
1111
			$name = $tag->Name();
1112
			$data['field_data'][] = [
1113
				'id' => $tag->SHA(),
1114
				'text' => sprintf("%s", $name)
1115
			];
1116
		}
1117
1118
		// show newest tags first.
1119
		$data['field_data'] = array_reverse($data['field_data']);
1120
1121
		$tabs[] = $data;
1122
1123
		// Past deployments
1124
		$data = [
1125
			'id' => ++$id,
1126
			'name' => 'Redeploy a release that was previously deployed (to any environment)',
1127
			'field_type' => 'dropdown',
1128
			'field_label' => 'Choose a previously deployed release',
1129
			'field_id' => 'release',
1130
			'field_data' => [],
1131
			'options' => $options
1132
		];
1133
		// We are aiming at the format:
1134
		// [{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...
1135
		$redeploy = [];
1136 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...
1137
			$envName = $dnEnvironment->Name;
1138
			$perEnvDeploys = [];
1139
1140
			foreach ($dnEnvironment->DeployHistory() as $deploy) {
1141
				$sha = $deploy->SHA;
1142
1143
				// Check if exists to make sure the newest deployment date is used.
1144
				if (!isset($perEnvDeploys[$sha])) {
1145
					$pastValue = sprintf("%s (deployed %s)",
1146
						substr($sha, 0, 8),
1147
						$deploy->obj('LastEdited')->Ago()
1148
					);
1149
					$perEnvDeploys[$sha] = [
1150
						'id' => $sha,
1151
						'text' => $pastValue
1152
					];
1153
				}
1154
			}
1155
1156
			if (!empty($perEnvDeploys)) {
1157
				$redeploy[$envName] = array_values($perEnvDeploys);
1158
			}
1159
		}
1160
		// Convert the array to the frontend format (i.e. keyed to regular array)
1161
		foreach ($redeploy as $name => $descr) {
1162
			$data['field_data'][] = ['text' => $name, 'children' => $descr];
1163
		}
1164
		$tabs[] = $data;
1165
1166
		$data = [
1167
			'id' => ++$id,
1168
			'name' => 'Deploy a specific SHA',
1169
			'field_type' => 'textfield',
1170
			'field_label' => 'Choose a SHA',
1171
			'field_id' => 'SHA',
1172
			'field_data' => [],
1173
			'options' => $options
1174
		];
1175
		$tabs[] = $data;
1176
1177
		// get the last time git fetch was run
1178
		$lastFetched = 'never';
1179
		$fetch = DNGitFetch::get()
1180
			->filter('ProjectID', $project->ID)
1181
			->sort('LastEdited', 'DESC')
1182
			->first();
1183
		if ($fetch) {
1184
			$lastFetched = $fetch->dbObject('LastEdited')->Ago();
1185
		}
1186
1187
		$data = [
1188
			'Tabs' => $tabs,
1189
			'last_fetched' => $lastFetched
1190
		];
1191
1192
		$this->applyRedeploy($request, $data);
1193
1194
		return json_encode($data, JSON_PRETTY_PRINT);
1195
	}
1196
1197
	/**
1198
	 * @deprecated 2.0.0 - moved to PlanDispatcher
1199
	 *
1200
	 * @param \SS_HTTPRequest $request
1201
	 *
1202
	 * @return string
1203
	 */
1204
	public function deploySummary(\SS_HTTPRequest $request) {
1205
1206
		// Performs canView permission check by limiting visible projects
1207
		$project = $this->getCurrentProject();
1208
		if (!$project) {
1209
			return $this->project404Response();
1210
		}
1211
1212
		// Performs canView permission check by limiting visible projects
1213
		$environment = $this->getCurrentEnvironment($project);
1214
		if (!$environment) {
1215
			return $this->environment404Response();
1216
		}
1217
1218
		// Plan the deployment.
1219
		$strategy = $environment->getDeployStrategy($request);
1220
		$data = $strategy->toArray();
1221
1222
		// Add in a URL for comparing from->to code changes. Ensure that we have
1223
		// two proper 40 character SHAs, otherwise we can't show the compare link.
1224
		$interface = $project->getRepositoryInterface();
1225
		if (
1226
			!empty($interface) && !empty($interface->URL)
1227
			&& !empty($data['changes']['Code version']['from'])
1228
			&& strlen($data['changes']['Code version']['from']) == '40'
1229
			&& !empty($data['changes']['Code version']['to'])
1230
			&& strlen($data['changes']['Code version']['to']) == '40'
1231
		) {
1232
			$compareurl = sprintf(
1233
				'%s/compare/%s...%s',
1234
				$interface->URL,
1235
				$data['changes']['Code version']['from'],
1236
				$data['changes']['Code version']['to']
1237
			);
1238
			$data['changes']['Code version']['compareUrl'] = $compareurl;
1239
		}
1240
1241
		// Append json to response
1242
		$token = SecurityToken::inst();
1243
		$data['SecurityID'] = $token->getValue();
1244
1245
		$this->extend('updateDeploySummary', $data);
1246
1247
		return json_encode($data);
1248
	}
1249
1250
	/**
1251
	 * Deployment form submission handler.
1252
	 *
1253
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1254
	 *
1255
	 * Initiate a DNDeployment record and redirect to it for status polling
1256
	 *
1257
	 * @param \SS_HTTPRequest $request
1258
	 *
1259
	 * @return SS_HTTPResponse
1260
	 * @throws ValidationException
1261
	 * @throws null
1262
	 */
1263
	public function startDeploy(\SS_HTTPRequest $request) {
1264
1265
		$token = SecurityToken::inst();
1266
1267
		// Ensure the submitted token has a value
1268
		$submittedToken = $request->postVar(\Dispatcher::SECURITY_TOKEN_NAME);
1269
		if (!$submittedToken) {
1270
			return false;
1271
		}
1272
		// Do the actual check.
1273
		$check = $token->check($submittedToken);
1274
		// Ensure the CSRF Token is correct
1275
		if (!$check) {
1276
			// CSRF token didn't match
1277
			return $this->httpError(400, 'Bad Request');
1278
		}
1279
1280
		// Performs canView permission check by limiting visible projects
1281
		$project = $this->getCurrentProject();
1282
		if (!$project) {
1283
			return $this->project404Response();
1284
		}
1285
1286
		// Performs canView permission check by limiting visible projects
1287
		$environment = $this->getCurrentEnvironment($project);
1288
		if (!$environment) {
1289
			return $this->environment404Response();
1290
		}
1291
1292
		// Initiate the deployment
1293
		// The extension point should pass in: Project, Environment, SelectRelease, buildName
1294
		$this->extend('doDeploy', $project, $environment, $buildName, $data);
1295
1296
		// Start the deployment based on the approved strategy.
1297
		$strategy = new DeploymentStrategy($environment);
1298
		$strategy->fromArray($request->requestVar('strategy'));
1299
		$deployment = $strategy->createDeployment();
1300
		// Bypass approval by going straight to Queued.
1301
		$deployment->getMachine()->apply(DNDeployment::TR_QUEUE);
1302
1303
		return json_encode([
1304
			'url' => Director::absoluteBaseURL() . $deployment->Link()
1305
		], JSON_PRETTY_PRINT);
1306
	}
1307
1308
	/**
1309
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1310
	 *
1311
	 * Action - Do the actual deploy
1312
	 *
1313
	 * @param \SS_HTTPRequest $request
1314
	 *
1315
	 * @return SS_HTTPResponse|string
1316
	 * @throws SS_HTTPResponse_Exception
1317
	 */
1318
	public function deploy(\SS_HTTPRequest $request) {
1319
		$params = $request->params();
1320
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1321
1322
		if (!$deployment || !$deployment->ID) {
1323
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1324
		}
1325
		if (!$deployment->canView()) {
1326
			return Security::permissionFailure();
1327
		}
1328
1329
		$environment = $deployment->Environment();
1330
		$project = $environment->Project();
1331
1332
		if ($environment->Name != $params['Environment']) {
1333
			throw new LogicException("Environment in URL doesn't match this deploy");
1334
		}
1335
		if ($project->Name != $params['Project']) {
1336
			throw new LogicException("Project in URL doesn't match this deploy");
1337
		}
1338
1339
		return $this->render([
1340
			'Deployment' => $deployment,
1341
		]);
1342
	}
1343
1344
	/**
1345
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1346
	 *
1347
	 * Action - Get the latest deploy log
1348
	 *
1349
	 * @param \SS_HTTPRequest $request
1350
	 *
1351
	 * @return string
1352
	 * @throws SS_HTTPResponse_Exception
1353
	 */
1354
	public function deploylog(\SS_HTTPRequest $request) {
1355
		$params = $request->params();
1356
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1357
1358
		if (!$deployment || !$deployment->ID) {
1359
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1360
		}
1361
		if (!$deployment->canView()) {
1362
			return Security::permissionFailure();
1363
		}
1364
1365
		$environment = $deployment->Environment();
1366
		$project = $environment->Project();
1367
1368
		if ($environment->Name != $params['Environment']) {
1369
			throw new LogicException("Environment in URL doesn't match this deploy");
1370
		}
1371
		if ($project->Name != $params['Project']) {
1372
			throw new LogicException("Project in URL doesn't match this deploy");
1373
		}
1374
1375
		$log = $deployment->log();
1376
		if ($log->exists()) {
1377
			$content = $log->content();
1378
		} else {
1379
			$content = 'Waiting for action to start';
1380
		}
1381
1382
		return $this->sendResponse($deployment->ResqueStatus(), $content);
1383
	}
1384
1385
	public function abortDeploy(\SS_HTTPRequest $request) {
1386
		$params = $request->params();
1387
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1388
1389
		if (!$deployment || !$deployment->ID) {
1390
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1391
		}
1392
		if (!$deployment->canView()) {
1393
			return Security::permissionFailure();
1394
		}
1395
1396
		// For now restrict to ADMINs only.
1397
		if (!Permission::check('ADMIN')) {
1398
			return Security::permissionFailure();
1399
		}
1400
1401
		$environment = $deployment->Environment();
1402
		$project = $environment->Project();
1403
1404
		if ($environment->Name != $params['Environment']) {
1405
			throw new LogicException("Environment in URL doesn't match this deploy");
1406
		}
1407
		if ($project->Name != $params['Project']) {
1408
			throw new LogicException("Project in URL doesn't match this deploy");
1409
		}
1410
1411
		if (!in_array($deployment->Status, ['Queued', 'Deploying', 'Aborting'])) {
1412
			throw new LogicException(sprintf("Cannot abort from %s state.", $deployment->Status));
1413
		}
1414
1415
		$deployment->getMachine()->apply(DNDeployment::TR_ABORT);
1416
1417
		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...
1418
	}
1419
1420
	/**
1421
	 * @param \SS_HTTPRequest|null $request
1422
	 *
1423
	 * @return Form
1424
	 */
1425
	public function getDataTransferForm(\SS_HTTPRequest $request = null) {
1426
		// Performs canView permission check by limiting visible projects
1427
		$envs = $this->getCurrentProject()->DNEnvironmentList()->filterByCallback(function ($item) {
1428
			return $item->canBackup();
1429
		});
1430
1431
		if (!$envs) {
1432
			return $this->environment404Response();
1433
		}
1434
1435
		$items = [];
1436
		$disabledEnvironments = [];
1437 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...
1438
			$items[$env->ID] = $env->Title;
1439
			if ($env->CurrentBuild() === false) {
1440
				$items[$env->ID] = sprintf("%s - requires initial deployment", $env->Title);
1441
				$disabledEnvironments[] = $env->ID;
1442
			}
1443
		}
1444
1445
		$envsField =  DropdownField::create('EnvironmentID', 'Environment', $items)
1446
			->setEmptyString('Select an environment');
1447
		$envsField->setDisabledItems($disabledEnvironments);
1448
1449
		$formAction = FormAction::create('doDataTransfer', 'Create')
1450
			->addExtraClass('btn');
1451
1452
		if (count($disabledEnvironments) === $envs->count()) {
1453
			$formAction->setDisabled(true);
1454
		}
1455
1456
		$form = Form::create(
1457
			$this,
1458
			'DataTransferForm',
1459
			FieldList::create(
1460
				HiddenField::create('Direction', null, 'get'),
1461
				$envsField,
1462
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map())
1463
			),
1464
			FieldList::create($formAction)
1465
		);
1466
		$form->setFormAction($this->getRequest()->getURL() . '/DataTransferForm');
1467
1468
		return $form;
1469
	}
1470
1471
	/**
1472
	 * @param array $data
1473
	 * @param Form $form
1474
	 *
1475
	 * @return SS_HTTPResponse
1476
	 * @throws SS_HTTPResponse_Exception
1477
	 */
1478
	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...
1479
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1480
1481
		// Performs canView permission check by limiting visible projects
1482
		$project = $this->getCurrentProject();
1483
		if (!$project) {
1484
			return $this->project404Response();
1485
		}
1486
1487
		$dataArchive = null;
1488
1489
		// Validate direction.
1490
		if ($data['Direction'] == 'get') {
1491
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1492
				->filterByCallback(function ($item) {
1493
					return $item->canBackup();
1494
				});
1495
		} else if ($data['Direction'] == 'push') {
1496
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1497
				->filterByCallback(function ($item) {
1498
					return $item->canRestore();
1499
				});
1500
		} else {
1501
			throw new LogicException('Invalid direction');
1502
		}
1503
1504
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1505
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1506
		if (!$environment) {
1507
			throw new LogicException('Invalid environment');
1508
		}
1509
1510
		$this->validateSnapshotMode($data['Mode']);
1511
1512
		// Only 'push' direction is allowed an association with an existing archive.
1513
		if (
1514
			$data['Direction'] == 'push'
1515
			&& isset($data['DataArchiveID'])
1516
			&& is_numeric($data['DataArchiveID'])
1517
		) {
1518
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1519
			if (!$dataArchive) {
1520
				throw new LogicException('Invalid data archive');
1521
			}
1522
1523
			if (!$dataArchive->canDownload()) {
1524
				throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1525
			}
1526
		}
1527
1528
		$transfer = DNDataTransfer::create();
1529
		$transfer->EnvironmentID = $environment->ID;
1530
		$transfer->Direction = $data['Direction'];
1531
		$transfer->Mode = $data['Mode'];
1532
		$transfer->DataArchiveID = $dataArchive ? $dataArchive->ID : null;
1533
		if ($data['Direction'] == 'push') {
1534
			$transfer->setBackupBeforePush(!empty($data['BackupBeforePush']));
1535
		}
1536
		$transfer->write();
1537
		$transfer->start();
1538
1539
		return $this->redirect($transfer->Link());
1540
	}
1541
1542
	/**
1543
	 * View into the log for a {@link DNDataTransfer}.
1544
	 *
1545
	 * @param \SS_HTTPRequest $request
1546
	 *
1547
	 * @return SS_HTTPResponse|string
1548
	 * @throws SS_HTTPResponse_Exception
1549
	 */
1550
	public function transfer(\SS_HTTPRequest $request) {
1551
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1552
1553
		$params = $request->params();
1554
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1555
1556
		if (!$transfer || !$transfer->ID) {
1557
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1558
		}
1559
		if (!$transfer->canView()) {
1560
			return Security::permissionFailure();
1561
		}
1562
1563
		$environment = $transfer->Environment();
1564
		$project = $environment->Project();
1565
1566
		if ($project->Name != $params['Project']) {
1567
			throw new LogicException("Project in URL doesn't match this deploy");
1568
		}
1569
1570
		return $this->render([
1571
			'CurrentTransfer' => $transfer,
1572
			'SnapshotsSection' => 1,
1573
		]);
1574
	}
1575
1576
	/**
1577
	 * Action - Get the latest deploy log
1578
	 *
1579
	 * @param \SS_HTTPRequest $request
1580
	 *
1581
	 * @return string
1582
	 * @throws SS_HTTPResponse_Exception
1583
	 */
1584
	public function transferlog(\SS_HTTPRequest $request) {
1585
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1586
1587
		$params = $request->params();
1588
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1589
1590
		if (!$transfer || !$transfer->ID) {
1591
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1592
		}
1593
		if (!$transfer->canView()) {
1594
			return Security::permissionFailure();
1595
		}
1596
1597
		$environment = $transfer->Environment();
1598
		$project = $environment->Project();
1599
1600
		if ($project->Name != $params['Project']) {
1601
			throw new LogicException("Project in URL doesn't match this deploy");
1602
		}
1603
1604
		$log = $transfer->log();
1605
		if ($log->exists()) {
1606
			$content = $log->content();
1607
		} else {
1608
			$content = 'Waiting for action to start';
1609
		}
1610
1611
		return $this->sendResponse($transfer->ResqueStatus(), $content);
1612
	}
1613
1614
	/**
1615
	 * Note: Submits to the same action as {@link getDataTransferForm()},
1616
	 * but with a Direction=push and an archive reference.
1617
	 *
1618
	 * @param \SS_HTTPRequest $request
1619
	 * @param \DNDataArchive|null $dataArchive Only set when method is called manually in {@link restore()},
1620
	 *                            otherwise the state is inferred from the request data.
1621
	 * @return Form
1622
	 */
1623
	public function getDataTransferRestoreForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1624
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1625
1626
		// Performs canView permission check by limiting visible projects
1627
		$project = $this->getCurrentProject();
1628
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
1629
			return $item->canRestore();
1630
		});
1631
1632
		if (!$envs) {
1633
			return $this->environment404Response();
1634
		}
1635
1636
		$modesMap = [];
1637
		if (in_array($dataArchive->Mode, ['all'])) {
1638
			$modesMap['all'] = 'Database and Assets';
1639
		};
1640
		if (in_array($dataArchive->Mode, ['all', 'db'])) {
1641
			$modesMap['db'] = 'Database only';
1642
		};
1643
		if (in_array($dataArchive->Mode, ['all', 'assets'])) {
1644
			$modesMap['assets'] = 'Assets only';
1645
		};
1646
1647
		$alertMessage = '<div class="alert alert-warning"><strong>Warning:</strong> '
1648
			. 'This restore will overwrite the data on the chosen environment below</div>';
1649
1650
1651
		$items = [];
1652
		$disabledEnvironments = [];
1653 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...
1654
			$items[$env->ID] = $env->Title;
1655
			if ($env->CurrentBuild() === false) {
1656
				$items[$env->ID] = sprintf("%s - requires initial deployment", $env->Title);
1657
				$disabledEnvironments[] = $env->ID;
1658
			}
1659
		}
1660
1661
		$envsField = DropdownField::create('EnvironmentID', 'Environment', $items)
1662
			->setEmptyString('Select an environment');
1663
		$envsField->setDisabledItems($disabledEnvironments);
1664
		$formAction = FormAction::create('doDataTransfer', 'Restore Data')->addExtraClass('btn');
1665
1666
		if (count($disabledEnvironments) == $envs->count()) {
1667
			$formAction->setDisabled(true);
1668
		}
1669
1670
		$form = Form::create(
1671
			$this,
1672
			'DataTransferRestoreForm',
1673
			FieldList::create(
1674
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1675
				HiddenField::create('Direction', null, 'push'),
1676
				LiteralField::create('Warning', $alertMessage),
1677
				$envsField,
1678
				DropdownField::create('Mode', 'Transfer', $modesMap),
1679
				CheckboxField::create('BackupBeforePush', 'Backup existing data', '1')
1680
			),
1681
			FieldList::create($formAction)
1682
		);
1683
		$form->setFormAction($project->Link() . '/DataTransferRestoreForm');
1684
1685
		return $form;
1686
	}
1687
1688
	/**
1689
	 * View a form to restore a specific {@link DataArchive}.
1690
	 * Permission checks are handled in {@link DataArchives()}.
1691
	 * Submissions are handled through {@link doDataTransfer()}, same as backup operations.
1692
	 *
1693
	 * @param \SS_HTTPRequest $request
1694
	 *
1695
	 * @return HTMLText
1696
	 * @throws SS_HTTPResponse_Exception
1697
	 */
1698 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...
1699
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1700
1701
		/** @var DNDataArchive $dataArchive */
1702
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1703
1704
		if (!$dataArchive) {
1705
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1706
		}
1707
1708
		// We check for canDownload because that implies access to the data.
1709
		// canRestore is later checked on the actual restore action per environment.
1710
		if (!$dataArchive->canDownload()) {
1711
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1712
		}
1713
1714
		$form = $this->getDataTransferRestoreForm($this->request, $dataArchive);
1715
1716
		// View currently only available via ajax
1717
		return $form->forTemplate();
1718
	}
1719
1720
	/**
1721
	 * View a form to delete a specific {@link DataArchive}.
1722
	 * Permission checks are handled in {@link DataArchives()}.
1723
	 * Submissions are handled through {@link doDelete()}.
1724
	 *
1725
	 * @param \SS_HTTPRequest $request
1726
	 *
1727
	 * @return HTMLText
1728
	 * @throws SS_HTTPResponse_Exception
1729
	 */
1730 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...
1731
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1732
1733
		/** @var DNDataArchive $dataArchive */
1734
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1735
1736
		if (!$dataArchive) {
1737
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1738
		}
1739
1740
		if (!$dataArchive->canDelete()) {
1741
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1742
		}
1743
1744
		$form = $this->getDeleteForm($this->request, $dataArchive);
1745
1746
		// View currently only available via ajax
1747
		return $form->forTemplate();
1748
	}
1749
1750
	/**
1751
	 * @param \SS_HTTPRequest $request
1752
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually, otherwise the state is inferred
1753
	 *        from the request data.
1754
	 * @return Form
1755
	 */
1756
	public function getDeleteForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1757
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1758
1759
		// Performs canView permission check by limiting visible projects
1760
		$project = $this->getCurrentProject();
1761
		if (!$project) {
1762
			return $this->project404Response();
1763
		}
1764
1765
		$snapshotDeleteWarning = '<div class="alert alert-warning">'
1766
			. 'Are you sure you want to permanently delete this snapshot from this archive area?'
1767
			. '</div>';
1768
1769
		$form = Form::create(
1770
			$this,
1771
			'DeleteForm',
1772
			FieldList::create(
1773
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1774
				LiteralField::create('Warning', $snapshotDeleteWarning)
1775
			),
1776
			FieldList::create(
1777
				FormAction::create('doDelete', 'Delete')
1778
					->addExtraClass('btn')
1779
			)
1780
		);
1781
		$form->setFormAction($project->Link() . '/DeleteForm');
1782
1783
		return $form;
1784
	}
1785
1786
	/**
1787
	 * @param array $data
1788
	 * @param Form $form
1789
	 *
1790
	 * @return bool|SS_HTTPResponse
1791
	 * @throws SS_HTTPResponse_Exception
1792
	 */
1793
	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...
1794
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1795
1796
		// Performs canView permission check by limiting visible projects
1797
		$project = $this->getCurrentProject();
1798
		if (!$project) {
1799
			return $this->project404Response();
1800
		}
1801
1802
		$dataArchive = null;
1803
1804
		if (
1805
			isset($data['DataArchiveID'])
1806
			&& is_numeric($data['DataArchiveID'])
1807
		) {
1808
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1809
		}
1810
1811
		if (!$dataArchive) {
1812
			throw new LogicException('Invalid data archive');
1813
		}
1814
1815
		if (!$dataArchive->canDelete()) {
1816
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1817
		}
1818
1819
		$dataArchive->delete();
1820
1821
		return $this->redirectBack();
1822
	}
1823
1824
	/**
1825
	 * View a form to move a specific {@link DataArchive}.
1826
	 *
1827
	 * @param \SS_HTTPRequest $request
1828
	 *
1829
	 * @return HTMLText
1830
	 * @throws SS_HTTPResponse_Exception
1831
	 */
1832 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...
1833
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1834
1835
		/** @var DNDataArchive $dataArchive */
1836
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1837
1838
		if (!$dataArchive) {
1839
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1840
		}
1841
1842
		// We check for canDownload because that implies access to the data.
1843
		if (!$dataArchive->canDownload()) {
1844
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1845
		}
1846
1847
		$form = $this->getMoveForm($this->request, $dataArchive);
1848
1849
		// View currently only available via ajax
1850
		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...
1851
	}
1852
1853
	/**
1854
	 * Build snapshot move form.
1855
	 *
1856
	 * @param \SS_HTTPRequest $request
1857
	 * @param DNDataArchive|null $dataArchive
1858
	 *
1859
	 * @return Form|SS_HTTPResponse
1860
	 */
1861
	public function getMoveForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1862
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1863
1864
		$envs = $dataArchive->validTargetEnvironments();
1865
		if (!$envs) {
1866
			return $this->environment404Response();
1867
		}
1868
1869
		$warningMessage = '<div class="alert alert-warning"><strong>Warning:</strong> This will make the snapshot '
1870
			. 'available to people with access to the target environment.<br>By pressing "Change ownership" you '
1871
			. 'confirm that you have considered data confidentiality regulations.</div>';
1872
1873
		$form = Form::create(
1874
			$this,
1875
			'MoveForm',
1876
			FieldList::create(
1877
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1878
				LiteralField::create('Warning', $warningMessage),
1879
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1880
					->setEmptyString('Select an environment')
1881
			),
1882
			FieldList::create(
1883
				FormAction::create('doMove', 'Change ownership')
1884
					->addExtraClass('btn')
1885
			)
1886
		);
1887
		$form->setFormAction($this->getCurrentProject()->Link() . '/MoveForm');
1888
1889
		return $form;
1890
	}
1891
1892
	/**
1893
	 * @param array $data
1894
	 * @param Form $form
1895
	 *
1896
	 * @return bool|SS_HTTPResponse
1897
	 * @throws SS_HTTPResponse_Exception
1898
	 * @throws ValidationException
1899
	 * @throws null
1900
	 */
1901
	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...
1902
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1903
1904
		// Performs canView permission check by limiting visible projects
1905
		$project = $this->getCurrentProject();
1906
		if (!$project) {
1907
			return $this->project404Response();
1908
		}
1909
1910
		/** @var DNDataArchive $dataArchive */
1911
		$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1912
		if (!$dataArchive) {
1913
			throw new LogicException('Invalid data archive');
1914
		}
1915
1916
		// We check for canDownload because that implies access to the data.
1917
		if (!$dataArchive->canDownload()) {
1918
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1919
		}
1920
1921
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1922
		$validEnvs = $dataArchive->validTargetEnvironments();
1923
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1924
		if (!$environment) {
1925
			throw new LogicException('Invalid environment');
1926
		}
1927
1928
		$dataArchive->EnvironmentID = $environment->ID;
1929
		$dataArchive->write();
1930
1931
		return $this->redirectBack();
1932
	}
1933
1934
	/**
1935
	 * Returns an error message if redis is unavailable
1936
	 *
1937
	 * @return string
1938
	 */
1939
	public static function RedisUnavailable() {
1940
		try {
1941
			Resque::queues();
1942
		} catch (Exception $e) {
1943
			return $e->getMessage();
1944
		}
1945
		return '';
1946
	}
1947
1948
	/**
1949
	 * Returns the number of connected Redis workers
1950
	 *
1951
	 * @return int
1952
	 */
1953
	public static function RedisWorkersCount() {
1954
		return count(Resque_Worker::all());
1955
	}
1956
1957
	/**
1958
	 * @return array
1959
	 */
1960
	public function providePermissions() {
1961
		return [
1962
			self::DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS => [
1963
				'name' => "Access to advanced deploy options",
1964
				'category' => "Deploynaut",
1965
			],
1966
1967
			// Permissions that are intended to be added to the roles.
1968
			self::ALLOW_PROD_DEPLOYMENT => [
1969
				'name' => "Ability to deploy to production environments",
1970
				'category' => "Deploynaut",
1971
			],
1972
			self::ALLOW_NON_PROD_DEPLOYMENT => [
1973
				'name' => "Ability to deploy to non-production environments",
1974
				'category' => "Deploynaut",
1975
			],
1976
			self::ALLOW_PROD_SNAPSHOT => [
1977
				'name' => "Ability to make production snapshots",
1978
				'category' => "Deploynaut",
1979
			],
1980
			self::ALLOW_NON_PROD_SNAPSHOT => [
1981
				'name' => "Ability to make non-production snapshots",
1982
				'category' => "Deploynaut",
1983
			],
1984
			self::ALLOW_CREATE_ENVIRONMENT => [
1985
				'name' => "Ability to create environments",
1986
				'category' => "Deploynaut",
1987
			],
1988
		];
1989
	}
1990
1991
	/**
1992
	 * @return DNProject|null
1993
	 */
1994
	public function getCurrentProject() {
1995
		$projectName = trim($this->getRequest()->param('Project'));
1996
		if (!$projectName) {
1997
			return null;
1998
		}
1999
		if (empty(self::$_project_cache[$projectName])) {
2000
			self::$_project_cache[$projectName] = $this->DNProjectList()->filter('Name', $projectName)->First();
2001
		}
2002
		return self::$_project_cache[$projectName];
2003
	}
2004
2005
	/**
2006
	 * @param \DNProject|null $project
2007
	 * @return \DNEnvironment|null
2008
	 */
2009
	public function getCurrentEnvironment(\DNProject $project = null) {
2010
		if ($this->getRequest()->param('Environment') === null) {
2011
			return null;
2012
		}
2013
		if ($project === null) {
2014
			$project = $this->getCurrentProject();
2015
		}
2016
		// project can still be null
2017
		if ($project === null) {
2018
			return null;
2019
		}
2020
		return $project->DNEnvironmentList()->filter('Name', $this->getRequest()->param('Environment'))->First();
2021
	}
2022
2023
	/**
2024
	 * This will return a const that indicates the class of action currently being performed
2025
	 *
2026
	 * Until DNRoot is de-godded, it does a bunch of different actions all in the same class.
2027
	 * So we just have each action handler calll setCurrentActionType to define what sort of
2028
	 * action it is.
2029
	 *
2030
	 * @return string - one of the consts from self::$action_types
2031
	 */
2032
	public function getCurrentActionType() {
2033
		return $this->actionType;
2034
	}
2035
2036
	/**
2037
	 * Sets the current action type
2038
	 *
2039
	 * @param string $actionType string - one of the consts from self::$action_types
2040
	 */
2041
	public function setCurrentActionType($actionType) {
2042
		$this->actionType = $actionType;
2043
	}
2044
2045
	/**
2046
	 * Helper method to allow templates to know whether they should show the 'Archive List' include or not.
2047
	 * The actual permissions are set on a per-environment level, so we need to find out if this $member can upload to
2048
	 * or download from *any* {@link DNEnvironment} that (s)he has access to.
2049
	 *
2050
	 * TODO To be replaced with a method that just returns the list of archives this {@link Member} has access to.
2051
	 *
2052
	 * @param Member|null $member The {@link Member} to check (or null to check the currently logged in Member)
2053
	 * @return boolean|null true if $member has access to upload or download to at least one {@link DNEnvironment}.
2054
	 */
2055
	public function CanViewArchives(\Member $member = null) {
2056
		if ($member === null) {
2057
			$member = Member::currentUser();
2058
		}
2059
2060
		if (Permission::checkMember($member, 'ADMIN')) {
2061
			return true;
2062
		}
2063
2064
		$allProjects = $this->DNProjectList();
2065
		if (!$allProjects) {
2066
			return false;
2067
		}
2068
2069
		foreach ($allProjects as $project) {
2070
			if ($project->Environments()) {
2071
				foreach ($project->Environments() as $environment) {
2072
					if (
2073
						$environment->canRestore($member) ||
2074
						$environment->canBackup($member) ||
2075
						$environment->canUploadArchive($member) ||
2076
						$environment->canDownloadArchive($member)
2077
					) {
2078
						// We can return early as we only need to know that we can access one environment
2079
						return true;
2080
					}
2081
				}
2082
			}
2083
		}
2084
	}
2085
2086
	/**
2087
	 * Returns a list of attempted environment creations.
2088
	 *
2089
	 * @return PaginatedList
2090
	 */
2091
	public function CreateEnvironmentList() {
2092
		$project = $this->getCurrentProject();
2093
		if ($project) {
2094
			$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...
2095
		} else {
2096
			$dataList = new ArrayList();
2097
		}
2098
2099
		$this->extend('updateCreateEnvironmentList', $dataList);
2100
		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...
2101
	}
2102
2103
	/**
2104
	 * Returns a list of all archive files that can be accessed by the currently logged-in {@link Member}
2105
	 *
2106
	 * @return PaginatedList
2107
	 */
2108
	public function CompleteDataArchives() {
2109
		$project = $this->getCurrentProject();
2110
		$archives = new ArrayList();
2111
2112
		$archiveList = $project->Environments()->relation("DataArchives");
2113
		if ($archiveList->count() > 0) {
2114
			foreach ($archiveList as $archive) {
2115
				if (!$archive->isPending()) {
2116
					$archives->push($archive);
2117
				}
2118
			}
2119
		}
2120
		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...
2121
	}
2122
2123
	/**
2124
	 * @return PaginatedList The list of "pending" data archives which are waiting for a file
2125
	 * to be delivered offline by post, and manually uploaded into the system.
2126
	 */
2127
	public function PendingDataArchives() {
2128
		$project = $this->getCurrentProject();
2129
		$archives = new ArrayList();
2130
		foreach ($project->DNEnvironmentList() as $env) {
2131
			foreach ($env->DataArchives() as $archive) {
2132
				if ($archive->isPending()) {
2133
					$archives->push($archive);
2134
				}
2135
			}
2136
		}
2137
		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...
2138
	}
2139
2140
	/**
2141
	 * @return PaginatedList
2142
	 */
2143
	public function DataTransferLogs() {
2144
		$environments = $this->getCurrentProject()->Environments()->column('ID');
2145
		$transfers = DNDataTransfer::get()
2146
			->filter('EnvironmentID', $environments)
2147
			->filterByCallback(
2148
				function ($record) {
2149
					return
2150
						$record->Environment()->canRestore() || // Ensure member can perform an action on the transfers env
2151
						$record->Environment()->canBackup() ||
2152
						$record->Environment()->canUploadArchive() ||
2153
						$record->Environment()->canDownloadArchive();
2154
				});
2155
2156
		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...
2157
	}
2158
2159
	/**
2160
	 * @deprecated 2.0.0 - moved to DeployDispatcher
2161
	 *
2162
	 * @return null|PaginatedList
2163
	 */
2164
	public function DeployHistory() {
2165
		if ($env = $this->getCurrentEnvironment()) {
2166
			$history = $env->DeployHistory();
2167
			if ($history->count() > 0) {
2168
				$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...
2169
				$pagination->setPageLength(4);
2170
				return $pagination;
2171
			}
2172
		}
2173
		return null;
2174
	}
2175
2176
	/**
2177
	 * @param string $status
2178
	 * @param string $content
2179
	 *
2180
	 * @return string
2181
	 */
2182
	public function sendResponse($status, $content) {
2183
		// strip excessive newlines
2184
		$content = preg_replace('/(?:(?:\r\n|\r|\n)\s*){2}/s', "\n", $content);
2185
2186
		$sendJSON = (strpos($this->getRequest()->getHeader('Accept'), 'application/json') !== false)
2187
			|| $this->getRequest()->getExtension() == 'json';
2188
2189
		if (!$sendJSON) {
2190
			$this->response->addHeader("Content-type", "text/plain");
2191
			return $content;
2192
		}
2193
		$this->response->addHeader("Content-type", "application/json");
2194
		return json_encode([
2195
			'status' => $status,
2196
			'content' => $content,
2197
		]);
2198
	}
2199
2200
	/**
2201
	 * Get items for the ambient menu that should be accessible from all pages.
2202
	 *
2203
	 * @return ArrayList
2204
	 */
2205
	public function AmbientMenu() {
2206
		$list = new ArrayList();
2207
2208
		if (Member::currentUserID()) {
2209
			$list->push(new ArrayData([
2210
				'Classes' => 'logout',
2211
				'FaIcon' => 'sign-out',
2212
				'Link' => 'Security/logout',
2213
				'Title' => 'Log out',
2214
				'IsCurrent' => false,
2215
				'IsSection' => false
2216
			]));
2217
		}
2218
2219
		$this->extend('updateAmbientMenu', $list);
2220
		return $list;
2221
	}
2222
2223
	/**
2224
	 * Checks whether the user can create a project.
2225
	 *
2226
	 * @return bool
2227
	 */
2228
	public function canCreateProjects($member = null) {
2229
		if (!$member) {
2230
			$member = Member::currentUser();
2231
		}
2232
		if (!$member) {
2233
			return false;
2234
		}
2235
2236
		return singleton('DNProject')->canCreate($member);
2237
	}
2238
2239
	protected function applyRedeploy(\SS_HTTPRequest $request, &$data) {
2240
		if (!$request->getVar('redeploy')) {
2241
			return;
2242
		}
2243
2244
		$project = $this->getCurrentProject();
2245
		if (!$project) {
2246
			return $this->project404Response();
2247
		}
2248
2249
		// Performs canView permission check by limiting visible projects
2250
		$env = $this->getCurrentEnvironment($project);
2251
		if (!$env) {
2252
			return $this->environment404Response();
2253
		}
2254
2255
		$current = $env->CurrentBuild();
2256
		if ($current && $current->exists()) {
2257
			$data['preselect_tab'] = 3;
2258
			$data['preselect_sha'] = $current->SHA;
2259
		} else {
2260
			$master = $project->DNBranchList()->byName('master');
2261
			if ($master) {
2262
				$data['preselect_tab'] = 1;
2263
				$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...
2264
			}
2265
		}
2266
	}
2267
2268
	/**
2269
	 * @return SS_HTTPResponse
2270
	 */
2271
	protected function project404Response() {
2272
		return new SS_HTTPResponse(
2273
			"Project '" . Convert::raw2xml($this->getRequest()->param('Project')) . "' not found.",
2274
			404
2275
		);
2276
	}
2277
2278
	/**
2279
	 * @return SS_HTTPResponse
2280
	 */
2281
	protected function environment404Response() {
2282
		$envName = Convert::raw2xml($this->getRequest()->param('Environment'));
2283
		return new SS_HTTPResponse("Environment '" . $envName . "' not found.", 404);
2284
	}
2285
2286
	/**
2287
	 * Validate the snapshot mode
2288
	 *
2289
	 * @param string $mode
2290
	 */
2291
	protected function validateSnapshotMode($mode) {
2292
		if (!in_array($mode, ['all', 'assets', 'db'])) {
2293
			throw new LogicException('Invalid mode');
2294
		}
2295
	}
2296
2297
	/**
2298
	 * @param string $sectionName
2299
	 * @param string $title
2300
	 *
2301
	 * @return SS_HTTPResponse
2302
	 */
2303
	protected function getCustomisedViewSection($sectionName, $title = '', $data = []) {
2304
		// Performs canView permission check by limiting visible projects
2305
		$project = $this->getCurrentProject();
2306
		if (!$project) {
2307
			return $this->project404Response();
2308
		}
2309
		$data[$sectionName] = 1;
2310
2311
		if ($this !== '') {
2312
			$data['Title'] = $title;
2313
		}
2314
2315
		return $this->render($data);
2316
	}
2317
2318
}
2319
2320