Issues (524)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

code/control/DNRoot.php (44 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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...
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...
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...
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...
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...
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...
The property $logged_in_links is not named in camelCase.

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

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

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

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

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

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

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

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

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

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

Loading history...
155
		self::ACTION_DEPLOY,
156
		self::ACTION_SNAPSHOT,
157
		self::PROJECT_OVERVIEW
158
	];
159
160
	/**
161
	 * Include requirements that deploynaut needs, such as javascript.
162
	 */
163
	public static function include_requirements() {
164
165
		// JS should always go to the bottom, otherwise there's the risk that Requirements
166
		// puts them halfway through the page to the nearest <script> tag. We don't want that.
167
		Requirements::set_force_js_to_bottom(true);
168
169
		// todo these should be bundled into the same JS as the others in "static" below.
170
		// We've deliberately not used combined_files as it can mess with some of the JS used
171
		// here and cause sporadic errors.
172
		Requirements::javascript('deploynaut/javascript/jquery.js');
173
		Requirements::javascript('deploynaut/javascript/bootstrap.js');
174
		Requirements::javascript('deploynaut/javascript/q.js');
175
		Requirements::javascript('deploynaut/javascript/tablefilter.js');
176
		Requirements::javascript('deploynaut/javascript/deploynaut.js');
177
178
		Requirements::javascript('deploynaut/javascript/bootstrap.file-input.js');
179
		Requirements::javascript('deploynaut/thirdparty/select2/dist/js/select2.min.js');
180
		Requirements::javascript('deploynaut/javascript/selectize.js');
181
		Requirements::javascript('deploynaut/thirdparty/bootstrap-switch/dist/js/bootstrap-switch.min.js');
182
		Requirements::javascript('deploynaut/javascript/material.js');
183
184
		// Load the buildable dependencies only if not loaded centrally.
185
		if (!is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'static')) {
186
			if (\Director::isDev()) {
187
				\Requirements::javascript('deploynaut/static/bundle-debug.js');
188
			} else {
189
				\Requirements::javascript('deploynaut/static/bundle.js');
190
			}
191
		}
192
193
		// We need to include javascript here so that prerequisite js object(s) from
194
		// the deploynaut module have been loaded
195
		Requirements::javascript('static/platform.js');
196
197
		Requirements::css('deploynaut/static/style.css');
198
	}
199
200
	/**
201
	 * @return ArrayList
202
	 */
203
	public static function get_support_links() {
204
		$supportLinks = self::config()->support_links;
205
		if ($supportLinks) {
206
			return new ArrayList($supportLinks);
207
		}
208
	}
209
210
	/**
211
	 * @return ArrayList
212
	 */
213
	public static function get_logged_in_links() {
214
		$loggedInLinks = self::config()->logged_in_links;
215
		if ($loggedInLinks) {
216
			return new ArrayList($loggedInLinks);
217
		}
218
	}
219
220
	/**
221
	 * Return the platform title if configured, defaulting to "Deploynaut".
222
	 * @return string
223
	 */
224
	public static function platform_title() {
225
		if (defined('DEPLOYNAUT_PLATFORM_TITLE')) {
226
			return DEPLOYNAUT_PLATFORM_TITLE;
227
		}
228
		return 'Deploynaut';
229
	}
230
231
	/**
232
	 * @return array
233
	 */
234
	public static function get_template_global_variables() {
235
		return [
236
			'PlatformTitle' => 'platform_title',
237
			'RedisUnavailable' => 'RedisUnavailable',
238
			'RedisWorkersCount' => 'RedisWorkersCount',
239
			'SidebarLinks' => 'SidebarLinks',
240
			'SupportLinks' => 'get_support_links',
241
			'LoggedInLinks' => 'get_logged_in_links',
242
		];
243
	}
244
245
	/**
246
	 */
247
	public function init() {
248
		parent::init();
249
250
		if (!Member::currentUser() && !Session::get('AutoLoginHash')) {
251
			return Security::permissionFailure();
252
		}
253
254
		// Block framework jquery
255
		Requirements::block(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
256
257
		self::include_requirements();
258
	}
259
260
	/**
261
	 * @return string
262
	 */
263
	public function Link() {
264
		return "naut/";
265
	}
266
267
	/**
268
	 * Actions
269
	 *
270
	 * @param \SS_HTTPRequest $request
271
	 * @return \SS_HTTPResponse
272
	 */
273
	public function index(\SS_HTTPRequest $request) {
274
		return $this->redirect($this->Link() . 'projects/');
275
	}
276
277
	/**
278
	 * Action
279
	 *
280
	 * @param \SS_HTTPRequest $request
281
	 * @return string - HTML
282
	 */
283
	public function projects(\SS_HTTPRequest $request) {
284
		// Performs canView permission check by limiting visible projects in DNProjectsList() call.
285
		return $this->customise([
286
			'Title' => 'Projects',
287
		])->render();
288
	}
289
290
	/**
291
	 * @param \SS_HTTPRequest $request
292
	 * @return HTMLText
293
	 */
294
	public function nav(\SS_HTTPRequest $request) {
295
		return $this->renderWith('Nav');
296
	}
297
298
	/**
299
	 * Return a link to the navigation template used for AJAX requests.
300
	 * @return string
301
	 */
302
	public function NavLink() {
303
		$currentProject = $this->getCurrentProject();
304
		$projectName = $currentProject ? $currentProject->Name : null;
305
		return Controller::join_links(Director::absoluteBaseURL(), 'naut', 'nav', $projectName);
306
	}
307
308
	/**
309
	 * Action
310
	 *
311
	 * @param \SS_HTTPRequest $request
312
	 * @return SS_HTTPResponse - HTML
313
	 */
314
	public function snapshots(\SS_HTTPRequest $request) {
315
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
316
		return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots');
317
	}
318
319
	/**
320
	 * Action
321
	 *
322
	 * @param \SS_HTTPRequest $request
323
	 * @return string - HTML
324
	 */
325 View Code Duplication
	public function createsnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
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...
326
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
327
328
		// Performs canView permission check by limiting visible projects
329
		$project = $this->getCurrentProject();
330
		if (!$project) {
331
			return $this->project404Response();
332
		}
333
334
		if (!$project->canBackup()) {
335
			return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
336
		}
337
338
		return $this->customise([
339
			'Title' => 'Create Data Snapshot',
340
			'SnapshotsSection' => 1,
341
			'DataTransferForm' => $this->getDataTransferForm($request)
342
		])->render();
343
	}
344
345
	/**
346
	 * Action
347
	 *
348
	 * @param \SS_HTTPRequest $request
349
	 * @return string - HTML
350
	 */
351 View Code Duplication
	public function uploadsnapshot(\SS_HTTPRequest $request) {
0 ignored issues
show
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...
352
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
353
354
		// Performs canView permission check by limiting visible projects
355
		$project = $this->getCurrentProject();
356
		if (!$project) {
357
			return $this->project404Response();
358
		}
359
360
		if (!$project->canUploadArchive()) {
361
			return new SS_HTTPResponse("Not allowed to upload", 401);
362
		}
363
364
		return $this->customise([
365
			'SnapshotsSection' => 1,
366
			'UploadSnapshotForm' => $this->getUploadSnapshotForm($request),
367
			'PostSnapshotForm' => $this->getPostSnapshotForm($request)
368
		])->render();
369
	}
370
371
	/**
372
	 * Return the upload limit for snapshot uploads
373
	 * @return string
374
	 */
375
	public function UploadLimit() {
376
		return File::format_size(min(
377
			File::ini2bytes(ini_get('upload_max_filesize')),
378
			File::ini2bytes(ini_get('post_max_size'))
379
		));
380
	}
381
382
	/**
383
	 * Construct the upload form.
384
	 *
385
	 * @param \SS_HTTPRequest $request
386
	 * @return Form
387
	 */
388
	public function getUploadSnapshotForm(\SS_HTTPRequest $request) {
389
		// Performs canView permission check by limiting visible projects
390
		$project = $this->getCurrentProject();
391
		if (!$project) {
392
			return $this->project404Response();
393
		}
394
395
		if (!$project->canUploadArchive()) {
396
			return new SS_HTTPResponse("Not allowed to upload", 401);
397
		}
398
399
		// Framing an environment as a "group of people with download access"
400
		// makes more sense to the user here, while still allowing us to enforce
401
		// environment specific restrictions on downloading the file later on.
402
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
403
			return $item->canUploadArchive();
404
		});
405
		$envsMap = [];
406
		foreach ($envs as $env) {
407
			$envsMap[$env->ID] = $env->Name;
408
		}
409
410
		$maxSize = min(File::ini2bytes(ini_get('upload_max_filesize')), File::ini2bytes(ini_get('post_max_size')));
411
		$fileField = DataArchiveFileField::create('ArchiveFile', 'File');
412
		$fileField->getValidator()->setAllowedExtensions(['sspak']);
413
		$fileField->getValidator()->setAllowedMaxFileSize(['*' => $maxSize]);
414
415
		$form = Form::create(
416
			$this,
417
			'UploadSnapshotForm',
418
			FieldList::create(
419
				$fileField,
420
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
421
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
422
					->setEmptyString('Select an environment')
423
			),
424
			FieldList::create(
425
				FormAction::create('doUploadSnapshot', 'Upload File')
426
					->addExtraClass('btn')
427
			),
428
			RequiredFields::create('ArchiveFile')
429
		);
430
431
		$form->disableSecurityToken();
432
		$form->addExtraClass('fields-wide');
433
		// Tweak the action so it plays well with our fake URL structure.
434
		$form->setFormAction($project->Link() . '/UploadSnapshotForm');
435
436
		return $form;
437
	}
438
439
	/**
440
	 * @param array $data
441
	 * @param Form $form
442
	 *
443
	 * @return bool|HTMLText|SS_HTTPResponse
444
	 */
445
	public function doUploadSnapshot($data, \Form $form) {
446
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
447
448
		// Performs canView permission check by limiting visible projects
449
		$project = $this->getCurrentProject();
450
		if (!$project) {
451
			return $this->project404Response();
452
		}
453
454
		$validEnvs = $project->DNEnvironmentList()
455
			->filterByCallback(function ($item) {
456
				return $item->canUploadArchive();
457
			});
458
459
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
460
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
461
		if (!$environment) {
462
			throw new LogicException('Invalid environment');
463
		}
464
465
		$this->validateSnapshotMode($data['Mode']);
466
467
		$dataArchive = DNDataArchive::create([
468
			'AuthorID' => Member::currentUserID(),
469
			'EnvironmentID' => $data['EnvironmentID'],
470
			'IsManualUpload' => true,
471
		]);
472
		// needs an ID and transfer to determine upload path
473
		$dataArchive->write();
474
		$dataTransfer = DNDataTransfer::create([
475
			'AuthorID' => Member::currentUserID(),
476
			'Mode' => $data['Mode'],
477
			'Origin' => 'ManualUpload',
478
			'EnvironmentID' => $data['EnvironmentID']
479
		]);
480
		$dataTransfer->write();
481
		$dataArchive->DataTransfers()->add($dataTransfer);
482
		$form->saveInto($dataArchive);
483
		$dataArchive->write();
484
		$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
485
486 View Code Duplication
		$cleanupFn = function () use ($workingDir, $dataTransfer, $dataArchive) {
0 ignored issues
show
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...
487
			$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
488
			$process->setTimeout(120);
489
			$process->run();
490
			$dataTransfer->delete();
491
			$dataArchive->delete();
492
		};
493
494
		// extract the sspak contents so we can inspect them
495
		try {
496
			$dataArchive->extractArchive($workingDir);
497
		} catch (Exception $e) {
498
			$cleanupFn();
499
			$form->sessionMessage(
500
				'There was a problem trying to open your snapshot for processing. Please try uploading again',
501
				'bad'
502
			);
503
			return $this->redirectBack();
504
		}
505
506
		// validate that the sspak contents match the declared contents
507
		$result = $dataArchive->validateArchiveContents();
508
		if (!$result->valid()) {
509
			$cleanupFn();
510
			$form->sessionMessage($result->message(), 'bad');
511
			return $this->redirectBack();
512
		}
513
514
		// fix file permissions of extracted sspak files then re-build the sspak
515
		try {
516
			$dataArchive->fixArchivePermissions($workingDir);
517
			$dataArchive->setArchiveFromFiles($workingDir);
518
		} catch (Exception $e) {
519
			$cleanupFn();
520
			$form->sessionMessage(
521
				'There was a problem processing your snapshot. Please try uploading again',
522
				'bad'
523
			);
524
			return $this->redirectBack();
525
		}
526
527
		// cleanup any extracted sspak contents lying around
528
		$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
529
		$process->setTimeout(120);
530
		$process->run();
531
532
		return $this->customise([
533
			'Project' => $project,
534
			'CurrentProject' => $project,
535
			'SnapshotsSection' => 1,
536
			'DataArchive' => $dataArchive,
537
			'DataTransferRestoreForm' => $this->getDataTransferRestoreForm($this->request, $dataArchive),
538
			'BackURL' => $project->Link('snapshots')
539
		])->renderWith(['DNRoot_uploadsnapshot', 'DNRoot']);
540
	}
541
542
	/**
543
	 * @param \SS_HTTPRequest $request
544
	 * @return Form
545
	 */
546
	public function getPostSnapshotForm(\SS_HTTPRequest $request) {
547
		// Performs canView permission check by limiting visible projects
548
		$project = $this->getCurrentProject();
549
		if (!$project) {
550
			return $this->project404Response();
551
		}
552
553
		if (!$project->canUploadArchive()) {
554
			return new SS_HTTPResponse("Not allowed to upload", 401);
555
		}
556
557
		// Framing an environment as a "group of people with download access"
558
		// makes more sense to the user here, while still allowing us to enforce
559
		// environment specific restrictions on downloading the file later on.
560
		$envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
561
			return $item->canUploadArchive();
562
		});
563
		$envsMap = [];
564
		foreach ($envs as $env) {
565
			$envsMap[$env->ID] = $env->Name;
566
		}
567
568
		$form = Form::create(
569
			$this,
570
			'PostSnapshotForm',
571
			FieldList::create(
572
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
573
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
574
					->setEmptyString('Select an environment')
575
			),
576
			FieldList::create(
577
				FormAction::create('doPostSnapshot', 'Submit request')
578
					->addExtraClass('btn')
579
			),
580
			RequiredFields::create('File')
581
		);
582
583
		$form->disableSecurityToken();
584
		$form->addExtraClass('fields-wide');
585
		// Tweak the action so it plays well with our fake URL structure.
586
		$form->setFormAction($project->Link() . '/PostSnapshotForm');
587
588
		return $form;
589
	}
590
591
	/**
592
	 * @param array $data
593
	 * @param Form $form
594
	 *
595
	 * @return SS_HTTPResponse
596
	 */
597
	public function doPostSnapshot($data, $form) {
598
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
599
600
		$project = $this->getCurrentProject();
601
		if (!$project) {
602
			return $this->project404Response();
603
		}
604
605
		$validEnvs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
606
			return $item->canUploadArchive();
607
		});
608
609
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
610
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
611
		if (!$environment) {
612
			throw new LogicException('Invalid environment');
613
		}
614
615
		$dataArchive = DNDataArchive::create([
616
			'UploadToken' => DNDataArchive::generate_upload_token(),
617
		]);
618
		$form->saveInto($dataArchive);
619
		$dataArchive->write();
620
621
		return $this->redirect(Controller::join_links(
622
			$project->Link(),
623
			'postsnapshotsuccess',
624
			$dataArchive->ID
625
		));
626
	}
627
628
	/**
629
	 * Action
630
	 *
631
	 * @param \SS_HTTPRequest $request
632
	 * @return SS_HTTPResponse - HTML
633
	 */
634
	public function snapshotslog(\SS_HTTPRequest $request) {
635
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
636
		return $this->getCustomisedViewSection('SnapshotsSection', 'Snapshots log');
637
	}
638
639
	/**
640
	 * @param \SS_HTTPRequest $request
641
	 * @return SS_HTTPResponse|string
642
	 * @throws SS_HTTPResponse_Exception
643
	 */
644
	public function postsnapshotsuccess(\SS_HTTPRequest $request) {
645
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
646
647
		// Performs canView permission check by limiting visible projects
648
		$project = $this->getCurrentProject();
649
		if (!$project) {
650
			return $this->project404Response();
651
		}
652
653
		if (!$project->canUploadArchive()) {
654
			return new SS_HTTPResponse("Not allowed to upload", 401);
655
		}
656
657
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
658
		if (!$dataArchive) {
659
			return new SS_HTTPResponse("Archive not found.", 404);
660
		}
661
662
		if (!$dataArchive->canRestore()) {
663
			throw new SS_HTTPResponse_Exception('Not allowed to restore archive', 403);
664
		}
665
666
		return $this->render([
667
			'Title' => 'How to send us your Data Snapshot by post',
668
			'DataArchive' => $dataArchive,
669
			'Address' => Config::inst()->get('Deploynaut', 'snapshot_post_address'),
670
			'BackURL' => $project->Link(),
671
		]);
672
	}
673
674
	/**
675
	 * @param \SS_HTTPRequest $request
676
	 * @return \SS_HTTPResponse
677
	 */
678
	public function project(\SS_HTTPRequest $request) {
679
		$this->setCurrentActionType(self::PROJECT_OVERVIEW);
680
		return $this->getCustomisedViewSection('ProjectOverview', '', ['IsAdmin' => Permission::check('ADMIN')]);
681
	}
682
683
	/**
684
	 * This action will star / unstar a project for the current member
685
	 *
686
	 * @param \SS_HTTPRequest $request
687
	 *
688
	 * @return SS_HTTPResponse
689
	 */
690
	public function toggleprojectstar(\SS_HTTPRequest $request) {
691
		$project = $this->getCurrentProject();
692
		if (!$project) {
693
			return $this->project404Response();
694
		}
695
696
		$member = Member::currentUser();
697
		if ($member === null) {
698
			return $this->project404Response();
699
		}
700
		$favProject = $member->StarredProjects()
701
			->filter('DNProjectID', $project->ID)
702
			->first();
703
704
		if ($favProject) {
705
			$member->StarredProjects()->remove($favProject);
706
		} else {
707
			$member->StarredProjects()->add($project);
708
		}
709
710
		if (!$request->isAjax()) {
711
			return $this->redirectBack();
712
		}
713
	}
714
715
	/**
716
	 * @param \SS_HTTPRequest $request
717
	 * @return \SS_HTTPResponse
718
	 */
719
	public function branch(\SS_HTTPRequest $request) {
720
		$project = $this->getCurrentProject();
721
		if (!$project) {
722
			return $this->project404Response();
723
		}
724
725
		$branchName = $request->getVar('name');
726
		$branch = $project->DNBranchList()->byName($branchName);
727
		if (!$branch) {
728
			return new SS_HTTPResponse("Branch '" . Convert::raw2xml($branchName) . "' not found.", 404);
729
		}
730
731
		return $this->render([
732
			'CurrentBranch' => $branch,
733
		]);
734
	}
735
736
	/**
737
	 * @param \SS_HTTPRequest $request
738
	 * @return \SS_HTTPResponse
739
	 */
740
	public function environment(\SS_HTTPRequest $request) {
741
		// Performs canView permission check by limiting visible projects
742
		$project = $this->getCurrentProject();
743
		if (!$project) {
744
			return $this->project404Response();
745
		}
746
747
		// Performs canView permission check by limiting visible projects
748
		$env = $this->getCurrentEnvironment($project);
749
		if (!$env) {
750
			return $this->environment404Response();
751
		}
752
753
		return $this->render([
754
			'DNEnvironmentList' => $this->getCurrentProject()->DNEnvironmentList(),
755
			'Redeploy' => (bool) $request->getVar('redeploy')
756
		]);
757
	}
758
759
	/**
760
	 * Shows the creation log.
761
	 *
762
	 * @param \SS_HTTPRequest $request
763
	 * @return string
764
	 */
765
	public function createenv(\SS_HTTPRequest $request) {
766
		$params = $request->params();
767
		if ($params['Identifier']) {
768
			$record = DNCreateEnvironment::get()->byId($params['Identifier']);
769
770
			if (!$record || !$record->ID) {
771
				throw new SS_HTTPResponse_Exception('Create environment not found', 404);
772
			}
773
			if (!$record->canView()) {
774
				return Security::permissionFailure();
775
			}
776
777
			$project = $this->getCurrentProject();
778
			if (!$project) {
779
				return $this->project404Response();
780
			}
781
782
			if ($project->Name != $params['Project']) {
783
				throw new LogicException("Project in URL doesn't match this creation");
784
			}
785
786
			return $this->render([
787
				'CreateEnvironment' => $record,
788
			]);
789
		}
790
		return $this->render(['CurrentTitle' => 'Create an environment']);
791
	}
792
793
	public function createenvlog(\SS_HTTPRequest $request) {
794
		$params = $request->params();
795
		$env = DNCreateEnvironment::get()->byId($params['Identifier']);
796
797
		if (!$env || !$env->ID) {
798
			throw new SS_HTTPResponse_Exception('Log not found', 404);
799
		}
800
		if (!$env->canView()) {
801
			return Security::permissionFailure();
802
		}
803
804
		$project = $env->Project();
805
806
		if ($project->Name != $params['Project']) {
807
			throw new LogicException("Project in URL doesn't match this deploy");
808
		}
809
810
		$log = $env->log();
811
		if ($log->exists()) {
812
			$content = $log->content();
813
		} else {
814
			$content = 'Waiting for action to start';
815
		}
816
817
		return $this->sendResponse($env->ResqueStatus(), $content);
818
	}
819
820
	/**
821
	 * @param \SS_HTTPRequest $request
822
	 * @return Form
823
	 */
824
	public function getCreateEnvironmentForm(\SS_HTTPRequest $request = null) {
825
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
826
827
		$project = $this->getCurrentProject();
828
		if (!$project) {
829
			return $this->project404Response();
830
		}
831
832
		$envType = $project->AllowedEnvironmentType;
0 ignored issues
show
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...
833
		if (!$envType || !class_exists($envType)) {
834
			return null;
835
		}
836
837
		$backend = Injector::inst()->get($envType);
838
		if (!($backend instanceof EnvironmentCreateBackend)) {
839
			// Only allow this for supported backends.
840
			return null;
841
		}
842
843
		$fields = $backend->getCreateEnvironmentFields($project);
844
		if (!$fields) {
845
			return null;
846
		}
847
848
		if (!$project->canCreateEnvironments()) {
849
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
850
		}
851
852
		$form = Form::create(
853
			$this,
854
			'CreateEnvironmentForm',
855
			$fields,
856
			FieldList::create(
857
				FormAction::create('doCreateEnvironment', 'Create')
858
					->addExtraClass('btn')
859
			),
860
			$backend->getCreateEnvironmentValidator()
861
		);
862
863
		// Tweak the action so it plays well with our fake URL structure.
864
		$form->setFormAction($project->Link() . '/CreateEnvironmentForm');
865
866
		return $form;
867
	}
868
869
	/**
870
	 * @param array $data
871
	 * @param Form $form
872
	 *
873
	 * @return bool|HTMLText|SS_HTTPResponse
874
	 */
875
	public function doCreateEnvironment($data, Form $form) {
0 ignored issues
show
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...
876
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
877
878
		$project = $this->getCurrentProject();
879
		if (!$project) {
880
			return $this->project404Response();
881
		}
882
883
		if (!$project->canCreateEnvironments()) {
884
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
885
		}
886
887
		// Set the environment type so we know what we're creating.
888
		$data['EnvironmentType'] = $project->AllowedEnvironmentType;
0 ignored issues
show
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...
889
890
		$job = DNCreateEnvironment::create();
891
892
		$job->Data = serialize($data);
893
		$job->ProjectID = $project->ID;
894
		$job->write();
895
		$job->start();
896
897
		return $this->redirect($project->Link('createenv') . '/' . $job->ID);
898
	}
899
900
	/**
901
	 * Get the DNData object.
902
	 *
903
	 * @return DNData
904
	 */
905
	public function DNData() {
906
		return DNData::inst();
907
	}
908
909
	/**
910
	 * Provide a list of all projects.
911
	 *
912
	 * @return SS_List
913
	 */
914
	public function DNProjectList() {
915
		$memberId = Member::currentUserID();
916
		if (!$memberId) {
917
			return new ArrayList();
918
		}
919
920
		if (Permission::check('ADMIN')) {
921
			return DNProject::get();
922
		}
923
924
		$projects = Member::get()->filter('ID', $memberId)
925
			->relation('Groups')
926
			->relation('Projects');
927
928
		$this->extend('updateDNProjectList', $projects);
929
		return $projects;
930
	}
931
932
	/**
933
	 * @return ArrayList
934
	 */
935
	public function getPlatformSpecificStrings() {
936
		$strings = $this->config()->platform_specific_strings;
937
		if ($strings) {
938
			return new ArrayList($strings);
939
		}
940
	}
941
942
	/**
943
	 * Provide a list of all starred projects for the currently logged in member
944
	 *
945
	 * @return SS_List
946
	 */
947
	public function getStarredProjects() {
948
		$member = Member::currentUser();
949
		if ($member === null) {
950
			return new ArrayList();
951
		}
952
953
		$favProjects = $member->StarredProjects();
954
955
		$list = new ArrayList();
956
		foreach ($favProjects as $project) {
957
			if ($project->canView($member)) {
958
				$list->add($project);
959
			}
960
		}
961
		return $list;
962
	}
963
964
	/**
965
	 * Returns top level navigation of projects.
966
	 *
967
	 * @param int $limit
968
	 *
969
	 * @return ArrayList
970
	 */
971
	public function Navigation($limit = 5) {
972
		$navigation = new ArrayList();
973
974
		$currentProject = $this->getCurrentProject();
975
		$currentEnvironment = $this->getCurrentEnvironment();
976
		$actionType = $this->getCurrentActionType();
977
978
		$projects = $this->getStarredProjects();
979
		if ($projects->count() < 1) {
980
			$projects = $this->DNProjectList();
981
		} else {
982
			$limit = -1;
983
		}
984
985
		if ($projects->count() > 0) {
986
			$activeProject = false;
987
988
			if ($limit > 0) {
989
				$limitedProjects = $projects->limit($limit);
990
			} else {
991
				$limitedProjects = $projects;
992
			}
993
994
			foreach ($limitedProjects as $project) {
995
				$isActive = $currentProject && $currentProject->ID == $project->ID;
996
				if ($isActive) {
997
					$activeProject = true;
998
				}
999
1000
				$isCurrentEnvironment = false;
1001
				if ($project && $currentEnvironment) {
1002
					$isCurrentEnvironment = (bool) $project->DNEnvironmentList()->find('ID', $currentEnvironment->ID);
1003
				}
1004
1005
				$navigation->push([
1006
					'Project' => $project,
1007
					'IsCurrentEnvironment' => $isCurrentEnvironment,
1008
					'IsActive' => $currentProject && $currentProject->ID == $project->ID,
1009
					'IsOverview' => $actionType == self::PROJECT_OVERVIEW && !$isCurrentEnvironment && $currentProject->ID == $project->ID
0 ignored issues
show
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...
1010
				]);
1011
			}
1012
1013
			// Ensure the current project is in the list
1014
			if (!$activeProject && $currentProject) {
1015
				$navigation->unshift([
1016
					'Project' => $currentProject,
1017
					'IsActive' => true,
1018
					'IsCurrentEnvironment' => $currentEnvironment,
1019
					'IsOverview' => $actionType == self::PROJECT_OVERVIEW && !$currentEnvironment
1020
				]);
1021
				if ($limit > 0 && $navigation->count() > $limit) {
1022
					$navigation->pop();
1023
				}
1024
			}
1025
		}
1026
1027
		return $navigation;
1028
	}
1029
1030
	/**
1031
	 * Construct the deployment form
1032
	 *
1033
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1034
	 *
1035
	 * @return Form
1036
	 */
1037
	public function getDeployForm($request = null) {
1038
1039
		// Performs canView permission check by limiting visible projects
1040
		$project = $this->getCurrentProject();
1041
		if (!$project) {
1042
			return $this->project404Response();
1043
		}
1044
1045
		// Performs canView permission check by limiting visible projects
1046
		$environment = $this->getCurrentEnvironment($project);
1047
		if (!$environment) {
1048
			return $this->environment404Response();
1049
		}
1050
1051
		if (!$environment->canDeploy()) {
1052
			return new SS_HTTPResponse("Not allowed to deploy", 401);
1053
		}
1054
1055
		// Generate the form
1056
		$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...
1057
1058
		// If this is an ajax request we don't want to submit the form - we just want to retrieve the markup.
1059
		if (
1060
			$request &&
1061
			!$request->requestVar('action_showDeploySummary') &&
1062
			$this->getRequest()->isAjax() &&
1063
			$this->getRequest()->isGET()
1064
		) {
1065
			// We can just use the URL we're accessing
1066
			$form->setFormAction($this->getRequest()->getURL());
1067
1068
			$body = json_encode(['Content' => $form->forAjaxTemplate()->forTemplate()]);
1069
			$this->getResponse()->addHeader('Content-Type', 'application/json');
1070
			$this->getResponse()->setBody($body);
1071
			return $body;
1072
		}
1073
1074
		$form->setFormAction($this->getRequest()->getURL() . '/DeployForm');
1075
		return $form;
1076
	}
1077
1078
	/**
1079
	 * @deprecated 2.0.0 - moved to GitDispatcher
1080
	 *
1081
	 * @param \SS_HTTPRequest $request
1082
	 *
1083
	 * @return SS_HTTPResponse|string
1084
	 */
1085
	public function gitRevisions(\SS_HTTPRequest $request) {
1086
1087
		// Performs canView permission check by limiting visible projects
1088
		$project = $this->getCurrentProject();
1089
		if (!$project) {
1090
			return $this->project404Response();
1091
		}
1092
1093
		// Performs canView permission check by limiting visible projects
1094
		$env = $this->getCurrentEnvironment($project);
1095
		if (!$env) {
1096
			return $this->environment404Response();
1097
		}
1098
1099
		$options = [];
1100
		foreach ($env->getSupportedOptions() as $option) {
1101
			$options[] = [
1102
				'name' => $option->getName(),
1103
				'title' => $option->getTitle(),
1104
				'defaultValue' => $option->getDefaultValue()
1105
			];
1106
		}
1107
1108
		$tabs = [];
1109
		$id = 0;
1110
		$data = [
1111
			'id' => ++$id,
1112
			'name' => 'Deploy the latest version of a branch',
1113
			'field_type' => 'dropdown',
1114
			'field_label' => 'Choose a branch',
1115
			'field_id' => 'branch',
1116
			'field_data' => [],
1117
			'options' => $options
1118
		];
1119 View Code Duplication
		foreach ($project->DNBranchList() as $branch) {
0 ignored issues
show
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...
1120
			$sha = $branch->SHA();
1121
			$name = $branch->Name();
1122
			$branchValue = sprintf("%s (%s, %s old)",
1123
				$name,
1124
				substr($sha, 0, 8),
1125
				$branch->LastUpdated()->TimeDiff()
1126
			);
1127
			$data['field_data'][] = [
1128
				'id' => $sha,
1129
				'text' => $branchValue,
1130
				'branch_name' => $name // the raw branch name, not including the time etc
1131
			];
1132
		}
1133
		$tabs[] = $data;
1134
1135
		$data = [
1136
			'id' => ++$id,
1137
			'name' => 'Deploy a tagged release',
1138
			'field_type' => 'dropdown',
1139
			'field_label' => 'Choose a tag',
1140
			'field_id' => 'tag',
1141
			'field_data' => [],
1142
			'options' => $options
1143
		];
1144
1145 View Code Duplication
		foreach ($project->DNTagList()->setLimit(null) as $tag) {
0 ignored issues
show
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...
1146
			$name = $tag->Name();
1147
			$data['field_data'][] = [
1148
				'id' => $tag->SHA(),
1149
				'text' => sprintf("%s", $name)
1150
			];
1151
		}
1152
1153
		// show newest tags first.
1154
		$data['field_data'] = array_reverse($data['field_data']);
1155
1156
		$tabs[] = $data;
1157
1158
		// Past deployments
1159
		$data = [
1160
			'id' => ++$id,
1161
			'name' => 'Redeploy a release that was previously deployed (to any environment)',
1162
			'field_type' => 'dropdown',
1163
			'field_label' => 'Choose a previously deployed release',
1164
			'field_id' => 'release',
1165
			'field_data' => [],
1166
			'options' => $options
1167
		];
1168
		// We are aiming at the format:
1169
		// [{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...
1170
		$redeploy = [];
1171 View Code Duplication
		foreach ($project->DNEnvironmentList() as $dnEnvironment) {
0 ignored issues
show
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...
1172
			$envName = $dnEnvironment->Name;
1173
			$perEnvDeploys = [];
1174
1175
			foreach ($dnEnvironment->DeployHistory()->filter('State', \DNDeployment::STATE_COMPLETED) as $deploy) {
1176
				$sha = $deploy->SHA;
1177
1178
				// Check if exists to make sure the newest deployment date is used.
1179
				if (!isset($perEnvDeploys[$sha])) {
1180
					$pastValue = sprintf("%s (deployed %s)",
1181
						substr($sha, 0, 8),
1182
						$deploy->obj('LastEdited')->Ago()
1183
					);
1184
					$perEnvDeploys[$sha] = [
1185
						'id' => $sha,
1186
						'text' => $pastValue
1187
					];
1188
				}
1189
			}
1190
1191
			if (!empty($perEnvDeploys)) {
1192
				$redeploy[$envName] = array_values($perEnvDeploys);
1193
			}
1194
		}
1195
		// Convert the array to the frontend format (i.e. keyed to regular array)
1196
		foreach ($redeploy as $name => $descr) {
1197
			$data['field_data'][] = ['text' => $name, 'children' => $descr];
1198
		}
1199
		$tabs[] = $data;
1200
1201
		$data = [
1202
			'id' => ++$id,
1203
			'name' => 'Deploy a specific SHA',
1204
			'field_type' => 'textfield',
1205
			'field_label' => 'Choose a SHA',
1206
			'field_id' => 'SHA',
1207
			'field_data' => [],
1208
			'options' => $options
1209
		];
1210
		$tabs[] = $data;
1211
1212
		// get the last time git fetch was run
1213
		$lastFetched = 'never';
1214
		$fetch = DNGitFetch::get()
1215
			->filter([
1216
				'ProjectID' => $project->ID,
1217
				'Status' => 'Finished'
1218
			])
1219
			->sort('LastEdited', 'DESC')
1220
			->first();
1221
		if ($fetch) {
1222
			$lastFetched = $fetch->dbObject('LastEdited')->Ago();
1223
		}
1224
1225
		$data = [
1226
			'Tabs' => $tabs,
1227
			'last_fetched' => $lastFetched
1228
		];
1229
1230
		$this->applyRedeploy($request, $data);
1231
1232
		return json_encode($data, JSON_PRETTY_PRINT);
1233
	}
1234
1235
	/**
1236
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1237
	 *
1238
	 * @param \SS_HTTPRequest $request
1239
	 *
1240
	 * @return string
1241
	 */
1242
	public function deploySummary(\SS_HTTPRequest $request) {
1243
1244
		// Performs canView permission check by limiting visible projects
1245
		$project = $this->getCurrentProject();
1246
		if (!$project) {
1247
			return $this->project404Response();
1248
		}
1249
1250
		// Performs canView permission check by limiting visible projects
1251
		$environment = $this->getCurrentEnvironment($project);
1252
		if (!$environment) {
1253
			return $this->environment404Response();
1254
		}
1255
1256
		// Plan the deployment.
1257
		$strategy = $environment->getDeployStrategy($request);
1258
		$data = $strategy->toArray();
1259
1260
		// Add in a URL for comparing from->to code changes. Ensure that we have
1261
		// two proper 40 character SHAs, otherwise we can't show the compare link.
1262
		$interface = $project->getRepositoryInterface();
1263
		if (
1264
			!empty($interface) && !empty($interface->URL)
1265
			&& !empty($data['changes']['Code version']['from'])
1266
			&& strlen($data['changes']['Code version']['from']) == '40'
1267
			&& !empty($data['changes']['Code version']['to'])
1268
			&& strlen($data['changes']['Code version']['to']) == '40'
1269
		) {
1270
			$compareurl = sprintf(
1271
				'%s/compare/%s...%s',
1272
				$interface->URL,
1273
				$data['changes']['Code version']['from'],
1274
				$data['changes']['Code version']['to']
1275
			);
1276
			$data['changes']['Code version']['compareUrl'] = $compareurl;
1277
		}
1278
1279
		// Append json to response
1280
		$token = SecurityToken::inst();
1281
		$data['SecurityID'] = $token->getValue();
1282
1283
		$this->extend('updateDeploySummary', $data);
1284
1285
		return json_encode($data);
1286
	}
1287
1288
	/**
1289
	 * Deployment form submission handler.
1290
	 *
1291
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1292
	 *
1293
	 * Initiate a DNDeployment record and redirect to it for status polling
1294
	 *
1295
	 * @param \SS_HTTPRequest $request
1296
	 *
1297
	 * @return SS_HTTPResponse
1298
	 * @throws ValidationException
1299
	 * @throws null
1300
	 */
1301
	public function startDeploy(\SS_HTTPRequest $request) {
1302
1303
		$token = SecurityToken::inst();
1304
1305
		// Ensure the submitted token has a value
1306
		$submittedToken = $request->postVar(\Dispatcher::SECURITY_TOKEN_NAME);
1307
		if (!$submittedToken) {
1308
			return false;
1309
		}
1310
		// Do the actual check.
1311
		$check = $token->check($submittedToken);
1312
		// Ensure the CSRF Token is correct
1313
		if (!$check) {
1314
			// CSRF token didn't match
1315
			return $this->httpError(400, 'Bad Request');
1316
		}
1317
1318
		// Performs canView permission check by limiting visible projects
1319
		$project = $this->getCurrentProject();
1320
		if (!$project) {
1321
			return $this->project404Response();
1322
		}
1323
1324
		// Performs canView permission check by limiting visible projects
1325
		$environment = $this->getCurrentEnvironment($project);
1326
		if (!$environment) {
1327
			return $this->environment404Response();
1328
		}
1329
1330
		// Initiate the deployment
1331
		// The extension point should pass in: Project, Environment, SelectRelease, buildName
1332
		$this->extend('doDeploy', $project, $environment, $buildName, $data);
1333
1334
		// Start the deployment based on the approved strategy.
1335
		$strategy = new DeploymentStrategy($environment);
1336
		$strategy->fromArray($request->requestVar('strategy'));
1337
		$deployment = $strategy->createDeployment();
1338
		// Bypass approval by going straight to Queued.
1339
		$deployment->getMachine()->apply(DNDeployment::TR_QUEUE);
1340
1341
		return json_encode([
1342
			'url' => Director::absoluteBaseURL() . $deployment->Link()
1343
		], JSON_PRETTY_PRINT);
1344
	}
1345
1346
	/**
1347
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1348
	 *
1349
	 * Action - Do the actual deploy
1350
	 *
1351
	 * @param \SS_HTTPRequest $request
1352
	 *
1353
	 * @return SS_HTTPResponse|string
1354
	 * @throws SS_HTTPResponse_Exception
1355
	 */
1356
	public function deploy(\SS_HTTPRequest $request) {
1357
		$params = $request->params();
1358
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1359
1360
		if (!$deployment || !$deployment->ID) {
1361
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1362
		}
1363
		if (!$deployment->canView()) {
1364
			return Security::permissionFailure();
1365
		}
1366
1367
		$environment = $deployment->Environment();
1368
		$project = $environment->Project();
1369
1370
		if ($environment->Name != $params['Environment']) {
1371
			throw new LogicException("Environment in URL doesn't match this deploy");
1372
		}
1373
		if ($project->Name != $params['Project']) {
1374
			throw new LogicException("Project in URL doesn't match this deploy");
1375
		}
1376
1377
		return $this->render([
1378
			'Deployment' => $deployment,
1379
		]);
1380
	}
1381
1382
	/**
1383
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1384
	 *
1385
	 * Action - Get the latest deploy log
1386
	 *
1387
	 * @param \SS_HTTPRequest $request
1388
	 *
1389
	 * @return string
1390
	 * @throws SS_HTTPResponse_Exception
1391
	 */
1392
	public function deploylog(\SS_HTTPRequest $request) {
1393
		$params = $request->params();
1394
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1395
1396
		if (!$deployment || !$deployment->ID) {
1397
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1398
		}
1399
		if (!$deployment->canView()) {
1400
			return Security::permissionFailure();
1401
		}
1402
1403
		$environment = $deployment->Environment();
1404
		$project = $environment->Project();
1405
1406
		if ($environment->Name != $params['Environment']) {
1407
			throw new LogicException("Environment in URL doesn't match this deploy");
1408
		}
1409
		if ($project->Name != $params['Project']) {
1410
			throw new LogicException("Project in URL doesn't match this deploy");
1411
		}
1412
1413
		$log = $deployment->log();
1414
		if ($log->exists()) {
1415
			$content = $log->content();
1416
		} else {
1417
			$content = 'Waiting for action to start';
1418
		}
1419
1420
		return $this->sendResponse($deployment->ResqueStatus(), $content);
1421
	}
1422
1423
	/**
1424
	 * @deprecated 2.0.0 - moved to DeployDispatcher
1425
	 *
1426
	 * @param \SS_HTTPRequest $request
1427
	 *
1428
	 * @return string
1429
	 * @throws SS_HTTPResponse_Exception
1430
	 */
1431
	public function abortDeploy(\SS_HTTPRequest $request) {
1432
		$params = $request->params();
1433
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1434
1435
		if (!$deployment || !$deployment->ID) {
1436
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1437
		}
1438
		if (!$deployment->canView()) {
1439
			return Security::permissionFailure();
1440
		}
1441
1442
		// For now restrict to ADMINs only.
1443
		if (!Permission::check('ADMIN')) {
1444
			return Security::permissionFailure();
1445
		}
1446
1447
		$environment = $deployment->Environment();
1448
		$project = $environment->Project();
1449
1450
		if ($environment->Name != $params['Environment']) {
1451
			throw new LogicException("Environment in URL doesn't match this deploy");
1452
		}
1453
		if ($project->Name != $params['Project']) {
1454
			throw new LogicException("Project in URL doesn't match this deploy");
1455
		}
1456
1457
		if (!in_array($deployment->Status, ['Queued', 'Deploying', 'Aborting'])) {
1458
			throw new LogicException(sprintf("Cannot abort from %s state.", $deployment->Status));
1459
		}
1460
1461
		$deployment->getMachine()->apply(DNDeployment::TR_ABORT);
1462
1463
		return $this->sendResponse($deployment->ResqueStatus(), []);
0 ignored issues
show
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...
1464
	}
1465
1466
	/**
1467
	 * @param \SS_HTTPRequest|null $request
1468
	 *
1469
	 * @return Form
1470
	 */
1471
	public function getDataTransferForm(\SS_HTTPRequest $request = null) {
1472
		// Performs canView permission check by limiting visible projects
1473
		$envs = $this->getCurrentProject()->DNEnvironmentList()->filterByCallback(function ($item) {
1474
			return $item->canBackup();
1475
		});
1476
1477
		if (!$envs) {
1478
			return $this->environment404Response();
1479
		}
1480
1481
		$items = [];
1482
		$disabledEnvironments = [];
1483 View Code Duplication
		foreach($envs as $env) {
0 ignored issues
show
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...
1484
			$items[$env->ID] = $env->Title;
1485
			if ($env->CurrentBuild() === false) {
1486
				$items[$env->ID] = sprintf("%s - requires initial deployment", $env->Title);
1487
				$disabledEnvironments[] = $env->ID;
1488
			}
1489
		}
1490
1491
		$envsField = DropdownField::create('EnvironmentID', 'Environment', $items)
1492
			->setEmptyString('Select an environment');
1493
		$envsField->setDisabledItems($disabledEnvironments);
1494
1495
		$formAction = FormAction::create('doDataTransfer', 'Create')
1496
			->addExtraClass('btn');
1497
1498
		if (count($disabledEnvironments) === $envs->count()) {
1499
			$formAction->setDisabled(true);
1500
		}
1501
1502
		// Allow the _resampled dir to be included if we are a Rainforest Env
1503
		if ($this->getCurrentProject()->DNEnvironmentList()->first() instanceof RainforestEnvironment) {
0 ignored issues
show
The class RainforestEnvironment does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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