Completed
Push — master ( 0c8969...da5dbd )
by Stig
01:21
created

DNRoot::CanViewArchives()   C

Complexity

Conditions 11
Paths 14

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 17
nc 14
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * God controller for the deploynaut interface
5
 *
6
 * @package deploynaut
7
 * @subpackage control
8
 */
9
class DNRoot extends Controller implements PermissionProvider, TemplateGlobalProvider {
0 ignored issues
show
Coding Style introduced by
The property $_project_cache is not named in camelCase.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
294
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
295
296
		// Performs canView permission check by limiting visible projects
297
		$project = $this->getCurrentProject();
298
		if (!$project) {
299
			return $this->project404Response();
300
		}
301
302
		if (!$project->canBackup()) {
303
			return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
304
		}
305
306
		return $this->customise([
307
			'Title' => 'Create Data Snapshot',
308
			'SnapshotsSection' => 1,
309
			'DataTransferForm' => $this->getDataTransferForm($request)
310
		])->render();
311
	}
312
313
	/**
314
	 * Action
315
	 *
316
	 * @param \SS_HTTPRequest $request
317
	 * @return string - HTML
318
	 */
319 View Code Duplication
	public function uploadsnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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