Completed
Push — master ( 0e6878...e80bb4 )
by Stig
04:38
created

DNRoot::platform_title()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
155
		self::ACTION_DEPLOY,
156
		self::ACTION_SNAPSHOT,
157
		self::PROJECT_OVERVIEW
158
	];
159
160
	/**
161
	 * Include requirements that deploynaut needs, such as javascript.
162
	 */
163
	public static function include_requirements() {
164
165
		// JS should always go to the bottom, otherwise there's the risk that Requirements
166
		// puts them halfway through the page to the nearest <script> tag. We don't want that.
167
		Requirements::set_force_js_to_bottom(true);
168
169
		// todo these should be bundled into the same JS as the others in "static" below.
170
		// We've deliberately not used combined_files as it can mess with some of the JS used
171
		// here and cause sporadic errors.
172
		Requirements::javascript('deploynaut/javascript/jquery.js');
173
		Requirements::javascript('deploynaut/javascript/bootstrap.js');
174
		Requirements::javascript('deploynaut/javascript/q.js');
175
		Requirements::javascript('deploynaut/javascript/tablefilter.js');
176
		Requirements::javascript('deploynaut/javascript/deploynaut.js');
177
178
		Requirements::javascript('deploynaut/javascript/bootstrap.file-input.js');
179
		Requirements::javascript('deploynaut/thirdparty/select2/dist/js/select2.min.js');
180
		Requirements::javascript('deploynaut/javascript/selectize.js');
181
		Requirements::javascript('deploynaut/thirdparty/bootstrap-switch/dist/js/bootstrap-switch.min.js');
182
		Requirements::javascript('deploynaut/javascript/material.js');
183
184
		// Load the buildable dependencies only if not loaded centrally.
185
		if (!is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'static')) {
186
			if (\Director::isDev()) {
187
				\Requirements::javascript('deploynaut/static/bundle-debug.js');
188
			} else {
189
				\Requirements::javascript('deploynaut/static/bundle.js');
190
			}
191
		}
192
193
		Requirements::css('deploynaut/static/style.css');
194
	}
195
196
	/**
197
	 * @return ArrayList
198
	 */
199
	public static function get_support_links() {
200
		$supportLinks = self::config()->support_links;
201
		if ($supportLinks) {
202
			return new ArrayList($supportLinks);
203
		}
204
	}
205
206
	/**
207
	 * @return ArrayList
208
	 */
209
	public static function get_logged_in_links() {
210
		$loggedInLinks = self::config()->logged_in_links;
211
		if ($loggedInLinks) {
212
			return new ArrayList($loggedInLinks);
213
		}
214
	}
215
216
	/**
217
	 * Return the platform title if configured, defaulting to "Deploynaut".
218
	 * @return string
219
	 */
220
	public static function platform_title() {
221
		if (defined('DEPLOYNAUT_PLATFORM_TITLE')) {
222
			return DEPLOYNAUT_PLATFORM_TITLE;
223
		}
224
		return 'Deploynaut';
225
	}
226
227
	/**
228
	 * @return array
229
	 */
230
	public static function get_template_global_variables() {
231
		return [
232
			'PlatformTitle' => 'platform_title',
233
			'RedisUnavailable' => 'RedisUnavailable',
234
			'RedisWorkersCount' => 'RedisWorkersCount',
235
			'SidebarLinks' => 'SidebarLinks',
236
			'SupportLinks' => 'get_support_links',
237
			'LoggedInLinks' => 'get_logged_in_links',
238
		];
239
	}
240
241
	/**
242
	 */
243
	public function init() {
244
		parent::init();
245
246
		if (!Member::currentUser() && !Session::get('AutoLoginHash')) {
247
			return Security::permissionFailure();
248
		}
249
250
		// Block framework jquery
251
		Requirements::block(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
252
253
		self::include_requirements();
254
	}
255
256
	/**
257
	 * @return string
258
	 */
259
	public function Link() {
260
		return "naut/";
261
	}
262
263
	/**
264
	 * Actions
265
	 *
266
	 * @param \SS_HTTPRequest $request
267
	 * @return \SS_HTTPResponse
268
	 */
269
	public function index(\SS_HTTPRequest $request) {
270
		return $this->redirect($this->Link() . 'projects/');
271
	}
272
273
	/**
274
	 * Action
275
	 *
276
	 * @param \SS_HTTPRequest $request
277
	 * @return string - HTML
278
	 */
279
	public function projects(\SS_HTTPRequest $request) {
280
		// Performs canView permission check by limiting visible projects in DNProjectsList() call.
281
		return $this->customise([
282
			'Title' => 'Projects',
283
		])->render();
284
	}
285
286
	/**
287
	 * @param \SS_HTTPRequest $request
288
	 * @return HTMLText
289
	 */
290
	public function nav(\SS_HTTPRequest $request) {
291
		return $this->renderWith('Nav');
292
	}
293
294
	/**
295
	 * Return a link to the navigation template used for AJAX requests.
296
	 * @return string
297
	 */
298
	public function NavLink() {
299
		$currentProject = $this->getCurrentProject();
300
		$projectName = $currentProject ? $currentProject->Name : null;
301
		return Controller::join_links(Director::absoluteBaseURL(), 'naut', 'nav', $projectName);
302
	}
303
304
	/**
305
	 * Action
306
	 *
307
	 * @param \SS_HTTPRequest $request
308
	 * @return SS_HTTPResponse - HTML
309
	 */
310
	public function snapshots(\SS_HTTPRequest $request) {
311
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
312
		return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots');
313
	}
314
315
	/**
316
	 * Action
317
	 *
318
	 * @param \SS_HTTPRequest $request
319
	 * @return string - HTML
320
	 */
321 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...
322
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
323
324
		// Performs canView permission check by limiting visible projects
325
		$project = $this->getCurrentProject();
326
		if (!$project) {
327
			return $this->project404Response();
328
		}
329
330
		if (!$project->canBackup()) {
331
			return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
332
		}
333
334
		return $this->customise([
335
			'Title' => 'Create Data Snapshot',
336
			'SnapshotsSection' => 1,
337
			'DataTransferForm' => $this->getDataTransferForm($request)
338
		])->render();
339
	}
340
341
	/**
342
	 * Action
343
	 *
344
	 * @param \SS_HTTPRequest $request
345
	 * @return string - HTML
346
	 */
347 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...
348
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
349
350
		// Performs canView permission check by limiting visible projects
351
		$project = $this->getCurrentProject();
352
		if (!$project) {
353
			return $this->project404Response();
354
		}
355
356
		if (!$project->canUploadArchive()) {
357
			return new SS_HTTPResponse("Not allowed to upload", 401);
358
		}
359
360
		return $this->customise([
361
			'SnapshotsSection' => 1,
362
			'UploadSnapshotForm' => $this->getUploadSnapshotForm($request),
363
			'PostSnapshotForm' => $this->getPostSnapshotForm($request)
364
		])->render();
365
	}
366
367
	/**
368
	 * Return the upload limit for snapshot uploads
369
	 * @return string
370
	 */
371
	public function UploadLimit() {
372
		return File::format_size(min(
373
			File::ini2bytes(ini_get('upload_max_filesize')),
374
			File::ini2bytes(ini_get('post_max_size'))
375
		));
376
	}
377
378
	/**
379
	 * Construct the upload form.
380
	 *
381
	 * @param \SS_HTTPRequest $request
382
	 * @return Form
383
	 */
384
	public function getUploadSnapshotForm(\SS_HTTPRequest $request) {
385
		// Performs canView permission check by limiting visible projects
386
		$project = $this->getCurrentProject();
387
		if (!$project) {
388
			return $this->project404Response();
389
		}
390
391
		if (!$project->canUploadArchive()) {
392
			return new SS_HTTPResponse("Not allowed to upload", 401);
393
		}
394
395
		// Framing an environment as a "group of people with download access"
396
		// makes more sense to the user here, while still allowing us to enforce
397
		// environment specific restrictions on downloading the file later on.
398
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
399
			return $item->canUploadArchive();
400
		});
401
		$envsMap = [];
402
		foreach ($envs as $env) {
403
			$envsMap[$env->ID] = $env->Name;
404
		}
405
406
		$maxSize = min(File::ini2bytes(ini_get('upload_max_filesize')), File::ini2bytes(ini_get('post_max_size')));
407
		$fileField = DataArchiveFileField::create('ArchiveFile', 'File');
408
		$fileField->getValidator()->setAllowedExtensions(['sspak']);
409
		$fileField->getValidator()->setAllowedMaxFileSize(['*' => $maxSize]);
410
411
		$form = Form::create(
412
			$this,
413
			'UploadSnapshotForm',
414
			FieldList::create(
415
				$fileField,
416
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
417
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
418
					->setEmptyString('Select an environment')
419
			),
420
			FieldList::create(
421
				FormAction::create('doUploadSnapshot', 'Upload File')
422
					->addExtraClass('btn')
423
			),
424
			RequiredFields::create('ArchiveFile')
425
		);
426
427
		$form->disableSecurityToken();
428
		$form->addExtraClass('fields-wide');
429
		// Tweak the action so it plays well with our fake URL structure.
430
		$form->setFormAction($project->Link() . '/UploadSnapshotForm');
431
432
		return $form;
433
	}
434
435
	/**
436
	 * @param array $data
437
	 * @param Form $form
438
	 *
439
	 * @return bool|HTMLText|SS_HTTPResponse
440
	 */
441
	public function doUploadSnapshot($data, \Form $form) {
442
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
443
444
		// Performs canView permission check by limiting visible projects
445
		$project = $this->getCurrentProject();
446
		if (!$project) {
447
			return $this->project404Response();
448
		}
449
450
		$validEnvs = $project->DNEnvironmentList()
451
			->filterByCallback(function ($item) {
452
				return $item->canUploadArchive();
453
			});
454
455
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
456
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
457
		if (!$environment) {
458
			throw new LogicException('Invalid environment');
459
		}
460
461
		$this->validateSnapshotMode($data['Mode']);
462
463
		$dataArchive = DNDataArchive::create([
464
			'AuthorID' => Member::currentUserID(),
465
			'EnvironmentID' => $data['EnvironmentID'],
466
			'IsManualUpload' => true,
467
		]);
468
		// needs an ID and transfer to determine upload path
469
		$dataArchive->write();
470
		$dataTransfer = DNDataTransfer::create([
471
			'AuthorID' => Member::currentUserID(),
472
			'Mode' => $data['Mode'],
473
			'Origin' => 'ManualUpload',
474
			'EnvironmentID' => $data['EnvironmentID']
475
		]);
476
		$dataTransfer->write();
477
		$dataArchive->DataTransfers()->add($dataTransfer);
478
		$form->saveInto($dataArchive);
479
		$dataArchive->write();
480
		$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
481
482 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...
483
			$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
484
			$process->setTimeout(120);
485
			$process->run();
486
			$dataTransfer->delete();
487
			$dataArchive->delete();
488
		};
489
490
		// extract the sspak contents so we can inspect them
491
		try {
492
			$dataArchive->extractArchive($workingDir);
493
		} catch (Exception $e) {
494
			$cleanupFn();
495
			$form->sessionMessage(
496
				'There was a problem trying to open your snapshot for processing. Please try uploading again',
497
				'bad'
498
			);
499
			return $this->redirectBack();
500
		}
501
502
		// validate that the sspak contents match the declared contents
503
		$result = $dataArchive->validateArchiveContents();
504
		if (!$result->valid()) {
505
			$cleanupFn();
506
			$form->sessionMessage($result->message(), 'bad');
507
			return $this->redirectBack();
508
		}
509
510
		// fix file permissions of extracted sspak files then re-build the sspak
511
		try {
512
			$dataArchive->fixArchivePermissions($workingDir);
513
			$dataArchive->setArchiveFromFiles($workingDir);
514
		} catch (Exception $e) {
515
			$cleanupFn();
516
			$form->sessionMessage(
517
				'There was a problem processing your snapshot. Please try uploading again',
518
				'bad'
519
			);
520
			return $this->redirectBack();
521
		}
522
523
		// cleanup any extracted sspak contents lying around
524
		$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
525
		$process->setTimeout(120);
526
		$process->run();
527
528
		return $this->customise([
529
			'Project' => $project,
530
			'CurrentProject' => $project,
531
			'SnapshotsSection' => 1,
532
			'DataArchive' => $dataArchive,
533
			'DataTransferRestoreForm' => $this->getDataTransferRestoreForm($this->request, $dataArchive),
534
			'BackURL' => $project->Link('snapshots')
535
		])->renderWith(['DNRoot_uploadsnapshot', 'DNRoot']);
536
	}
537
538
	/**
539
	 * @param \SS_HTTPRequest $request
540
	 * @return Form
541
	 */
542
	public function getPostSnapshotForm(\SS_HTTPRequest $request) {
543
		// Performs canView permission check by limiting visible projects
544
		$project = $this->getCurrentProject();
545
		if (!$project) {
546
			return $this->project404Response();
547
		}
548
549
		if (!$project->canUploadArchive()) {
550
			return new SS_HTTPResponse("Not allowed to upload", 401);
551
		}
552
553
		// Framing an environment as a "group of people with download access"
554
		// makes more sense to the user here, while still allowing us to enforce
555
		// environment specific restrictions on downloading the file later on.
556
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
557
			return $item->canUploadArchive();
558
		});
559
		$envsMap = [];
560
		foreach ($envs as $env) {
561
			$envsMap[$env->ID] = $env->Name;
562
		}
563
564
		$form = Form::create(
565
			$this,
566
			'PostSnapshotForm',
567
			FieldList::create(
568
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
569
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
570
					->setEmptyString('Select an environment')
571
			),
572
			FieldList::create(
573
				FormAction::create('doPostSnapshot', 'Submit request')
574
					->addExtraClass('btn')
575
			),
576
			RequiredFields::create('File')
577
		);
578
579
		$form->disableSecurityToken();
580
		$form->addExtraClass('fields-wide');
581
		// Tweak the action so it plays well with our fake URL structure.
582
		$form->setFormAction($project->Link() . '/PostSnapshotForm');
583
584
		return $form;
585
	}
586
587
	/**
588
	 * @param array $data
589
	 * @param Form $form
590
	 *
591
	 * @return SS_HTTPResponse
592
	 */
593
	public function doPostSnapshot($data, $form) {
594
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
595
596
		$project = $this->getCurrentProject();
597
		if (!$project) {
598
			return $this->project404Response();
599
		}
600
601
		$validEnvs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
602
			return $item->canUploadArchive();
603
		});
604
605
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
606
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
607
		if (!$environment) {
608
			throw new LogicException('Invalid environment');
609
		}
610
611
		$dataArchive = DNDataArchive::create([
612
			'UploadToken' => DNDataArchive::generate_upload_token(),
613
		]);
614
		$form->saveInto($dataArchive);
615
		$dataArchive->write();
616
617
		return $this->redirect(Controller::join_links(
618
			$project->Link(),
619
			'postsnapshotsuccess',
620
			$dataArchive->ID
621
		));
622
	}
623
624
	/**
625
	 * Action
626
	 *
627
	 * @param \SS_HTTPRequest $request
628
	 * @return SS_HTTPResponse - HTML
629
	 */
630
	public function snapshotslog(\SS_HTTPRequest $request) {
631
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
632
		return $this->getCustomisedViewSection('SnapshotsSection', 'Snapshots log');
633
	}
634
635
	/**
636
	 * @param \SS_HTTPRequest $request
637
	 * @return SS_HTTPResponse|string
638
	 * @throws SS_HTTPResponse_Exception
639
	 */
640
	public function postsnapshotsuccess(\SS_HTTPRequest $request) {
641
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
642
643
		// Performs canView permission check by limiting visible projects
644
		$project = $this->getCurrentProject();
645
		if (!$project) {
646
			return $this->project404Response();
647
		}
648
649
		if (!$project->canUploadArchive()) {
650
			return new SS_HTTPResponse("Not allowed to upload", 401);
651
		}
652
653
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
654
		if (!$dataArchive) {
655
			return new SS_HTTPResponse("Archive not found.", 404);
656
		}
657
658
		if (!$dataArchive->canRestore()) {
659
			throw new SS_HTTPResponse_Exception('Not allowed to restore archive', 403);
660
		}
661
662
		return $this->render([
663
			'Title' => 'How to send us your Data Snapshot by post',
664
			'DataArchive' => $dataArchive,
665
			'Address' => Config::inst()->get('Deploynaut', 'snapshot_post_address'),
666
			'BackURL' => $project->Link(),
667
		]);
668
	}
669
670
	/**
671
	 * @param \SS_HTTPRequest $request
672
	 * @return \SS_HTTPResponse
673
	 */
674
	public function project(\SS_HTTPRequest $request) {
675
		$this->setCurrentActionType(self::PROJECT_OVERVIEW);
676
		return $this->getCustomisedViewSection('ProjectOverview', '', ['IsAdmin' => Permission::check('ADMIN')]);
677
	}
678
679
	/**
680
	 * This action will star / unstar a project for the current member
681
	 *
682
	 * @param \SS_HTTPRequest $request
683
	 *
684
	 * @return SS_HTTPResponse
685
	 */
686
	public function toggleprojectstar(\SS_HTTPRequest $request) {
687
		$project = $this->getCurrentProject();
688
		if (!$project) {
689
			return $this->project404Response();
690
		}
691
692
		$member = Member::currentUser();
693
		if ($member === null) {
694
			return $this->project404Response();
695
		}
696
		$favProject = $member->StarredProjects()
697
			->filter('DNProjectID', $project->ID)
698
			->first();
699
700
		if ($favProject) {
701
			$member->StarredProjects()->remove($favProject);
702
		} else {
703
			$member->StarredProjects()->add($project);
704
		}
705
706
		if (!$request->isAjax()) {
707
			return $this->redirectBack();
708
		}
709
	}
710
711
	/**
712
	 * @param \SS_HTTPRequest $request
713
	 * @return \SS_HTTPResponse
714
	 */
715
	public function branch(\SS_HTTPRequest $request) {
716
		$project = $this->getCurrentProject();
717
		if (!$project) {
718
			return $this->project404Response();
719
		}
720
721
		$branchName = $request->getVar('name');
722
		$branch = $project->DNBranchList()->byName($branchName);
723
		if (!$branch) {
724
			return new SS_HTTPResponse("Branch '" . Convert::raw2xml($branchName) . "' not found.", 404);
725
		}
726
727
		return $this->render([
728
			'CurrentBranch' => $branch,
729
		]);
730
	}
731
732
	/**
733
	 * @param \SS_HTTPRequest $request
734
	 * @return \SS_HTTPResponse
735
	 */
736
	public function environment(\SS_HTTPRequest $request) {
737
		// Performs canView permission check by limiting visible projects
738
		$project = $this->getCurrentProject();
739
		if (!$project) {
740
			return $this->project404Response();
741
		}
742
743
		// Performs canView permission check by limiting visible projects
744
		$env = $this->getCurrentEnvironment($project);
745
		if (!$env) {
746
			return $this->environment404Response();
747
		}
748
749
		return $this->render([
750
			'DNEnvironmentList' => $this->getCurrentProject()->DNEnvironmentList(),
751
			'Redeploy' => (bool) $request->getVar('redeploy')
752
		]);
753
	}
754
755
	/**
756
	 * Shows the creation log.
757
	 *
758
	 * @param \SS_HTTPRequest $request
759
	 * @return string
760
	 */
761
	public function createenv(\SS_HTTPRequest $request) {
762
		$params = $request->params();
763
		if ($params['Identifier']) {
764
			$record = DNCreateEnvironment::get()->byId($params['Identifier']);
765
766
			if (!$record || !$record->ID) {
767
				throw new SS_HTTPResponse_Exception('Create environment not found', 404);
768
			}
769
			if (!$record->canView()) {
770
				return Security::permissionFailure();
771
			}
772
773
			$project = $this->getCurrentProject();
774
			if (!$project) {
775
				return $this->project404Response();
776
			}
777
778
			if ($project->Name != $params['Project']) {
779
				throw new LogicException("Project in URL doesn't match this creation");
780
			}
781
782
			return $this->render([
783
				'CreateEnvironment' => $record,
784
			]);
785
		}
786
		return $this->render(['CurrentTitle' => 'Create an environment']);
787
	}
788
789
	public function createenvlog(\SS_HTTPRequest $request) {
790
		$params = $request->params();
791
		$env = DNCreateEnvironment::get()->byId($params['Identifier']);
792
793
		if (!$env || !$env->ID) {
794
			throw new SS_HTTPResponse_Exception('Log not found', 404);
795
		}
796
		if (!$env->canView()) {
797
			return Security::permissionFailure();
798
		}
799
800
		$project = $env->Project();
801
802
		if ($project->Name != $params['Project']) {
803
			throw new LogicException("Project in URL doesn't match this deploy");
804
		}
805
806
		$log = $env->log();
807
		if ($log->exists()) {
808
			$content = $log->content();
809
		} else {
810
			$content = 'Waiting for action to start';
811
		}
812
813
		return $this->sendResponse($env->ResqueStatus(), $content);
814
	}
815
816
	/**
817
	 * @param \SS_HTTPRequest $request
818
	 * @return Form
819
	 */
820
	public function getCreateEnvironmentForm(\SS_HTTPRequest $request = null) {
821
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
822
823
		$project = $this->getCurrentProject();
824
		if (!$project) {
825
			return $this->project404Response();
826
		}
827
828
		$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...
829
		if (!$envType || !class_exists($envType)) {
830
			return null;
831
		}
832
833
		$backend = Injector::inst()->get($envType);
834
		if (!($backend instanceof EnvironmentCreateBackend)) {
835
			// Only allow this for supported backends.
836
			return null;
837
		}
838
839
		$fields = $backend->getCreateEnvironmentFields($project);
840
		if (!$fields) {
841
			return null;
842
		}
843
844
		if (!$project->canCreateEnvironments()) {
845
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
846
		}
847
848
		$form = Form::create(
849
			$this,
850
			'CreateEnvironmentForm',
851
			$fields,
852
			FieldList::create(
853
				FormAction::create('doCreateEnvironment', 'Create')
854
					->addExtraClass('btn')
855
			),
856
			$backend->getCreateEnvironmentValidator()
857
		);
858
859
		// Tweak the action so it plays well with our fake URL structure.
860
		$form->setFormAction($project->Link() . '/CreateEnvironmentForm');
861
862
		return $form;
863
	}
864
865
	/**
866
	 * @param array $data
867
	 * @param Form $form
868
	 *
869
	 * @return bool|HTMLText|SS_HTTPResponse
870
	 */
871
	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...
872
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
873
874
		$project = $this->getCurrentProject();
875
		if (!$project) {
876
			return $this->project404Response();
877
		}
878
879
		if (!$project->canCreateEnvironments()) {
880
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
881
		}
882
883
		// Set the environment type so we know what we're creating.
884
		$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...
885
886
		$job = DNCreateEnvironment::create();
887
888
		$job->Data = serialize($data);
889
		$job->ProjectID = $project->ID;
890
		$job->write();
891
		$job->start();
892
893
		return $this->redirect($project->Link('createenv') . '/' . $job->ID);
894
	}
895
896
	/**
897
	 * Get the DNData object.
898
	 *
899
	 * @return DNData
900
	 */
901
	public function DNData() {
902
		return DNData::inst();
903
	}
904
905
	/**
906
	 * Provide a list of all projects.
907
	 *
908
	 * @return SS_List
909
	 */
910
	public function DNProjectList() {
911
		$memberId = Member::currentUserID();
912
		if (!$memberId) {
913
			return new ArrayList();
914
		}
915
916
		if (Permission::check('ADMIN')) {
917
			return DNProject::get();
918
		}
919
920
		$projects = Member::get()->filter('ID', $memberId)
921
			->relation('Groups')
922
			->relation('Projects');
923
924
		$this->extend('updateDNProjectList', $projects);
925
		return $projects;
926
	}
927
928
	/**
929
	 * @return ArrayList
930
	 */
931
	public function getPlatformSpecificStrings() {
932
		$strings = $this->config()->platform_specific_strings;
933
		if ($strings) {
934
			return new ArrayList($strings);
935
		}
936
	}
937
938
	/**
939
	 * Provide a list of all starred projects for the currently logged in member
940
	 *
941
	 * @return SS_List
942
	 */
943
	public function getStarredProjects() {
944
		$member = Member::currentUser();
945
		if ($member === null) {
946
			return new ArrayList();
947
		}
948
949
		$favProjects = $member->StarredProjects();
950
951
		$list = new ArrayList();
952
		foreach ($favProjects as $project) {
953
			if ($project->canView($member)) {
954
				$list->add($project);
955
			}
956
		}
957
		return $list;
958
	}
959
960
	/**
961
	 * Returns top level navigation of projects.
962
	 *
963
	 * @param int $limit
964
	 *
965
	 * @return ArrayList
966
	 */
967
	public function Navigation($limit = 5) {
968
		$navigation = new ArrayList();
969
970
		$currentProject = $this->getCurrentProject();
971
		$currentEnvironment = $this->getCurrentEnvironment();
972
		$actionType = $this->getCurrentActionType();
973
974
		$projects = $this->getStarredProjects();
975
		if ($projects->count() < 1) {
976
			$projects = $this->DNProjectList();
977
		} else {
978
			$limit = -1;
979
		}
980
981
		if ($projects->count() > 0) {
982
			$activeProject = false;
983
984
			if ($limit > 0) {
985
				$limitedProjects = $projects->limit($limit);
986
			} else {
987
				$limitedProjects = $projects;
988
			}
989
990
			foreach ($limitedProjects as $project) {
991
				$isActive = $currentProject && $currentProject->ID == $project->ID;
992
				if ($isActive) {
993
					$activeProject = true;
994
				}
995
996
				$isCurrentEnvironment = false;
997
				if ($project && $currentEnvironment) {
998
					$isCurrentEnvironment = (bool) $project->DNEnvironmentList()->find('ID', $currentEnvironment->ID);
999
				}
1000
1001
				$navigation->push([
1002
					'Project' => $project,
1003
					'IsCurrentEnvironment' => $isCurrentEnvironment,
1004
					'IsActive' => $currentProject && $currentProject->ID == $project->ID,
1005
					'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...
1006
				]);
1007
			}
1008
1009
			// Ensure the current project is in the list
1010
			if (!$activeProject && $currentProject) {
1011
				$navigation->unshift([
1012
					'Project' => $currentProject,
1013
					'IsActive' => true,
1014
					'IsCurrentEnvironment' => $currentEnvironment,
1015
					'IsOverview' => $actionType == self::PROJECT_OVERVIEW && !$currentEnvironment
1016
				]);
1017
				if ($limit > 0 && $navigation->count() > $limit) {
1018
					$navigation->pop();
1019
				}
1020
			}
1021
		}
1022
1023
		return $navigation;
1024
	}
1025
1026
	/**
1027
	 * Construct the deployment form
1028
	 *
1029
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1030
	 *
1031
	 * @return Form
1032
	 */
1033
	public function getDeployForm($request = null) {
1034
1035
		// Performs canView permission check by limiting visible projects
1036
		$project = $this->getCurrentProject();
1037
		if (!$project) {
1038
			return $this->project404Response();
1039
		}
1040
1041
		// Performs canView permission check by limiting visible projects
1042
		$environment = $this->getCurrentEnvironment($project);
1043
		if (!$environment) {
1044
			return $this->environment404Response();
1045
		}
1046
1047
		if (!$environment->canDeploy()) {
1048
			return new SS_HTTPResponse("Not allowed to deploy", 401);
1049
		}
1050
1051
		// Generate the form
1052
		$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...
1053
1054
		// If this is an ajax request we don't want to submit the form - we just want to retrieve the markup.
1055
		if (
1056
			$request &&
1057
			!$request->requestVar('action_showDeploySummary') &&
1058
			$this->getRequest()->isAjax() &&
1059
			$this->getRequest()->isGET()
1060
		) {
1061
			// We can just use the URL we're accessing
1062
			$form->setFormAction($this->getRequest()->getURL());
1063
1064
			$body = json_encode(['Content' => $form->forAjaxTemplate()->forTemplate()]);
1065
			$this->getResponse()->addHeader('Content-Type', 'application/json');
1066
			$this->getResponse()->setBody($body);
1067
			return $body;
1068
		}
1069
1070
		$form->setFormAction($this->getRequest()->getURL() . '/DeployForm');
1071
		return $form;
1072
	}
1073
1074
	/**
1075
	 * @deprecated 2.0.0 - moved to GitDispatcher
1076
	 *
1077
	 * @param \SS_HTTPRequest $request
1078
	 *
1079
	 * @return SS_HTTPResponse|string
1080
	 */
1081
	public function gitRevisions(\SS_HTTPRequest $request) {
1082
1083
		// Performs canView permission check by limiting visible projects
1084
		$project = $this->getCurrentProject();
1085
		if (!$project) {
1086
			return $this->project404Response();
1087
		}
1088
1089
		// Performs canView permission check by limiting visible projects
1090
		$env = $this->getCurrentEnvironment($project);
1091
		if (!$env) {
1092
			return $this->environment404Response();
1093
		}
1094
1095
		$options = [];
1096
		foreach ($env->getSupportedOptions() as $option) {
1097
			$options[] = [
1098
				'name' => $option->getName(),
1099
				'title' => $option->getTitle(),
1100
				'defaultValue' => $option->getDefaultValue()
1101
			];
1102
		}
1103
1104
		$tabs = [];
1105
		$id = 0;
1106
		$data = [
1107
			'id' => ++$id,
1108
			'name' => 'Deploy the latest version of a branch',
1109
			'field_type' => 'dropdown',
1110
			'field_label' => 'Choose a branch',
1111
			'field_id' => 'branch',
1112
			'field_data' => [],
1113
			'options' => $options
1114
		];
1115 View Code Duplication
		foreach ($project->DNBranchList() as $branch) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1116
			$sha = $branch->SHA();
1117
			$name = $branch->Name();
1118
			$branchValue = sprintf("%s (%s, %s old)",
1119
				$name,
1120
				substr($sha, 0, 8),
1121
				$branch->LastUpdated()->TimeDiff()
1122
			);
1123
			$data['field_data'][] = [
1124
				'id' => $sha,
1125
				'text' => $branchValue,
1126
				'branch_name' => $name // the raw branch name, not including the time etc
1127
			];
1128
		}
1129
		$tabs[] = $data;
1130
1131
		$data = [
1132
			'id' => ++$id,
1133
			'name' => 'Deploy a tagged release',
1134
			'field_type' => 'dropdown',
1135
			'field_label' => 'Choose a tag',
1136
			'field_id' => 'tag',
1137
			'field_data' => [],
1138
			'options' => $options
1139
		];
1140
1141 View Code Duplication
		foreach ($project->DNTagList()->setLimit(null) as $tag) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
1500
			$fields = FieldList::create(
1501
				HiddenField::create('Direction', null, 'get'),
1502
				$envsField,
1503
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map()),
1504
				CheckboxField::create('IncludeResampled', 'Include Resampled Images Directory? (e.g. for total content migration)')
1505
			);
1506
		} else {
1507
			$fields = FieldList::create(
1508
				HiddenField::create('Direction', null, 'get'),
1509
				$envsField,
1510
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map())
1511
			);
1512
		}
1513
1514
		$form = Form::create(
1515
			$this,
1516
			'DataTransferForm',
1517
			$fields,
1518
			FieldList::create($formAction)
1519
		);
1520
		$form->setFormAction($this->getRequest()->getURL() . '/DataTransferForm');
1521
1522
		return $form;
1523
	}
1524
1525
	/**
1526
	 * @param array $data
1527
	 * @param Form $form
1528
	 *
1529
	 * @return SS_HTTPResponse
1530
	 * @throws SS_HTTPResponse_Exception
1531
	 */
1532
	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...
1533
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1534
1535
		// Performs canView permission check by limiting visible projects
1536
		$project = $this->getCurrentProject();
1537
		if (!$project) {
1538
			return $this->project404Response();
1539
		}
1540
1541
		$dataArchive = null;
1542
1543
		// Validate direction.
1544
		if ($data['Direction'] == 'get') {
1545
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1546
				->filterByCallback(function ($item) {
1547
					return $item->canBackup();
1548
				});
1549
		} else if ($data['Direction'] == 'push') {
1550
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1551
				->filterByCallback(function ($item) {
1552
					return $item->canRestore();
1553
				});
1554
		} else {
1555
			throw new LogicException('Invalid direction');
1556
		}
1557
1558
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1559
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1560
		if (!$environment) {
1561
			throw new LogicException('Invalid environment');
1562
		}
1563
1564
		$this->validateSnapshotMode($data['Mode']);
1565
1566
		// Only 'push' direction is allowed an association with an existing archive.
1567
		if (
1568
			$data['Direction'] == 'push'
1569
			&& isset($data['DataArchiveID'])
1570
			&& is_numeric($data['DataArchiveID'])
1571
		) {
1572
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1573
			if (!$dataArchive) {
1574
				throw new LogicException('Invalid data archive');
1575
			}
1576
1577
			if (!$dataArchive->canDownload()) {
1578
				throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1579
			}
1580
		}
1581
1582
		$transfer = DNDataTransfer::create();
1583
		$transfer->EnvironmentID = $environment->ID;
1584
		$transfer->Direction = $data['Direction'];
1585
		$transfer->Mode = $data['Mode'];
1586
		$transfer->DataArchiveID = $dataArchive ? $dataArchive->ID : null;
1587
		$transfer->IncludeResampled = $data['IncludeResampled'];
0 ignored issues
show
Documentation introduced by
The property IncludeResampled does not exist on object<DNDataTransfer>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1588
		if ($data['Direction'] == 'push') {
1589
			$transfer->setBackupBeforePush(!empty($data['BackupBeforePush']));
1590
		}
1591
		$transfer->write();
1592
		$transfer->start();
1593
1594
		return $this->redirect($transfer->Link());
1595
	}
1596
1597
	/**
1598
	 * View into the log for a {@link DNDataTransfer}.
1599
	 *
1600
	 * @param \SS_HTTPRequest $request
1601
	 *
1602
	 * @return SS_HTTPResponse|string
1603
	 * @throws SS_HTTPResponse_Exception
1604
	 */
1605
	public function transfer(\SS_HTTPRequest $request) {
1606
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1607
1608
		$params = $request->params();
1609
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1610
1611
		if (!$transfer || !$transfer->ID) {
1612
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1613
		}
1614
		if (!$transfer->canView()) {
1615
			return Security::permissionFailure();
1616
		}
1617
1618
		$environment = $transfer->Environment();
1619
		$project = $environment->Project();
1620
1621
		if ($project->Name != $params['Project']) {
1622
			throw new LogicException("Project in URL doesn't match this deploy");
1623
		}
1624
1625
		return $this->render([
1626
			'CurrentTransfer' => $transfer,
1627
			'SnapshotsSection' => 1,
1628
		]);
1629
	}
1630
1631
	/**
1632
	 * Action - Get the latest deploy log
1633
	 *
1634
	 * @param \SS_HTTPRequest $request
1635
	 *
1636
	 * @return string
1637
	 * @throws SS_HTTPResponse_Exception
1638
	 */
1639
	public function transferlog(\SS_HTTPRequest $request) {
1640
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1641
1642
		$params = $request->params();
1643
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1644
1645
		if (!$transfer || !$transfer->ID) {
1646
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1647
		}
1648
		if (!$transfer->canView()) {
1649
			return Security::permissionFailure();
1650
		}
1651
1652
		$environment = $transfer->Environment();
1653
		$project = $environment->Project();
1654
1655
		if ($project->Name != $params['Project']) {
1656
			throw new LogicException("Project in URL doesn't match this deploy");
1657
		}
1658
1659
		$log = $transfer->log();
1660
		if ($log->exists()) {
1661
			$content = $log->content();
1662
		} else {
1663
			$content = 'Waiting for action to start';
1664
		}
1665
1666
		return $this->sendResponse($transfer->ResqueStatus(), $content);
1667
	}
1668
1669
	/**
1670
	 * Note: Submits to the same action as {@link getDataTransferForm()},
1671
	 * but with a Direction=push and an archive reference.
1672
	 *
1673
	 * @param \SS_HTTPRequest $request
1674
	 * @param \DNDataArchive|null $dataArchive Only set when method is called manually in {@link restore()},
1675
	 *                            otherwise the state is inferred from the request data.
1676
	 * @return Form
1677
	 */
1678
	public function getDataTransferRestoreForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1679
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1680
1681
		// Performs canView permission check by limiting visible projects
1682
		$project = $this->getCurrentProject();
1683
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
1684
			return $item->canRestore();
1685
		});
1686
1687
		if (!$envs) {
1688
			return $this->environment404Response();
1689
		}
1690
1691
		$modesMap = [];
1692
		if (in_array($dataArchive->Mode, ['all'])) {
1693
			$modesMap['all'] = 'Database and Assets';
1694
		};
1695
		if (in_array($dataArchive->Mode, ['all', 'db'])) {
1696
			$modesMap['db'] = 'Database only';
1697
		};
1698
		if (in_array($dataArchive->Mode, ['all', 'assets'])) {
1699
			$modesMap['assets'] = 'Assets only';
1700
		};
1701
1702
		$alertMessage = '<div class="alert alert-warning"><strong>Warning:</strong> '
1703
			. 'This restore will overwrite the data on the chosen environment below</div>';
1704
1705
1706
		$items = [];
1707
		$disabledEnvironments = [];
1708 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...
1709
			$items[$env->ID] = $env->Title;
1710
			if ($env->CurrentBuild() === false) {
1711
				$items[$env->ID] = sprintf("%s - requires initial deployment", $env->Title);
1712
				$disabledEnvironments[] = $env->ID;
1713
			}
1714
		}
1715
1716
		$envsField = DropdownField::create('EnvironmentID', 'Environment', $items)
1717
			->setEmptyString('Select an environment');
1718
		$envsField->setDisabledItems($disabledEnvironments);
1719
		$formAction = FormAction::create('doDataTransfer', 'Restore Data')->addExtraClass('btn');
1720
1721
		if (count($disabledEnvironments) == $envs->count()) {
1722
			$formAction->setDisabled(true);
1723
		}
1724
1725
		$form = Form::create(
1726
			$this,
1727
			'DataTransferRestoreForm',
1728
			FieldList::create(
1729
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1730
				HiddenField::create('Direction', null, 'push'),
1731
				LiteralField::create('Warning', $alertMessage),
1732
				$envsField,
1733
				DropdownField::create('Mode', 'Transfer', $modesMap),
1734
				CheckboxField::create('BackupBeforePush', 'Backup existing data', '1')
1735
			),
1736
			FieldList::create($formAction)
1737
		);
1738
		$form->setFormAction($project->Link() . '/DataTransferRestoreForm');
1739
1740
		return $form;
1741
	}
1742
1743
	/**
1744
	 * View a form to restore a specific {@link DataArchive}.
1745
	 * Permission checks are handled in {@link DataArchives()}.
1746
	 * Submissions are handled through {@link doDataTransfer()}, same as backup operations.
1747
	 *
1748
	 * @param \SS_HTTPRequest $request
1749
	 *
1750
	 * @return HTMLText
1751
	 * @throws SS_HTTPResponse_Exception
1752
	 */
1753 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...
1754
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1755
1756
		/** @var DNDataArchive $dataArchive */
1757
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1758
1759
		if (!$dataArchive) {
1760
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1761
		}
1762
1763
		// We check for canDownload because that implies access to the data.
1764
		// canRestore is later checked on the actual restore action per environment.
1765
		if (!$dataArchive->canDownload()) {
1766
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1767
		}
1768
1769
		$form = $this->getDataTransferRestoreForm($this->request, $dataArchive);
1770
1771
		// View currently only available via ajax
1772
		return $form->forTemplate();
1773
	}
1774
1775
	/**
1776
	 * View a form to delete a specific {@link DataArchive}.
1777
	 * Permission checks are handled in {@link DataArchives()}.
1778
	 * Submissions are handled through {@link doDelete()}.
1779
	 *
1780
	 * @param \SS_HTTPRequest $request
1781
	 *
1782
	 * @return HTMLText
1783
	 * @throws SS_HTTPResponse_Exception
1784
	 */
1785 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...
1786
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1787
1788
		/** @var DNDataArchive $dataArchive */
1789
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1790
1791
		if (!$dataArchive) {
1792
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1793
		}
1794
1795
		if (!$dataArchive->canDelete()) {
1796
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1797
		}
1798
1799
		$form = $this->getDeleteForm($this->request, $dataArchive);
1800
1801
		// View currently only available via ajax
1802
		return $form->forTemplate();
1803
	}
1804
1805
	/**
1806
	 * @param \SS_HTTPRequest $request
1807
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually, otherwise the state is inferred
1808
	 *        from the request data.
1809
	 * @return Form
1810
	 */
1811
	public function getDeleteForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1812
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1813
1814
		// Performs canView permission check by limiting visible projects
1815
		$project = $this->getCurrentProject();
1816
		if (!$project) {
1817
			return $this->project404Response();
1818
		}
1819
1820
		$snapshotDeleteWarning = '<div class="alert alert-warning">'
1821
			. 'Are you sure you want to permanently delete this snapshot from this archive area?'
1822
			. '</div>';
1823
1824
		$form = Form::create(
1825
			$this,
1826
			'DeleteForm',
1827
			FieldList::create(
1828
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1829
				LiteralField::create('Warning', $snapshotDeleteWarning)
1830
			),
1831
			FieldList::create(
1832
				FormAction::create('doDelete', 'Delete')
1833
					->addExtraClass('btn')
1834
			)
1835
		);
1836
		$form->setFormAction($project->Link() . '/DeleteForm');
1837
1838
		return $form;
1839
	}
1840
1841
	/**
1842
	 * @param array $data
1843
	 * @param Form $form
1844
	 *
1845
	 * @return bool|SS_HTTPResponse
1846
	 * @throws SS_HTTPResponse_Exception
1847
	 */
1848
	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...
1849
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1850
1851
		// Performs canView permission check by limiting visible projects
1852
		$project = $this->getCurrentProject();
1853
		if (!$project) {
1854
			return $this->project404Response();
1855
		}
1856
1857
		$dataArchive = null;
1858
1859
		if (
1860
			isset($data['DataArchiveID'])
1861
			&& is_numeric($data['DataArchiveID'])
1862
		) {
1863
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1864
		}
1865
1866
		if (!$dataArchive) {
1867
			throw new LogicException('Invalid data archive');
1868
		}
1869
1870
		if (!$dataArchive->canDelete()) {
1871
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1872
		}
1873
1874
		$dataArchive->delete();
1875
1876
		return $this->redirectBack();
1877
	}
1878
1879
	/**
1880
	 * View a form to move a specific {@link DataArchive}.
1881
	 *
1882
	 * @param \SS_HTTPRequest $request
1883
	 *
1884
	 * @return HTMLText
1885
	 * @throws SS_HTTPResponse_Exception
1886
	 */
1887 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...
1888
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1889
1890
		/** @var DNDataArchive $dataArchive */
1891
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1892
1893
		if (!$dataArchive) {
1894
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1895
		}
1896
1897
		// We check for canDownload because that implies access to the data.
1898
		if (!$dataArchive->canDownload()) {
1899
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1900
		}
1901
1902
		$form = $this->getMoveForm($this->request, $dataArchive);
1903
1904
		// View currently only available via ajax
1905
		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...
1906
	}
1907
1908
	/**
1909
	 * Build snapshot move form.
1910
	 *
1911
	 * @param \SS_HTTPRequest $request
1912
	 * @param DNDataArchive|null $dataArchive
1913
	 *
1914
	 * @return Form|SS_HTTPResponse
1915
	 */
1916
	public function getMoveForm(\SS_HTTPRequest $request, \DNDataArchive $dataArchive = null) {
1917
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1918
1919
		$envs = $dataArchive->validTargetEnvironments();
1920
		if (!$envs) {
1921
			return $this->environment404Response();
1922
		}
1923
1924
		$warningMessage = '<div class="alert alert-warning"><strong>Warning:</strong> This will make the snapshot '
1925
			. 'available to people with access to the target environment.<br>By pressing "Change ownership" you '
1926
			. 'confirm that you have considered data confidentiality regulations.</div>';
1927
1928
		$form = Form::create(
1929
			$this,
1930
			'MoveForm',
1931
			FieldList::create(
1932
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1933
				LiteralField::create('Warning', $warningMessage),
1934
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1935
					->setEmptyString('Select an environment')
1936
			),
1937
			FieldList::create(
1938
				FormAction::create('doMove', 'Change ownership')
1939
					->addExtraClass('btn')
1940
			)
1941
		);
1942
		$form->setFormAction($this->getCurrentProject()->Link() . '/MoveForm');
1943
1944
		return $form;
1945
	}
1946
1947
	/**
1948
	 * @param array $data
1949
	 * @param Form $form
1950
	 *
1951
	 * @return bool|SS_HTTPResponse
1952
	 * @throws SS_HTTPResponse_Exception
1953
	 * @throws ValidationException
1954
	 * @throws null
1955
	 */
1956
	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...
1957
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1958
1959
		// Performs canView permission check by limiting visible projects
1960
		$project = $this->getCurrentProject();
1961
		if (!$project) {
1962
			return $this->project404Response();
1963
		}
1964
1965
		/** @var DNDataArchive $dataArchive */
1966
		$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1967
		if (!$dataArchive) {
1968
			throw new LogicException('Invalid data archive');
1969
		}
1970
1971
		// We check for canDownload because that implies access to the data.
1972
		if (!$dataArchive->canDownload()) {
1973
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1974
		}
1975
1976
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1977
		$validEnvs = $dataArchive->validTargetEnvironments();
1978
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1979
		if (!$environment) {
1980
			throw new LogicException('Invalid environment');
1981
		}
1982
1983
		$dataArchive->EnvironmentID = $environment->ID;
1984
		$dataArchive->write();
1985
1986
		return $this->redirectBack();
1987
	}
1988
1989
	/**
1990
	 * Returns an error message if redis is unavailable
1991
	 *
1992
	 * @return string
1993
	 */
1994
	public static function RedisUnavailable() {
1995
		try {
1996
			Resque::queues();
1997
		} catch (Exception $e) {
1998
			return $e->getMessage();
1999
		}
2000
		return '';
2001
	}
2002
2003
	/**
2004
	 * Returns the number of connected Redis workers
2005
	 *
2006
	 * @return int
2007
	 */
2008
	public static function RedisWorkersCount() {
2009
		return count(Resque_Worker::all());
2010
	}
2011
2012
	/**
2013
	 * @return array
2014
	 */
2015
	public function providePermissions() {
2016
		return [
2017
			self::DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS => [
2018
				'name' => "Access to advanced deploy options",
2019
				'category' => "Deploynaut",
2020
			],
2021
2022
			// Permissions that are intended to be added to the roles.
2023
			self::ALLOW_PROD_DEPLOYMENT => [
2024
				'name' => "Ability to deploy to production environments",
2025
				'category' => "Deploynaut",
2026
			],
2027
			self::ALLOW_NON_PROD_DEPLOYMENT => [
2028
				'name' => "Ability to deploy to non-production environments",
2029
				'category' => "Deploynaut",
2030
			],
2031
			self::ALLOW_PROD_SNAPSHOT => [
2032
				'name' => "Ability to make production snapshots",
2033
				'category' => "Deploynaut",
2034
			],
2035
			self::ALLOW_NON_PROD_SNAPSHOT => [
2036
				'name' => "Ability to make non-production snapshots",
2037
				'category' => "Deploynaut",
2038
			],
2039
			self::ALLOW_CREATE_ENVIRONMENT => [
2040
				'name' => "Ability to create environments",
2041
				'category' => "Deploynaut",
2042
			],
2043
		];
2044
	}
2045
2046
	/**
2047
	 * @return DNProject|null
2048
	 */
2049
	public function getCurrentProject() {
2050
		$projectName = trim($this->getRequest()->param('Project'));
2051
		if (!$projectName) {
2052
			return null;
2053
		}
2054
		if (empty(self::$_project_cache[$projectName])) {
2055
			self::$_project_cache[$projectName] = $this->DNProjectList()->filter('Name', $projectName)->First();
2056
		}
2057
		return self::$_project_cache[$projectName];
2058
	}
2059
2060
	/**
2061
	 * @param \DNProject|null $project
2062
	 * @return \DNEnvironment|null
2063
	 */
2064
	public function getCurrentEnvironment(\DNProject $project = null) {
2065
		if ($this->getRequest()->param('Environment') === null) {
2066
			return null;
2067
		}
2068
		if ($project === null) {
2069
			$project = $this->getCurrentProject();
2070
		}
2071
		// project can still be null
2072
		if ($project === null) {
2073
			return null;
2074
		}
2075
		return $project->DNEnvironmentList()->filter('Name', $this->getRequest()->param('Environment'))->First();
2076
	}
2077
2078
	/**
2079
	 * This will return a const that indicates the class of action currently being performed
2080
	 *
2081
	 * Until DNRoot is de-godded, it does a bunch of different actions all in the same class.
2082
	 * So we just have each action handler calll setCurrentActionType to define what sort of
2083
	 * action it is.
2084
	 *
2085
	 * @return string - one of the consts from self::$action_types
2086
	 */
2087
	public function getCurrentActionType() {
2088
		return $this->actionType;
2089
	}
2090
2091
	/**
2092
	 * Sets the current action type
2093
	 *
2094
	 * @param string $actionType string - one of the consts from self::$action_types
2095
	 */
2096
	public function setCurrentActionType($actionType) {
2097
		$this->actionType = $actionType;
2098
	}
2099
2100
	/**
2101
	 * Helper method to allow templates to know whether they should show the 'Archive List' include or not.
2102
	 * The actual permissions are set on a per-environment level, so we need to find out if this $member can upload to
2103
	 * or download from *any* {@link DNEnvironment} that (s)he has access to.
2104
	 *
2105
	 * TODO To be replaced with a method that just returns the list of archives this {@link Member} has access to.
2106
	 *
2107
	 * @param Member|null $member The {@link Member} to check (or null to check the currently logged in Member)
2108
	 * @return boolean|null true if $member has access to upload or download to at least one {@link DNEnvironment}.
2109
	 */
2110
	public function CanViewArchives(\Member $member = null) {
2111
		if ($member === null) {
2112
			$member = Member::currentUser();
2113
		}
2114
2115
		if (Permission::checkMember($member, 'ADMIN')) {
2116
			return true;
2117
		}
2118
2119
		$allProjects = $this->DNProjectList();
2120
		if (!$allProjects) {
2121
			return false;
2122
		}
2123
2124
		foreach ($allProjects as $project) {
2125
			if ($project->Environments()) {
2126
				foreach ($project->Environments() as $environment) {
2127
					if (
2128
						$environment->canRestore($member) ||
2129
						$environment->canBackup($member) ||
2130
						$environment->canUploadArchive($member) ||
2131
						$environment->canDownloadArchive($member)
2132
					) {
2133
						// We can return early as we only need to know that we can access one environment
2134
						return true;
2135
					}
2136
				}
2137
			}
2138
		}
2139
	}
2140
2141
	/**
2142
	 * Returns a list of attempted environment creations.
2143
	 *
2144
	 * @return PaginatedList
2145
	 */
2146
	public function CreateEnvironmentList() {
2147
		$project = $this->getCurrentProject();
2148
		if ($project) {
2149
			$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...
2150
		} else {
2151
			$dataList = new ArrayList();
2152
		}
2153
2154
		$this->extend('updateCreateEnvironmentList', $dataList);
2155
		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...
2156
	}
2157
2158
	/**
2159
	 * Returns a list of all archive files that can be accessed by the currently logged-in {@link Member}
2160
	 *
2161
	 * @return PaginatedList
2162
	 */
2163
	public function CompleteDataArchives() {
2164
		$project = $this->getCurrentProject();
2165
		$archives = new ArrayList();
2166
2167
		$archiveList = $project->Environments()->relation("DataArchives");
2168
		if ($archiveList->count() > 0) {
2169
			foreach ($archiveList as $archive) {
2170
				if (!$archive->isPending()) {
2171
					$archives->push($archive);
2172
				}
2173
			}
2174
		}
2175
		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...
2176
	}
2177
2178
	/**
2179
	 * @return PaginatedList The list of "pending" data archives which are waiting for a file
2180
	 * to be delivered offline by post, and manually uploaded into the system.
2181
	 */
2182
	public function PendingDataArchives() {
2183
		$project = $this->getCurrentProject();
2184
		$archives = new ArrayList();
2185
		foreach ($project->DNEnvironmentList() as $env) {
2186
			foreach ($env->DataArchives() as $archive) {
2187
				if ($archive->isPending()) {
2188
					$archives->push($archive);
2189
				}
2190
			}
2191
		}
2192
		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...
2193
	}
2194
2195
	/**
2196
	 * @return PaginatedList
2197
	 */
2198
	public function DataTransferLogs() {
2199
		$environments = $this->getCurrentProject()->Environments()->column('ID');
2200
		$transfers = DNDataTransfer::get()
2201
			->filter('EnvironmentID', $environments)
2202
			->filterByCallback(
2203
				function ($record) {
2204
					return
2205
						$record->Environment()->canRestore() || // Ensure member can perform an action on the transfers env
2206
						$record->Environment()->canBackup() ||
2207
						$record->Environment()->canUploadArchive() ||
2208
						$record->Environment()->canDownloadArchive();
2209
				});
2210
2211
		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...
2212
	}
2213
2214
	/**
2215
	 * @deprecated 2.0.0 - moved to DeployDispatcher
2216
	 *
2217
	 * @return null|PaginatedList
2218
	 */
2219
	public function DeployHistory() {
2220
		if ($env = $this->getCurrentEnvironment()) {
2221
			$history = $env->DeployHistory();
2222
			if ($history->count() > 0) {
2223
				$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...
2224
				$pagination->setPageLength(4);
2225
				return $pagination;
2226
			}
2227
		}
2228
		return null;
2229
	}
2230
2231
	/**
2232
	 * @param string $status
2233
	 * @param string $content
2234
	 *
2235
	 * @return string
2236
	 */
2237
	public function sendResponse($status, $content) {
2238
		// strip excessive newlines
2239
		$content = preg_replace('/(?:(?:\r\n|\r|\n)\s*){2}/s', "\n", $content);
2240
2241
		$sendJSON = (strpos($this->getRequest()->getHeader('Accept'), 'application/json') !== false)
2242
			|| $this->getRequest()->getExtension() == 'json';
2243
2244
		if (!$sendJSON) {
2245
			$this->response->addHeader("Content-type", "text/plain");
2246
			return $content;
2247
		}
2248
		$this->response->addHeader("Content-type", "application/json");
2249
		return json_encode([
2250
			'status' => $status,
2251
			'content' => $content,
2252
		]);
2253
	}
2254
2255
	/**
2256
	 * Get items for the ambient menu that should be accessible from all pages.
2257
	 *
2258
	 * @return ArrayList
2259
	 */
2260
	public function AmbientMenu() {
2261
		$list = new ArrayList();
2262
2263
		if (Member::currentUserID()) {
2264
			$list->push(new ArrayData([
2265
				'Classes' => 'logout',
2266
				'FaIcon' => 'sign-out',
2267
				'Link' => 'Security/logout',
2268
				'Title' => 'Log out',
2269
				'IsCurrent' => false,
2270
				'IsSection' => false
2271
			]));
2272
		}
2273
2274
		$this->extend('updateAmbientMenu', $list);
2275
		return $list;
2276
	}
2277
2278
	/**
2279
	 * Checks whether the user can create a project.
2280
	 *
2281
	 * @return bool
2282
	 */
2283
	public function canCreateProjects($member = null) {
2284
		if (!$member) {
2285
			$member = Member::currentUser();
2286
		}
2287
		if (!$member) {
2288
			return false;
2289
		}
2290
2291
		return singleton('DNProject')->canCreate($member);
2292
	}
2293
2294
	protected function applyRedeploy(\SS_HTTPRequest $request, &$data) {
2295
		if (!$request->getVar('redeploy')) {
2296
			return;
2297
		}
2298
2299
		$project = $this->getCurrentProject();
2300
		if (!$project) {
2301
			return $this->project404Response();
2302
		}
2303
2304
		// Performs canView permission check by limiting visible projects
2305
		$env = $this->getCurrentEnvironment($project);
2306
		if (!$env) {
2307
			return $this->environment404Response();
2308
		}
2309
2310
		$current = $env->CurrentBuild();
2311
		if ($current && $current->exists()) {
2312
			$data['preselect_tab'] = 3;
2313
			$data['preselect_sha'] = $current->SHA;
2314
		} else {
2315
			$master = $project->DNBranchList()->byName('master');
2316
			if ($master) {
2317
				$data['preselect_tab'] = 1;
2318
				$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...
2319
			}
2320
		}
2321
	}
2322
2323
	/**
2324
	 * @return SS_HTTPResponse
2325
	 */
2326
	protected function project404Response() {
2327
		return new SS_HTTPResponse(
2328
			"Project '" . Convert::raw2xml($this->getRequest()->param('Project')) . "' not found.",
2329
			404
2330
		);
2331
	}
2332
2333
	/**
2334
	 * @return SS_HTTPResponse
2335
	 */
2336
	protected function environment404Response() {
2337
		$envName = Convert::raw2xml($this->getRequest()->param('Environment'));
2338
		return new SS_HTTPResponse("Environment '" . $envName . "' not found.", 404);
2339
	}
2340
2341
	/**
2342
	 * Validate the snapshot mode
2343
	 *
2344
	 * @param string $mode
2345
	 */
2346
	protected function validateSnapshotMode($mode) {
2347
		if (!in_array($mode, ['all', 'assets', 'db'])) {
2348
			throw new LogicException('Invalid mode');
2349
		}
2350
	}
2351
2352
	/**
2353
	 * @param string $sectionName
2354
	 * @param string $title
2355
	 *
2356
	 * @return SS_HTTPResponse
2357
	 */
2358
	protected function getCustomisedViewSection($sectionName, $title = '', $data = []) {
2359
		// Performs canView permission check by limiting visible projects
2360
		$project = $this->getCurrentProject();
2361
		if (!$project) {
2362
			return $this->project404Response();
2363
		}
2364
		$data[$sectionName] = 1;
2365
2366
		if ($this !== '') {
2367
			$data['Title'] = $title;
2368
		}
2369
2370
		return $this->render($data);
2371
	}
2372
2373
}
2374
2375