Completed
Pull Request — master (#495)
by Michael
12:40
created

DNRoot   F

Complexity

Total Complexity 318

Size/Duplication

Total Lines 2515
Duplicated Lines 6.16 %

Coupling/Cohesion

Components 1
Dependencies 58
Metric Value
wmc 318
lcom 1
cbo 58
dl 155
loc 2515
rs 0.5217

84 Methods

Rating   Name   Duplication   Size   Complexity  
A Link() 0 3 1
A index() 0 3 1
A nav() 0 3 1
A snapshots() 0 4 1
B include_requirements() 0 30 3
B FlagSnapshotsEnabled() 0 13 8
A get_support_links() 0 6 2
A get_template_global_variables() 0 8 1
A init() 0 12 3
A projects() 0 6 1
A NavLink() 0 5 2
A createsnapshot() 19 19 3
A uploadsnapshot() 19 19 3
A UploadLimit() 0 6 1
B getUploadSnapshotForm() 0 50 4
C doUploadSnapshot() 0 88 7
B getPostSnapshotForm() 0 44 4
B doPostSnapshot() 0 30 3
A snapshotslog() 0 4 1
B postsnapshotsuccess() 0 29 5
A project() 0 3 1
A toggleprojectstar() 0 21 4
A branch() 0 16 3
A environment() 0 18 3
A doDryRun() 0 3 1
A startPipeline() 0 3 1
B beginPipeline() 0 33 6
C pipeline() 0 24 7
C createenv() 0 27 7
B createenvlog() 28 28 6
C getCreateEnvironmentForm() 0 42 7
B doCreateEnvironment() 0 24 3
A metrics() 0 15 3
A DNData() 0 3 1
A DNProjectList() 0 14 3
A getPlatformSpecificStrings() 0 6 2
A getStarredProjects() 0 16 4
C Navigation() 0 47 12
C getDeployForm() 0 40 8
F gitRevisions() 0 142 13
A checkCsrfToken() 0 20 3
C deploySummary() 0 46 9
B startDeploy() 0 34 4
B deploy() 0 25 6
C deploylog() 0 30 7
B getDataTransferForm() 0 28 2
C doDataTransfer() 0 64 12
B transfer() 0 25 5
B transferlog() 29 29 6
B getDataTransferRestoreForm() 0 48 6
A restoresnapshot() 21 21 3
A deletesnapshot() 19 19 3
B getDeleteForm() 0 29 3
B doDelete() 0 30 6
A movesnapshot() 20 20 3
B getMoveForm() 0 30 3
B doMove() 0 32 5
A RedisUnavailable() 0 8 2
A RedisWorkersCount() 0 3 1
B providePermissions() 0 40 1
A getCurrentProject() 0 10 3
A getCurrentEnvironment() 0 13 4
A getCurrentActionType() 0 3 1
A setCurrentActionType() 0 3 1
C CanViewArchives() 0 30 11
A CreateEnvironmentList() 0 7 2
B CompleteDataArchives() 0 14 5
B PendingDataArchives() 0 12 5
B DataTransferLogs() 0 16 5
A DeployHistory() 0 11 3
A project404Response() 0 6 1
A environment404Response() 0 4 1
A sendResponse() 0 17 3
A validateSnapshotMode() 0 5 2
A getCustomisedViewSection() 0 14 3
A AmbientMenu() 0 17 2
A createproject() 0 6 2
A canCreateProjects() 0 6 3
A CreateProjectForm() 0 11 1
A getCreateProjectFormFields() 0 9 1
A getCreateProjectFormActions() 0 7 1
B doCreateProject() 0 24 4
C createprojectprogress() 0 49 10
B checkrepoaccess() 0 25 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
use \Symfony\Component\Process\Process;
3
4
/**
5
 * God controller for the deploynaut interface
6
 *
7
 * @package deploynaut
8
 * @subpackage control
9
 */
10
class DNRoot extends Controller implements PermissionProvider, TemplateGlobalProvider {
11
12
	/**
13
	 * @const string - action type for actions that perform deployments
14
	 */
15
	const ACTION_DEPLOY = 'deploy';
16
17
	/**
18
	 * @const string - action type for actions that manipulate snapshots
19
	 */
20
	const ACTION_SNAPSHOT = 'snapshot';
21
22
	const ACTION_ENVIRONMENTS = 'createenv';
23
24
	/**
25
	 * @var string
26
	 */
27
	private $actionType = self::ACTION_DEPLOY;
28
29
	/**
30
	 * Bypass pipeline permission code
31
	 */
32
	const DEPLOYNAUT_BYPASS_PIPELINE = 'DEPLOYNAUT_BYPASS_PIPELINE';
33
34
	/**
35
	 * Allow dryrun of pipelines
36
	 */
37
	const DEPLOYNAUT_DRYRUN_PIPELINE = 'DEPLOYNAUT_DRYRUN_PIPELINE';
38
39
	/**
40
	 * Allow advanced options on deployments
41
	 */
42
	const DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS = 'DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS';
43
44
	const ALLOW_PROD_DEPLOYMENT = 'ALLOW_PROD_DEPLOYMENT';
45
	const ALLOW_NON_PROD_DEPLOYMENT = 'ALLOW_NON_PROD_DEPLOYMENT';
46
	const ALLOW_PROD_SNAPSHOT = 'ALLOW_PROD_SNAPSHOT';
47
	const ALLOW_NON_PROD_SNAPSHOT = 'ALLOW_NON_PROD_SNAPSHOT';
48
	const ALLOW_CREATE_ENVIRONMENT = 'ALLOW_CREATE_ENVIRONMENT';
49
50
	/**
51
	 * @var array
52
	 */
53
	private static $allowed_actions = array(
54
		'projects',
55
		'nav',
56
		'update',
57
		'project',
58
		'toggleprojectstar',
59
		'branch',
60
		'environment',
61
		'abortpipeline',
62
		'pipeline',
63
		'pipelinelog',
64
		'metrics',
65
		'createenvlog',
66
		'createenv',
67
		'getDeployForm',
68
		'doDeploy',
69
		'deploy',
70
		'deploylog',
71
		'getDataTransferForm',
72
		'transfer',
73
		'transferlog',
74
		'snapshots',
75
		'createsnapshot',
76
		'snapshotslog',
77
		'uploadsnapshot',
78
		'getCreateEnvironmentForm',
79
		'getUploadSnapshotForm',
80
		'getPostSnapshotForm',
81
		'getDataTransferRestoreForm',
82
		'getDeleteForm',
83
		'getMoveForm',
84
		'restoresnapshot',
85
		'deletesnapshot',
86
		'movesnapshot',
87
		'postsnapshotsuccess',
88
		'gitRevisions',
89
		'deploySummary',
90
		'startDeploy',
91
		'createproject',
92
		'CreateProjectForm',
93
		'createprojectprogress',
94
		'checkrepoaccess',
95
	);
96
97
	/**
98
	 * URL handlers pretending that we have a deep URL structure.
99
	 */
100
	private static $url_handlers = array(
101
		'project/$Project/environment/$Environment/DeployForm' => 'getDeployForm',
102
		'project/$Project/createsnapshot/DataTransferForm' => 'getDataTransferForm',
103
		'project/$Project/DataTransferForm' => 'getDataTransferForm',
104
		'project/$Project/DataTransferRestoreForm' => 'getDataTransferRestoreForm',
105
		'project/$Project/DeleteForm' => 'getDeleteForm',
106
		'project/$Project/MoveForm' => 'getMoveForm',
107
		'project/$Project/UploadSnapshotForm' => 'getUploadSnapshotForm',
108
		'project/$Project/PostSnapshotForm' => 'getPostSnapshotForm',
109
		'project/$Project/environment/$Environment/metrics' => 'metrics',
110
		'project/$Project/environment/$Environment/pipeline/$Identifier//$Action/$ID/$OtherID' => 'pipeline',
111
		'project/$Project/environment/$Environment/deploy_summary' => 'deploySummary',
112
		'project/$Project/environment/$Environment/git_revisions' => 'gitRevisions',
113
		'project/$Project/environment/$Environment/start-deploy' => 'startDeploy',
114
		'project/$Project/environment/$Environment/deploy/$Identifier/log' => 'deploylog',
115
		'project/$Project/environment/$Environment/deploy/$Identifier' => 'deploy',
116
		'project/$Project/transfer/$Identifier/log' => 'transferlog',
117
		'project/$Project/transfer/$Identifier' => 'transfer',
118
		'project/$Project/environment/$Environment' => 'environment',
119
		'project/$Project/createenv/$Identifier/log' => 'createenvlog',
120
		'project/$Project/createenv/$Identifier' => 'createenv',
121
		'project/$Project/CreateEnvironmentForm' => 'getCreateEnvironmentForm',
122
		'project/$Project/branch' => 'branch',
123
		'project/$Project/build/$Build' => 'build',
124
		'project/$Project/restoresnapshot/$DataArchiveID' => 'restoresnapshot',
125
		'project/$Project/deletesnapshot/$DataArchiveID' => 'deletesnapshot',
126
		'project/$Project/movesnapshot/$DataArchiveID' => 'movesnapshot',
127
		'project/$Project/update' => 'update',
128
		'project/$Project/snapshots' => 'snapshots',
129
		'project/$Project/createsnapshot' => 'createsnapshot',
130
		'project/$Project/uploadsnapshot' => 'uploadsnapshot',
131
		'project/$Project/snapshotslog' => 'snapshotslog',
132
		'project/$Project/postsnapshotsuccess/$DataArchiveID' => 'postsnapshotsuccess',
133
		'project/$Project/star' => 'toggleprojectstar',
134
		'project/$Project/createprojectprogress' => 'createprojectprogress',
135
		'project/$Project/checkrepoaccess' => 'checkrepoaccess',
136
		'project/$Project' => 'project',
137
		'nav/$Project' => 'nav',
138
		'projects' => 'projects',
139
	);
140
141
	/**
142
	 * @var array
143
	 */
144
	protected static $_project_cache = array();
145
146
	/**
147
	 * @var array
148
	 */
149
	private static $support_links = array();
150
151
	/**
152
	 * @var array
153
	 */
154
	private static $platform_specific_strings = array();
155
156
	/**
157
	 * @var array
158
	 */
159
	private static $action_types = array(
160
		self::ACTION_DEPLOY,
161
		self::ACTION_SNAPSHOT
162
	);
163
164
	/**
165
	 * @var DNData
166
	 */
167
	protected $data;
168
169
	/**
170
	 * Include requirements that deploynaut needs, such as javascript.
171
	 */
172
	public static function include_requirements() {
173
174
		// JS should always go to the bottom, otherwise there's the risk that Requirements
175
		// puts them halfway through the page to the nearest <script> tag. We don't want that.
176
		Requirements::set_force_js_to_bottom(true);
177
178
		// todo these should be bundled into the same JS as the others in "static" below.
179
		// We've deliberately not used combined_files as it can mess with some of the JS used
180
		// here and cause sporadic errors.
181
		Requirements::javascript('deploynaut/javascript/jquery.js');
182
		Requirements::javascript('deploynaut/javascript/bootstrap.js');
183
		Requirements::javascript('deploynaut/javascript/q.js');
184
		Requirements::javascript('deploynaut/javascript/tablefilter.js');
185
		Requirements::javascript('deploynaut/javascript/deploynaut.js');
186
		Requirements::javascript('deploynaut/javascript/react-with-addons.js');
187
		Requirements::javascript('deploynaut/javascript/bootstrap.file-input.js');
188
		Requirements::javascript('deploynaut/thirdparty/select2/dist/js/select2.min.js');
189
		Requirements::javascript('deploynaut/javascript/material.js');
190
191
		// Load the buildable dependencies only if not loaded centrally.
192
		if (!is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'static')) {
193
			if (\Director::isDev()) {
194
				\Requirements::javascript('deploynaut/static/bundle-debug.js');
195
			} else {
196
				\Requirements::javascript('deploynaut/static/bundle.js');
197
			}
198
		}
199
200
		Requirements::css('deploynaut/static/style.css');
201
	}
202
203
	/**
204
	 * Check for feature flags:
205
	 * - FLAG_SNAPSHOTS_ENABLED: set to true to enable globally
206
	 * - FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS: set to semicolon-separated list of email addresses of allowed users.
207
	 *
208
	 * @return boolean
209
	 */
210
	public static function FlagSnapshotsEnabled() {
211
		if(defined('FLAG_SNAPSHOTS_ENABLED') && FLAG_SNAPSHOTS_ENABLED) {
212
			return true;
213
		}
214
		if(defined('FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS') && FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS) {
215
			$allowedMembers = explode(';', FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS);
216
			$member = Member::currentUser();
217
			if($allowedMembers && $member && in_array($member->Email, $allowedMembers)) {
218
				return true;
219
			}
220
		}
221
		return false;
222
	}
223
224
	/**
225
	 * @return ArrayList
226
	 */
227
	public static function get_support_links() {
228
		$supportLinks = self::config()->support_links;
229
		if($supportLinks) {
230
			return new ArrayList($supportLinks);
231
		}
232
	}
233
234
	/**
235
	 * @return array
236
	 */
237
	public static function get_template_global_variables() {
238
		return array(
239
			'RedisUnavailable' => 'RedisUnavailable',
240
			'RedisWorkersCount' => 'RedisWorkersCount',
241
			'SidebarLinks' => 'SidebarLinks',
242
			"SupportLinks" => 'get_support_links'
243
		);
244
	}
245
246
	/**
247
	 */
248
	public function init() {
249
		parent::init();
250
251
		if(!Member::currentUser() && !Session::get('AutoLoginHash')) {
252
			return Security::permissionFailure();
253
		}
254
255
		// Block framework jquery
256
		Requirements::block(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
257
258
		self::include_requirements();
259
	}
260
261
	/**
262
	 * @return string
263
	 */
264
	public function Link() {
265
		return "naut/";
266
	}
267
268
	/**
269
	 * Actions
270
	 *
271
	 * @param SS_HTTPRequest $request
272
	 * @return \SS_HTTPResponse
273
	 */
274
	public function index(SS_HTTPRequest $request) {
275
		return $this->redirect($this->Link() . 'projects/');
276
	}
277
278
	/**
279
	 * Action
280
	 *
281
	 * @param SS_HTTPRequest $request
282
	 * @return string - HTML
283
	 */
284
	public function projects(SS_HTTPRequest $request) {
285
		// Performs canView permission check by limiting visible projects in DNProjectsList() call.
286
		return $this->customise(array(
287
			'Title' => 'Projects',
288
		))->render();
289
	}
290
291
	/**
292
	 * @param SS_HTTPRequest $request
293
	 * @return HTMLText
294
	 */
295
	public function nav(SS_HTTPRequest $request) {
296
		return $this->renderWith('Nav');
297
	}
298
299
	/**
300
	 * Return a link to the navigation template used for AJAX requests.
301
	 * @return string
302
	 */
303
	public function NavLink() {
304
		$currentProject = $this->getCurrentProject();
305
		$projectName = $currentProject ? $currentProject->Name : null;
306
		return Controller::join_links(Director::absoluteBaseURL(), 'naut', 'nav', $projectName);
307
	}
308
309
	/**
310
	 * Action
311
	 *
312
	 * @param SS_HTTPRequest $request
313
	 * @return SS_HTTPResponse - HTML
314
	 */
315
	public function snapshots(SS_HTTPRequest $request) {
316
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
317
		return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots');
318
	}
319
320
	/**
321
	 * Action
322
	 *
323
	 * @param SS_HTTPRequest $request
324
	 * @return string - HTML
325
	 */
326 View Code Duplication
	public function createsnapshot(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
327
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
328
329
		// Performs canView permission check by limiting visible projects
330
		$project = $this->getCurrentProject();
331
		if(!$project) {
332
			return $this->project404Response();
333
		}
334
335
		if(!$project->canBackup()) {
336
			return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
337
		}
338
339
		return $this->customise(array(
340
			'Title' => 'Create Data Snapshot',
341
			'SnapshotsSection' => 1,
342
			'DataTransferForm' => $this->getDataTransferForm($request)
343
		))->render();
344
	}
345
346
	/**
347
	 * Action
348
	 *
349
	 * @param SS_HTTPRequest $request
350
	 * @return string - HTML
351
	 */
352 View Code Duplication
	public function uploadsnapshot(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
353
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
354
355
		// Performs canView permission check by limiting visible projects
356
		$project = $this->getCurrentProject();
357
		if(!$project) {
358
			return $this->project404Response();
359
		}
360
361
		if(!$project->canUploadArchive()) {
362
			return new SS_HTTPResponse("Not allowed to upload", 401);
363
		}
364
365
		return $this->customise(array(
366
			'SnapshotsSection' => 1,
367
			'UploadSnapshotForm' => $this->getUploadSnapshotForm($request),
368
			'PostSnapshotForm' => $this->getPostSnapshotForm($request)
369
		))->render();
370
	}
371
372
	/**
373
	 * Return the upload limit for snapshot uploads
374
	 * @return string
375
	 */
376
	public function UploadLimit() {
377
		return File::format_size(min(
378
			File::ini2bytes(ini_get('upload_max_filesize')),
379
			File::ini2bytes(ini_get('post_max_size'))
380
		));
381
	}
382
383
	/**
384
	 * Construct the upload form.
385
	 *
386
	 * @param SS_HTTPRequest $request
387
	 * @return Form
388
	 */
389
	public function getUploadSnapshotForm(SS_HTTPRequest $request) {
390
		// Performs canView permission check by limiting visible projects
391
		$project = $this->getCurrentProject();
392
		if(!$project) {
393
			return $this->project404Response();
394
		}
395
396
		if(!$project->canUploadArchive()) {
397
			return new SS_HTTPResponse("Not allowed to upload", 401);
398
		}
399
400
		// Framing an environment as a "group of people with download access"
401
		// makes more sense to the user here, while still allowing us to enforce
402
		// environment specific restrictions on downloading the file later on.
403
		$envs = $project->DNEnvironmentList()->filterByCallback(function($item) {
404
			return $item->canUploadArchive();
405
		});
406
		$envsMap = array();
407
		foreach($envs as $env) {
408
			$envsMap[$env->ID] = $env->Name;
409
		}
410
411
		$maxSize = min(File::ini2bytes(ini_get('upload_max_filesize')), File::ini2bytes(ini_get('post_max_size')));
412
		$fileField = DataArchiveFileField::create('ArchiveFile', 'File');
413
		$fileField->getValidator()->setAllowedExtensions(array('sspak'));
414
		$fileField->getValidator()->setAllowedMaxFileSize(array('*' => $maxSize));
415
416
		$form = Form::create(
417
			$this,
418
			'UploadSnapshotForm',
419
			FieldList::create(
420
				$fileField,
421
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
422
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
423
					->setEmptyString('Select an environment')
424
			),
425
			FieldList::create(
426
				FormAction::create('doUploadSnapshot', 'Upload File')
427
					->addExtraClass('btn')
428
			),
429
			RequiredFields::create('ArchiveFile')
430
		);
431
432
		$form->disableSecurityToken();
433
		$form->addExtraClass('fields-wide');
434
		// Tweak the action so it plays well with our fake URL structure.
435
		$form->setFormAction($project->Link() . '/UploadSnapshotForm');
436
437
		return $form;
438
	}
439
440
	/**
441
	 * @param array $data
442
	 * @param Form $form
443
	 *
444
	 * @return bool|HTMLText|SS_HTTPResponse
445
	 */
446
	public function doUploadSnapshot($data, Form $form) {
447
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
448
449
		// Performs canView permission check by limiting visible projects
450
		$project = $this->getCurrentProject();
451
		if(!$project) {
452
			return $this->project404Response();
453
		}
454
455
		$environment = $project->DNEnvironmentList()->byId($data['EnvironmentID']);
456
		if(!$environment || !$environment->canUploadArchive()) {
457
			throw new LogicException('Invalid environment');
458
		}
459
460
		$this->validateSnapshotMode($data['Mode']);
461
462
		$dataArchive = DNDataArchive::create(array(
463
			'AuthorID' => Member::currentUserID(),
464
			'EnvironmentID' => $data['EnvironmentID'],
465
			'IsManualUpload' => true,
466
		));
467
		// needs an ID and transfer to determine upload path
468
		$dataArchive->write();
469
		$dataTransfer = DNDataTransfer::create(array(
470
			'AuthorID' => Member::currentUserID(),
471
			'Mode' => $data['Mode'],
472
			'Origin' => 'ManualUpload',
473
			'EnvironmentID' => $data['EnvironmentID']
474
		));
475
		$dataTransfer->write();
476
		$dataArchive->DataTransfers()->add($dataTransfer);
477
		$form->saveInto($dataArchive);
478
		$dataArchive->write();
479
		$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
480
481
		$cleanupFn = function() use($workingDir, $dataTransfer, $dataArchive) {
482
			$process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
483
			$process->run();
484
			$dataTransfer->delete();
485
			$dataArchive->delete();
486
		};
487
488
		// extract the sspak contents so we can inspect them
489
		try {
490
			$dataArchive->extractArchive($workingDir);
491
		} catch(Exception $e) {
492
			$cleanupFn();
493
			$form->sessionMessage(
494
				'There was a problem trying to open your snapshot for processing. Please try uploading again',
495
				'bad'
496
			);
497
			return $this->redirectBack();
498
		}
499
500
		// validate that the sspak contents match the declared contents
501
		$result = $dataArchive->validateArchiveContents();
502
		if(!$result->valid()) {
503
			$cleanupFn();
504
			$form->sessionMessage($result->message(), 'bad');
505
			return $this->redirectBack();
506
		}
507
508
		// fix file permissions of extracted sspak files then re-build the sspak
509
		try {
510
			$dataArchive->fixArchivePermissions($workingDir);
511
			$dataArchive->setArchiveFromFiles($workingDir);
512
		} catch(Exception $e) {
513
			$cleanupFn();
514
			$form->sessionMessage(
515
				'There was a problem processing your snapshot. Please try uploading again',
516
				'bad'
517
			);
518
			return $this->redirectBack();
519
		}
520
521
		// cleanup any extracted sspak contents lying around
522
		$process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
523
		$process->run();
524
525
		return $this->customise(array(
526
			'Project' => $project,
527
			'CurrentProject' => $project,
528
			'SnapshotsSection' => 1,
529
			'DataArchive' => $dataArchive,
530
			'DataTransferRestoreForm' => $this->getDataTransferRestoreForm($this->request, $dataArchive),
531
			'BackURL' => $project->Link('snapshots')
532
		))->renderWith(array('DNRoot_uploadsnapshot', 'DNRoot'));
533
	}
534
535
	/**
536
	 * @param SS_HTTPRequest $request
537
	 * @return Form
538
	 */
539
	public function getPostSnapshotForm(SS_HTTPRequest $request) {
540
		// Performs canView permission check by limiting visible projects
541
		$project = $this->getCurrentProject();
542
		if(!$project) {
543
			return $this->project404Response();
544
		}
545
546
		if(!$project->canUploadArchive()) {
547
			return new SS_HTTPResponse("Not allowed to upload", 401);
548
		}
549
550
		// Framing an environment as a "group of people with download access"
551
		// makes more sense to the user here, while still allowing us to enforce
552
		// environment specific restrictions on downloading the file later on.
553
		$envs = $project->DNEnvironmentList()->filterByCallback(function($item) {
554
			return $item->canUploadArchive();
555
		});
556
		$envsMap = array();
557
		foreach($envs as $env) {
558
			$envsMap[$env->ID] = $env->Name;
559
		}
560
561
		$form = Form::create(
562
			$this,
563
			'PostSnapshotForm',
564
			FieldList::create(
565
				DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
566
				DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
567
					->setEmptyString('Select an environment')
568
			),
569
			FieldList::create(
570
				FormAction::create('doPostSnapshot', 'Submit request')
571
					->addExtraClass('btn')
572
			),
573
			RequiredFields::create('File')
574
		);
575
576
		$form->disableSecurityToken();
577
		$form->addExtraClass('fields-wide');
578
		// Tweak the action so it plays well with our fake URL structure.
579
		$form->setFormAction($project->Link() . '/PostSnapshotForm');
580
581
		return $form;
582
	}
583
584
	/**
585
	 * @param array $data
586
	 * @param Form $form
587
	 *
588
	 * @return SS_HTTPResponse
589
	 */
590
	public function doPostSnapshot($data, $form) {
591
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
592
593
		$project = $this->getCurrentProject();
594
		if(!$project) {
595
			return $this->project404Response();
596
		}
597
598
		$validEnvs = $project->DNEnvironmentList()->filterByCallback(function($item) {
599
				return $item->canUploadArchive();
600
		});
601
602
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
603
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
604
		if(!$environment) {
605
			throw new LogicException('Invalid environment');
606
		}
607
608
		$dataArchive = DNDataArchive::create(array(
609
			'UploadToken' => DNDataArchive::generate_upload_token(),
610
		));
611
		$form->saveInto($dataArchive);
612
		$dataArchive->write();
613
614
		return $this->redirect(Controller::join_links(
615
			$project->Link(),
616
			'postsnapshotsuccess',
617
			$dataArchive->ID
618
		));
619
	}
620
621
	/**
622
	 * Action
623
	 *
624
	 * @param SS_HTTPRequest $request
625
	 * @return SS_HTTPResponse - HTML
626
	 */
627
	public function snapshotslog(SS_HTTPRequest $request) {
628
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
629
		return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots Log');
630
	}
631
632
	/**
633
	 * @param SS_HTTPRequest $request
634
	 * @return SS_HTTPResponse|string
635
	 * @throws SS_HTTPResponse_Exception
636
	 */
637
	public function postsnapshotsuccess(SS_HTTPRequest $request) {
638
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
639
640
		// Performs canView permission check by limiting visible projects
641
		$project = $this->getCurrentProject();
642
		if(!$project) {
643
			return $this->project404Response();
644
		}
645
646
		if(!$project->canUploadArchive()) {
647
			return new SS_HTTPResponse("Not allowed to upload", 401);
648
		}
649
650
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
651
		if(!$dataArchive) {
652
			return new SS_HTTPResponse("Archive not found.", 404);
653
		}
654
655
		if(!$dataArchive->canRestore()) {
656
			throw new SS_HTTPResponse_Exception('Not allowed to restore archive', 403);
657
		}
658
659
		return $this->render(array(
660
				'Title' => 'How to send us your Data Snapshot by post',
661
				'DataArchive' => $dataArchive,
662
				'Address' => Config::inst()->get('Deploynaut', 'snapshot_post_address'),
663
				'BackURL' => $project->Link(),
664
			));
665
	}
666
667
	/**
668
	 * @param SS_HTTPRequest $request
669
	 * @return \SS_HTTPResponse
670
	 */
671
	public function project(SS_HTTPRequest $request) {
672
		return $this->getCustomisedViewSection('ProjectOverview', '', array('IsAdmin' => Permission::check('ADMIN')));
673
	}
674
675
	/**
676
	 * This action will star / unstar a project for the current member
677
	 *
678
	 * @param SS_HTTPRequest $request
679
	 *
680
	 * @return SS_HTTPResponse
681
	 */
682
	public function toggleprojectstar(SS_HTTPRequest $request) {
683
		$project = $this->getCurrentProject();
684
		if(!$project) {
685
			return $this->project404Response();
686
		}
687
688
		$member = Member::currentUser();
689
		if($member === null) {
690
			return $this->project404Response();
691
		}
692
		$favProject = $member->StarredProjects()
693
			->filter('DNProjectID', $project->ID)
694
			->first();
695
696
		if($favProject) {
697
			$member->StarredProjects()->remove($favProject);
698
		} else {
699
			$member->StarredProjects()->add($project);
700
		}
701
		return $this->redirectBack();
702
	}
703
704
	/**
705
	 * @param SS_HTTPRequest $request
706
	 * @return \SS_HTTPResponse
707
	 */
708
	public function branch(SS_HTTPRequest $request) {
709
		$project = $this->getCurrentProject();
710
		if(!$project) {
711
			return $this->project404Response();
712
		}
713
714
		$branchName = $request->getVar('name');
715
		$branch = $project->DNBranchList()->byName($branchName);
716
		if(!$branch) {
717
			return new SS_HTTPResponse("Branch '" . Convert::raw2xml($branchName) . "' not found.", 404);
718
		}
719
720
		return $this->render(array(
721
			'CurrentBranch' => $branch,
722
		));
723
	}
724
725
	/**
726
	 * @param SS_HTTPRequest $request
727
	 * @return \SS_HTTPResponse
728
	 */
729
	public function environment(SS_HTTPRequest $request) {
730
		// Performs canView permission check by limiting visible projects
731
		$project = $this->getCurrentProject();
732
		if(!$project) {
733
			return $this->project404Response();
734
		}
735
736
		// Performs canView permission check by limiting visible projects
737
		$env = $this->getCurrentEnvironment($project);
738
		if(!$env) {
739
			return $this->environment404Response();
740
		}
741
742
		return $this->render(array(
743
			'DNEnvironmentList' => $this->getCurrentProject()->DNEnvironmentList(),
744
			'FlagSnapshotsEnabled' => $this->FlagSnapshotsEnabled(),
745
		));
746
	}
747
748
749
	/**
750
	 * Initiate a pipeline dry run
751
	 *
752
	 * @param array $data
753
	 * @param DeployForm $form
754
	 *
755
	 * @return SS_HTTPResponse
756
	 */
757
	public function doDryRun($data, DeployForm $form) {
758
		return $this->beginPipeline($data, $form, true);
759
	}
760
761
	/**
762
	 * Initiate a pipeline
763
	 *
764
	 * @param array $data
765
	 * @param DeployForm $form
766
	 * @return \SS_HTTPResponse
767
	 */
768
	public function startPipeline($data, $form) {
769
		return $this->beginPipeline($data, $form);
770
	}
771
772
	/**
773
	 * Start a pipeline
774
	 *
775
	 * @param array $data
776
	 * @param DeployForm $form
777
	 * @param bool $isDryRun
778
	 * @return \SS_HTTPResponse
779
	 */
780
	protected function beginPipeline($data, DeployForm $form, $isDryRun = false) {
781
		$buildName = $form->getSelectedBuild($data);
782
783
		// Performs canView permission check by limiting visible projects
784
		$project = $this->getCurrentProject();
785
		if(!$project) {
786
			return $this->project404Response();
787
		}
788
789
		// Performs canView permission check by limiting visible projects
790
		$environment = $this->getCurrentEnvironment($project);
791
		if(!$environment) {
792
			return $this->environment404Response();
793
		}
794
795
		if(!$environment->DryRunEnabled && $isDryRun) {
796
			return new SS_HTTPResponse("Dry-run for pipelines is not enabled for this environment", 404);
797
		}
798
799
		// Initiate the pipeline
800
		$sha = $project->DNBuildList()->byName($buildName);
801
		$pipeline = Pipeline::create();
802
		$pipeline->DryRun = $isDryRun;
803
		$pipeline->EnvironmentID = $environment->ID;
804
		$pipeline->AuthorID = Member::currentUserID();
805
		$pipeline->SHA = $sha->FullName();
806
		// Record build at time of execution
807
		if($currentBuild = $environment->CurrentBuild()) {
808
			$pipeline->PreviousDeploymentID = $currentBuild->ID;
809
		}
810
		$pipeline->start(); // start() will call write(), so no need to do it here as well.
811
		return $this->redirect($environment->Link());
812
	}
813
814
	/**
815
	 * @param SS_HTTPRequest $request
816
	 *
817
	 * @return SS_HTTPResponse
818
	 * @throws SS_HTTPResponse_Exception
819
	 */
820
	public function pipeline(SS_HTTPRequest $request) {
821
		$params = $request->params();
822
		$pipeline = Pipeline::get()->byID($params['Identifier']);
823
824
		if(!$pipeline || !$pipeline->ID || !$pipeline->Environment()) {
825
			throw new SS_HTTPResponse_Exception('Pipeline not found', 404);
826
		}
827
		if(!$pipeline->Environment()->canView()) {
828
			return Security::permissionFailure();
829
		}
830
831
		$environment = $pipeline->Environment();
832
		$project = $pipeline->Environment()->Project();
833
834
		if($environment->Name != $params['Environment']) {
835
			throw new LogicException("Environment in URL doesn't match this pipeline");
836
		}
837
		if($project->Name != $params['Project']) {
838
			throw new LogicException("Project in URL doesn't match this pipeline");
839
		}
840
841
		// Delegate to sub-requesthandler
842
		return PipelineController::create($this, $pipeline);
843
	}
844
845
	/**
846
	 * Shows the creation log.
847
	 *
848
	 * @param SS_HTTPRequest $request
849
	 * @return string
850
	 */
851
	public function createenv(SS_HTTPRequest $request) {
852
		$params = $request->params();
853
		if($params['Identifier']) {
854
			$record = DNCreateEnvironment::get()->byId($params['Identifier']);
855
856
			if(!$record || !$record->ID) {
857
				throw new SS_HTTPResponse_Exception('Create environment not found', 404);
858
			}
859
			if(!$record->canView()) {
860
				return Security::permissionFailure();
861
			}
862
863
			$project = $this->getCurrentProject();
864
			if(!$project) {
865
				return $this->project404Response();
866
			}
867
868
			if($project->Name != $params['Project']) {
869
				throw new LogicException("Project in URL doesn't match this creation");
870
			}
871
872
			return $this->render(array(
873
				'CreateEnvironment' => $record,
874
			));
875
		}
876
		return $this->render(array('CurrentTitle' => 'Create an environment'));
877
	}
878
879
880 View Code Duplication
	public function createenvlog(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
881
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
882
883
		$params = $request->params();
884
		$env = DNCreateEnvironment::get()->byId($params['Identifier']);
885
886
		if(!$env || !$env->ID) {
887
			throw new SS_HTTPResponse_Exception('Log not found', 404);
888
		}
889
		if(!$env->canView()) {
890
			return Security::permissionFailure();
891
		}
892
893
		$project = $env->Project();
894
895
		if($project->Name != $params['Project']) {
896
			throw new LogicException("Project in URL doesn't match this deploy");
897
		}
898
899
		$log = $env->log();
900
		if($log->exists()) {
901
			$content = $log->content();
902
		} else {
903
			$content = 'Waiting for action to start';
904
		}
905
906
		return $this->sendResponse($env->ResqueStatus(), $content);
907
	}
908
909
	/**
910
	 * @param SS_HTTPRequest $request
911
	 * @return Form
912
	 */
913
	public function getCreateEnvironmentForm(SS_HTTPRequest $request) {
914
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
915
916
		$project = $this->getCurrentProject();
917
		if(!$project) {
918
			return $this->project404Response();
919
		}
920
921
		$envType = $project->AllowedEnvironmentType;
0 ignored issues
show
Documentation introduced by
The property AllowedEnvironmentType does not exist on object<DNProject>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
922
		if(!$envType || !class_exists($envType)) {
923
			return null;
924
		}
925
926
		$backend = Injector::inst()->get($envType);
927
		if(!($backend instanceof EnvironmentCreateBackend)) {
928
			// Only allow this for supported backends.
929
			return null;
930
		}
931
932
		$fields = $backend->getCreateEnvironmentFields($project);
933
		if(!$fields) return null;
934
935
		if(!$project->canCreateEnvironments()) {
936
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
937
		}
938
939
		$form = Form::create(
940
			$this,
941
			'CreateEnvironmentForm',
942
			$fields,
943
			FieldList::create(
944
				FormAction::create('doCreateEnvironment', 'Create')
945
					->addExtraClass('btn')
946
			),
947
			$backend->getCreateEnvironmentValidator()
948
		);
949
950
		// Tweak the action so it plays well with our fake URL structure.
951
		$form->setFormAction($project->Link() . '/CreateEnvironmentForm');
952
953
		return $form;
954
	}
955
956
	/**
957
	 * @param array $data
958
	 * @param Form $form
959
	 *
960
	 * @return bool|HTMLText|SS_HTTPResponse
961
	 */
962
	public function doCreateEnvironment($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
963
		$this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
964
965
		$project = $this->getCurrentProject();
966
		if(!$project) {
967
			return $this->project404Response();
968
		}
969
970
		if(!$project->canCreateEnvironments()) {
971
			return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
972
		}
973
974
		// Set the environment type so we know what we're creating.
975
		$data['EnvironmentType'] = $project->AllowedEnvironmentType;
0 ignored issues
show
Documentation introduced by
The property AllowedEnvironmentType does not exist on object<DNProject>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
976
977
		$job = DNCreateEnvironment::create();
978
979
		$job->Data = serialize($data);
980
		$job->ProjectID = $project->ID;
981
		$job->write();
982
		$job->start();
983
984
		return $this->redirect($project->Link('createenv') . '/' . $job->ID);
985
	}
986
987
	/**
988
	 *
989
	 * @param SS_HTTPRequest $request
990
	 * @return \SS_HTTPResponse
991
	 */
992
	public function metrics(SS_HTTPRequest $request) {
993
		// Performs canView permission check by limiting visible projects
994
		$project = $this->getCurrentProject();
995
		if(!$project) {
996
			return $this->project404Response();
997
		}
998
999
		// Performs canView permission check by limiting visible projects
1000
		$env = $this->getCurrentEnvironment($project);
1001
		if(!$env) {
1002
			return $this->environment404Response();
1003
		}
1004
1005
		return $this->render();
1006
	}
1007
1008
	/**
1009
	 * Get the DNData object.
1010
	 *
1011
	 * @return DNData
1012
	 */
1013
	public function DNData() {
1014
		return DNData::inst();
1015
	}
1016
1017
	/**
1018
	 * Provide a list of all projects.
1019
	 *
1020
	 * @return SS_List
1021
	 */
1022
	public function DNProjectList() {
1023
		$memberId = Member::currentUserID();
1024
		if(!$memberId) {
1025
			return new ArrayList();
1026
		}
1027
1028
		if(Permission::check('ADMIN')) {
1029
			return DNProject::get();
1030
		}
1031
1032
		return Member::get()->filter('ID', $memberId)
1033
			->relation('Groups')
1034
			->relation('Projects');
1035
	}
1036
1037
	/**
1038
	 * @return ArrayList
1039
	 */
1040
	public function getPlatformSpecificStrings() {
1041
		$strings = $this->config()->platform_specific_strings;
1042
		if ($strings) {
1043
			return new ArrayList($strings);
1044
		}
1045
	}
1046
1047
	/**
1048
	 * Provide a list of all starred projects for the currently logged in member
1049
	 *
1050
	 * @return SS_List
1051
	 */
1052
	public function getStarredProjects() {
1053
		$member = Member::currentUser();
1054
		if($member === null) {
1055
			return new ArrayList();
1056
		}
1057
1058
		$favProjects = $member->StarredProjects();
1059
1060
		$list = new ArrayList();
1061
		foreach($favProjects as $project) {
1062
			if($project->canView($member)) {
1063
				$list->add($project);
1064
			}
1065
		}
1066
		return $list;
1067
	}
1068
1069
	/**
1070
	 * Returns top level navigation of projects.
1071
	 *
1072
	 * @param int $limit
1073
	 *
1074
	 * @return ArrayList
1075
	 */
1076
	public function Navigation($limit = 5) {
1077
		$navigation = new ArrayList();
1078
1079
		$currentProject = $this->getCurrentProject();
1080
1081
		$projects = $this->getStarredProjects();
1082
		if($projects->count() < 1) {
1083
			$projects = $this->DNProjectList();
1084
		} else {
1085
			$limit = -1;
1086
		}
1087
1088
		if($projects->count() > 0) {
1089
			$activeProject = false;
1090
1091
			if($limit > 0) {
1092
				$limitedProjects = $projects->limit($limit);
1093
			} else {
1094
				$limitedProjects = $projects;
1095
			}
1096
1097
			foreach($limitedProjects as $project) {
1098
				$isActive = $currentProject && $currentProject->ID == $project->ID;
1099
				if($isActive) {
1100
					$activeProject = true;
1101
				}
1102
1103
				$navigation->push(array(
1104
					'Project' => $project,
1105
					'IsActive' => $currentProject && $currentProject->ID == $project->ID,
1106
				));
1107
			}
1108
1109
			// Ensure the current project is in the list
1110
			if(!$activeProject && $currentProject) {
1111
				$navigation->unshift(array(
1112
					'Project' => $currentProject,
1113
					'IsActive' => true,
1114
				));
1115
				if($limit > 0 && $navigation->count() > $limit) {
1116
					$navigation->pop();
1117
				}
1118
			}
1119
		}
1120
1121
		return $navigation;
1122
	}
1123
1124
	/**
1125
	 * Construct the deployment form
1126
	 *
1127
	 * @return Form
1128
	 */
1129
	public function getDeployForm($request = null) {
1130
1131
		// Performs canView permission check by limiting visible projects
1132
		$project = $this->getCurrentProject();
1133
		if(!$project) {
1134
			return $this->project404Response();
1135
		}
1136
1137
		// Performs canView permission check by limiting visible projects
1138
		$environment = $this->getCurrentEnvironment($project);
1139
		if(!$environment) {
1140
			return $this->environment404Response();
1141
		}
1142
1143
		if(!$environment->canDeploy()) {
1144
			return new SS_HTTPResponse("Not allowed to deploy", 401);
1145
		}
1146
1147
		// Generate the form
1148
		$form = new DeployForm($this, 'DeployForm', $environment, $project);
1149
1150
		// If this is an ajax request we don't want to submit the form - we just want to retrieve the markup.
1151
		if(
1152
			$request &&
1153
			!$request->requestVar('action_showDeploySummary') &&
1154
			$this->getRequest()->isAjax() &&
1155
			$this->getRequest()->isGET()
1156
		) {
1157
			// We can just use the URL we're accessing
1158
			$form->setFormAction($this->getRequest()->getURL());
1159
1160
			$body = json_encode(array('Content' => $form->forAjaxTemplate()->forTemplate()));
1161
			$this->getResponse()->addHeader('Content-Type', 'application/json');
1162
			$this->getResponse()->setBody($body);
1163
			return $body;
1164
		}
1165
1166
		$form->setFormAction($this->getRequest()->getURL() . '/DeployForm');
1167
		return $form;
1168
	}
1169
1170
	/**
1171
	 * @param SS_HTTPRequest $request
1172
	 *
1173
	 * @return SS_HTTPResponse|string
1174
	 */
1175
	public function gitRevisions(SS_HTTPRequest $request) {
1176
1177
		// Performs canView permission check by limiting visible projects
1178
		$project = $this->getCurrentProject();
1179
		if(!$project) {
1180
			return $this->project404Response();
1181
		}
1182
1183
		// Performs canView permission check by limiting visible projects
1184
		$env = $this->getCurrentEnvironment($project);
1185
		if(!$env) {
1186
			return $this->environment404Response();
1187
		}
1188
1189
		// For now only permit advanced options on one environment type, because we hacked the "full-deploy"
1190
		// checkbox in. Other environments such as the fast or capistrano one wouldn't know what to do with it.
1191
		if(get_class($env) === 'RainforestEnvironment') {
1192
			$advanced = Permission::check('DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS') ? 'true' : 'false';
1193
		} else {
1194
			$advanced = 'false';
1195
		}
1196
1197
		$tabs = array();
1198
		$id = 0;
1199
		$data = array(
1200
			'id' => ++$id,
1201
			'name' => 'Deploy the latest version of a branch',
1202
			'field_type' => 'dropdown',
1203
			'field_label' => 'Choose a branch',
1204
			'field_id' => 'branch',
1205
			'field_data' => array(),
1206
			'advanced_opts' => $advanced
1207
		);
1208
		foreach($project->DNBranchList() as $branch) {
1209
			$sha = $branch->SHA();
1210
			$name = $branch->Name();
1211
			$branchValue = sprintf("%s (%s, %s old)",
1212
				$name,
1213
				substr($sha, 0, 8),
1214
				$branch->LastUpdated()->TimeDiff()
1215
			);
1216
			$data['field_data'][] = array(
1217
				'id' => $sha,
1218
				'text' => $branchValue
1219
			);
1220
		}
1221
		$tabs[] = $data;
1222
1223
		$data = array(
1224
			'id' => ++$id,
1225
			'name' => 'Deploy a tagged release',
1226
			'field_type' => 'dropdown',
1227
			'field_label' => 'Choose a tag',
1228
			'field_id' => 'tag',
1229
			'field_data' => array(),
1230
			'advanced_opts' => $advanced
1231
		);
1232
1233
		foreach($project->DNTagList()->setLimit(null) as $tag) {
1234
			$name = $tag->Name();
1235
			$data['field_data'][] = array(
1236
				'id' => $tag->SHA(),
1237
				'text' => sprintf("%s", $name)
1238
			);
1239
		}
1240
1241
		// show newest tags first.
1242
		$data['field_data'] = array_reverse($data['field_data']);
1243
1244
		$tabs[] = $data;
1245
1246
		// Past deployments
1247
		$data = array(
1248
			'id' => ++$id,
1249
			'name' => 'Redeploy a release that was previously deployed (to any environment)',
1250
			'field_type' => 'dropdown',
1251
			'field_label' => 'Choose a previously deployed release',
1252
			'field_id' => 'release',
1253
			'field_data' => array(),
1254
			'advanced_opts' => $advanced
1255
		);
1256
		// We are aiming at the format:
1257
		// [{text: 'optgroup text', children: [{id: '<sha>', text: '<inner text>'}]}]
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% 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...
1258
		$redeploy = array();
1259
		foreach($project->DNEnvironmentList() as $dnEnvironment) {
1260
			$envName = $dnEnvironment->Name;
1261
			$perEnvDeploys = array();
1262
1263
			foreach($dnEnvironment->DeployHistory() as $deploy) {
1264
				$sha = $deploy->SHA;
1265
1266
				// Check if exists to make sure the newest deployment date is used.
1267
				if(!isset($perEnvDeploys[$sha])) {
1268
					$pastValue = sprintf("%s (deployed %s)",
1269
						substr($sha, 0, 8),
1270
						$deploy->obj('LastEdited')->Ago()
1271
					);
1272
					$perEnvDeploys[$sha] = array(
1273
						'id' => $sha,
1274
						'text' => $pastValue
1275
					);
1276
				}
1277
			}
1278
1279
			if(!empty($perEnvDeploys)) {
1280
				$redeploy[$envName] = array_values($perEnvDeploys);
1281
			}
1282
		}
1283
		// Convert the array to the frontend format (i.e. keyed to regular array)
1284
		foreach($redeploy as $env => $descr) {
1285
			$data['field_data'][] = array('text'=>$env, 'children'=>$descr);
1286
		}
1287
		$tabs[] = $data;
1288
1289
		$data = array(
1290
			'id' => ++$id,
1291
			'name' => 'Deploy a specific SHA',
1292
			'field_type' => 'textfield',
1293
			'field_label' => 'Choose a SHA',
1294
			'field_id' => 'SHA',
1295
			'field_data' => array(),
1296
			'advanced_opts' => $advanced
1297
		);
1298
		$tabs[] = $data;
1299
1300
		// get the last time git fetch was run
1301
		$lastFetched = 'never';
1302
		$fetch = DNGitFetch::get()
1303
			->filter('ProjectID', $project->ID)
1304
			->sort('LastEdited', 'DESC')
1305
			->first();
1306
		if($fetch) {
1307
			$lastFetched = $fetch->dbObject('LastEdited')->Ago();
1308
		}
1309
1310
		$data = array(
1311
			'Tabs' => $tabs,
1312
			'last_fetched' => $lastFetched
1313
		);
1314
1315
		return json_encode($data, JSON_PRETTY_PRINT);
1316
	}
1317
1318
	/**
1319
	 * Check and regenerate a global CSRF token
1320
	 *
1321
	 * @param SS_HTTPRequest $request
1322
	 * @param bool $resetToken
1323
	 *
1324
	 * @return bool
1325
	 */
1326
	protected function checkCsrfToken(SS_HTTPRequest $request, $resetToken = true) {
1327
		$token = SecurityToken::inst();
1328
1329
		// Ensure the submitted token has a value
1330
		$submittedToken = $request->postVar('SecurityID');
1331
		if(!$submittedToken) {
1332
			return false;
1333
		}
1334
1335
		// Do the actual check.
1336
		$check = $token->check($submittedToken);
1337
1338
		// Reset the token after we've checked the existing token
1339
		if($resetToken) {
1340
			$token->reset();
1341
		}
1342
1343
		// Return whether the token was correct or not
1344
		return $check;
1345
	}
1346
1347
	/**
1348
	 * @param SS_HTTPRequest $request
1349
	 *
1350
	 * @return string
1351
	 */
1352
	public function deploySummary(SS_HTTPRequest $request) {
1353
1354
		// Performs canView permission check by limiting visible projects
1355
		$project = $this->getCurrentProject();
1356
		if(!$project) {
1357
			return $this->project404Response();
1358
		}
1359
1360
		// Performs canView permission check by limiting visible projects
1361
		$environment = $this->getCurrentEnvironment($project);
1362
		if(!$environment) {
1363
			return $this->environment404Response();
1364
		}
1365
1366
		// Plan the deployment.
1367
		$strategy = $environment->Backend()->planDeploy(
1368
			$environment,
1369
			$request->requestVars()
0 ignored issues
show
Bug introduced by
It seems like $request->requestVars() targeting SS_HTTPRequest::requestVars() can also be of type null; however, DeploymentBackend::planDeploy() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1370
		);
1371
		$data = $strategy->toArray();
1372
1373
		// Add in a URL for comparing from->to code changes. Ensure that we have
1374
		// two proper 40 character SHAs, otherwise we can't show the compare link.
1375
		$interface = $project->getRepositoryInterface();
1376
		if(
1377
			!empty($interface) && !empty($interface->URL)
1378
			&& !empty($data['changes']['Code version']['from'])
1379
			&& strlen($data['changes']['Code version']['from']) == '40'
1380
			&& !empty($data['changes']['Code version']['to'])
1381
			&& strlen($data['changes']['Code version']['to']) == '40'
1382
		) {
1383
			$compareurl = sprintf(
1384
				'%s/compare/%s...%s',
1385
				$interface->URL,
1386
				$data['changes']['Code version']['from'],
1387
				$data['changes']['Code version']['to']
1388
			);
1389
			$data['changes']['Code version']['compareUrl'] = $compareurl;
1390
		}
1391
1392
		// Append json to response
1393
		$token = SecurityToken::inst();
1394
		$data['SecurityID'] = $token->getValue();
1395
1396
		return json_encode($data);
1397
	}
1398
1399
	/**
1400
	 * Deployment form submission handler.
1401
	 *
1402
	 * Initiate a DNDeployment record and redirect to it for status polling
1403
	 *
1404
	 * @param SS_HTTPRequest $request
1405
	 *
1406
	 * @return SS_HTTPResponse
1407
	 * @throws ValidationException
1408
	 * @throws null
1409
	 */
1410
	public function startDeploy(SS_HTTPRequest $request) {
1411
1412
		// Ensure the CSRF Token is correct
1413
		if(!$this->checkCsrfToken($request)) {
1414
			// CSRF token didn't match
1415
			return $this->httpError(400, 'Bad Request');
1416
		}
1417
1418
		// Performs canView permission check by limiting visible projects
1419
		$project = $this->getCurrentProject();
1420
		if(!$project) {
1421
			return $this->project404Response();
1422
		}
1423
1424
		// Performs canView permission check by limiting visible projects
1425
		$environment = $this->getCurrentEnvironment($project);
1426
		if(!$environment) {
1427
			return $this->environment404Response();
1428
		}
1429
1430
		// Initiate the deployment
1431
		// The extension point should pass in: Project, Environment, SelectRelease, buildName
1432
		$this->extend('doDeploy', $project, $environment, $buildName, $data);
1433
1434
		// Start the deployment based on the approved strategy.
1435
		$strategy = new DeploymentStrategy($environment);
1436
		$strategy->fromArray($request->requestVar('strategy'));
1437
		$deployment = $strategy->createDeployment();
1438
		$deployment->start();
1439
1440
		return json_encode(array(
1441
			'url' => Director::absoluteBaseURL() . $deployment->Link()
1442
		), JSON_PRETTY_PRINT);
1443
	}
1444
1445
	/**
1446
	 * Action - Do the actual deploy
1447
	 *
1448
	 * @param SS_HTTPRequest $request
1449
	 *
1450
	 * @return SS_HTTPResponse|string
1451
	 * @throws SS_HTTPResponse_Exception
1452
	 */
1453
	public function deploy(SS_HTTPRequest $request) {
1454
		$params = $request->params();
1455
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1456
1457
		if(!$deployment || !$deployment->ID) {
1458
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1459
		}
1460
		if(!$deployment->canView()) {
1461
			return Security::permissionFailure();
1462
		}
1463
1464
		$environment = $deployment->Environment();
1465
		$project = $environment->Project();
1466
1467
		if($environment->Name != $params['Environment']) {
1468
			throw new LogicException("Environment in URL doesn't match this deploy");
1469
		}
1470
		if($project->Name != $params['Project']) {
1471
			throw new LogicException("Project in URL doesn't match this deploy");
1472
		}
1473
1474
		return $this->render(array(
1475
			'Deployment' => $deployment,
1476
		));
1477
	}
1478
1479
1480
	/**
1481
	 * Action - Get the latest deploy log
1482
	 *
1483
	 * @param SS_HTTPRequest $request
1484
	 *
1485
	 * @return string
1486
	 * @throws SS_HTTPResponse_Exception
1487
	 */
1488
	public function deploylog(SS_HTTPRequest $request) {
1489
		$params = $request->params();
1490
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1491
1492
		if(!$deployment || !$deployment->ID) {
1493
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1494
		}
1495
		if(!$deployment->canView()) {
1496
			return Security::permissionFailure();
1497
		}
1498
1499
		$environment = $deployment->Environment();
1500
		$project = $environment->Project();
1501
1502
		if($environment->Name != $params['Environment']) {
1503
			throw new LogicException("Environment in URL doesn't match this deploy");
1504
		}
1505
		if($project->Name != $params['Project']) {
1506
			throw new LogicException("Project in URL doesn't match this deploy");
1507
		}
1508
1509
		$log = $deployment->log();
1510
		if($log->exists()) {
1511
			$content = $log->content();
1512
		} else {
1513
			$content = 'Waiting for action to start';
1514
		}
1515
1516
		return $this->sendResponse($deployment->ResqueStatus(), $content);
1517
	}
1518
1519
	/**
1520
	 * @param SS_HTTPRequest|null $request
1521
	 *
1522
	 * @return Form
1523
	 */
1524
	public function getDataTransferForm(SS_HTTPRequest $request = null) {
1525
		// Performs canView permission check by limiting visible projects
1526
		$envs = $this->getCurrentProject()->DNEnvironmentList()->filterByCallback(function($item) {
1527
			return $item->canBackup();
1528
		});
1529
1530
		if(!$envs) {
1531
			return $this->environment404Response();
1532
		}
1533
1534
		$form = Form::create(
1535
			$this,
1536
			'DataTransferForm',
1537
			FieldList::create(
1538
				HiddenField::create('Direction', null, 'get'),
1539
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1540
					->setEmptyString('Select an environment'),
1541
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map())
1542
			),
1543
			FieldList::create(
1544
				FormAction::create('doDataTransfer', 'Create')
1545
					->addExtraClass('btn')
1546
			)
1547
		);
1548
		$form->setFormAction($this->getRequest()->getURL() . '/DataTransferForm');
1549
1550
		return $form;
1551
	}
1552
1553
	/**
1554
	 * @param array $data
1555
	 * @param Form $form
1556
	 *
1557
	 * @return SS_HTTPResponse
1558
	 * @throws SS_HTTPResponse_Exception
1559
	 */
1560
	public function doDataTransfer($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1561
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1562
1563
		// Performs canView permission check by limiting visible projects
1564
		$project = $this->getCurrentProject();
1565
		if(!$project) {
1566
			return $this->project404Response();
1567
		}
1568
1569
		$dataArchive = null;
1570
1571
		// Validate direction.
1572
		if($data['Direction'] == 'get') {
1573
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1574
				->filterByCallback(function($item) {
1575
					return $item->canBackup();
1576
				});
1577
		} else if($data['Direction'] == 'push') {
1578
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1579
				->filterByCallback(function($item) {
1580
					return $item->canRestore();
1581
				});
1582
		} else {
1583
			throw new LogicException('Invalid direction');
1584
		}
1585
1586
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1587
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1588
		if(!$environment) {
1589
			throw new LogicException('Invalid environment');
1590
		}
1591
1592
		$this->validateSnapshotMode($data['Mode']);
1593
1594
1595
		// Only 'push' direction is allowed an association with an existing archive.
1596
		if(
1597
			$data['Direction'] == 'push'
1598
			&& isset($data['DataArchiveID'])
1599
			&& is_numeric($data['DataArchiveID'])
1600
		) {
1601
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1602
			if(!$dataArchive) {
1603
				throw new LogicException('Invalid data archive');
1604
			}
1605
1606
			if(!$dataArchive->canDownload()) {
1607
				throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1608
			}
1609
		}
1610
1611
		$transfer = DNDataTransfer::create();
1612
		$transfer->EnvironmentID = $environment->ID;
1613
		$transfer->Direction = $data['Direction'];
1614
		$transfer->Mode = $data['Mode'];
1615
		$transfer->DataArchiveID = $dataArchive ? $dataArchive->ID : null;
1616
		if($data['Direction'] == 'push') {
1617
			$transfer->setBackupBeforePush(!empty($data['BackupBeforePush']));
1618
		}
1619
		$transfer->write();
1620
		$transfer->start();
1621
1622
		return $this->redirect($transfer->Link());
1623
	}
1624
1625
	/**
1626
	 * View into the log for a {@link DNDataTransfer}.
1627
	 *
1628
	 * @param SS_HTTPRequest $request
1629
	 *
1630
	 * @return SS_HTTPResponse|string
1631
	 * @throws SS_HTTPResponse_Exception
1632
	 */
1633
	public function transfer(SS_HTTPRequest $request) {
1634
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1635
1636
		$params = $request->params();
1637
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1638
1639
		if(!$transfer || !$transfer->ID) {
1640
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1641
		}
1642
		if(!$transfer->canView()) {
1643
			return Security::permissionFailure();
1644
		}
1645
1646
		$environment = $transfer->Environment();
1647
		$project = $environment->Project();
1648
1649
		if($project->Name != $params['Project']) {
1650
			throw new LogicException("Project in URL doesn't match this deploy");
1651
		}
1652
1653
		return $this->render(array(
1654
			'CurrentTransfer' => $transfer,
1655
			'SnapshotsSection' => 1,
1656
		));
1657
	}
1658
1659
	/**
1660
	 * Action - Get the latest deploy log
1661
	 *
1662
	 * @param SS_HTTPRequest $request
1663
	 *
1664
	 * @return string
1665
	 * @throws SS_HTTPResponse_Exception
1666
	 */
1667 View Code Duplication
	public function transferlog(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1668
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1669
1670
		$params = $request->params();
1671
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1672
1673
		if(!$transfer || !$transfer->ID) {
1674
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1675
		}
1676
		if(!$transfer->canView()) {
1677
			return Security::permissionFailure();
1678
		}
1679
1680
		$environment = $transfer->Environment();
1681
		$project = $environment->Project();
1682
1683
		if($project->Name != $params['Project']) {
1684
			throw new LogicException("Project in URL doesn't match this deploy");
1685
		}
1686
1687
		$log = $transfer->log();
1688
		if($log->exists()) {
1689
			$content = $log->content();
1690
		} else {
1691
			$content = 'Waiting for action to start';
1692
		}
1693
1694
		return $this->sendResponse($transfer->ResqueStatus(), $content);
1695
	}
1696
1697
	/**
1698
	 * Note: Submits to the same action as {@link getDataTransferForm()},
1699
	 * but with a Direction=push and an archive reference.
1700
	 *
1701
	 * @param SS_HTTPRequest $request
1702
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually in {@link restore()},
1703
	 *                            otherwise the state is inferred from the request data.
1704
	 * @return Form
1705
	 */
1706
	public function getDataTransferRestoreForm(SS_HTTPRequest $request, DNDataArchive $dataArchive = null) {
1707
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1708
1709
		// Performs canView permission check by limiting visible projects
1710
		$project = $this->getCurrentProject();
1711
		$envs = $project->DNEnvironmentList()->filterByCallback(function($item) {
1712
			return $item->canRestore();
1713
		});
1714
1715
		if(!$envs) {
1716
			return $this->environment404Response();
1717
		}
1718
1719
		$modesMap = array();
1720
		if(in_array($dataArchive->Mode, array('all'))) {
1721
			$modesMap['all'] = 'Database and Assets';
1722
		};
1723
		if(in_array($dataArchive->Mode, array('all', 'db'))) {
1724
			$modesMap['db'] = 'Database only';
1725
		};
1726
		if(in_array($dataArchive->Mode, array('all', 'assets'))) {
1727
			$modesMap['assets'] = 'Assets only';
1728
		};
1729
1730
		$alertMessage = '<div class="alert alert-warning"><strong>Warning:</strong> '
1731
			. 'This restore will overwrite the data on the chosen environment below</div>';
1732
1733
		$form = Form::create(
1734
			$this,
1735
			'DataTransferRestoreForm',
1736
			FieldList::create(
1737
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1738
				HiddenField::create('Direction', null, 'push'),
1739
				LiteralField::create('Warning', $alertMessage),
1740
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1741
					->setEmptyString('Select an environment'),
1742
				DropdownField::create('Mode', 'Transfer', $modesMap),
1743
				CheckboxField::create('BackupBeforePush', 'Backup existing data', '1')
1744
			),
1745
			FieldList::create(
1746
				FormAction::create('doDataTransfer', 'Restore Data')
1747
					->addExtraClass('btn')
1748
			)
1749
		);
1750
		$form->setFormAction($project->Link() . '/DataTransferRestoreForm');
1751
1752
		return $form;
1753
	}
1754
1755
	/**
1756
	 * View a form to restore a specific {@link DataArchive}.
1757
	 * Permission checks are handled in {@link DataArchives()}.
1758
	 * Submissions are handled through {@link doDataTransfer()}, same as backup operations.
1759
	 *
1760
	 * @param SS_HTTPRequest $request
1761
	 *
1762
	 * @return HTMLText
1763
	 * @throws SS_HTTPResponse_Exception
1764
	 */
1765 View Code Duplication
	public function restoresnapshot(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1766
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1767
1768
		/** @var DNDataArchive $dataArchive */
1769
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1770
1771
		if(!$dataArchive) {
1772
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1773
		}
1774
1775
		// We check for canDownload because that implies access to the data.
1776
		// canRestore is later checked on the actual restore action per environment.
1777
		if(!$dataArchive->canDownload()) {
1778
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1779
		}
1780
1781
		$form = $this->getDataTransferRestoreForm($this->request, $dataArchive);
1782
1783
		// View currently only available via ajax
1784
		return $form->forTemplate();
1785
	}
1786
1787
	/**
1788
	 * View a form to delete a specific {@link DataArchive}.
1789
	 * Permission checks are handled in {@link DataArchives()}.
1790
	 * Submissions are handled through {@link doDelete()}.
1791
	 *
1792
	 * @param SS_HTTPRequest $request
1793
	 *
1794
	 * @return HTMLText
1795
	 * @throws SS_HTTPResponse_Exception
1796
	 */
1797 View Code Duplication
	public function deletesnapshot(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1798
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1799
1800
		/** @var DNDataArchive $dataArchive */
1801
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1802
1803
		if(!$dataArchive) {
1804
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1805
		}
1806
1807
		if(!$dataArchive->canDelete()) {
1808
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1809
		}
1810
1811
		$form = $this->getDeleteForm($this->request, $dataArchive);
1812
1813
		// View currently only available via ajax
1814
		return $form->forTemplate();
1815
	}
1816
1817
	/**
1818
	 * @param SS_HTTPRequest $request
1819
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually, otherwise the state is inferred
1820
	 *        from the request data.
1821
	 * @return Form
1822
	 */
1823
	public function getDeleteForm(SS_HTTPRequest $request, DNDataArchive $dataArchive = null) {
1824
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1825
1826
		// Performs canView permission check by limiting visible projects
1827
		$project = $this->getCurrentProject();
1828
		if(!$project) {
1829
			return $this->project404Response();
1830
		}
1831
1832
		$snapshotDeleteWarning = '<div class="alert alert-warning">'
1833
			. 'Are you sure you want to permanently delete this snapshot from this archive area?'
1834
			. '</div>';
1835
1836
		$form = Form::create(
1837
			$this,
1838
			'DeleteForm',
1839
			FieldList::create(
1840
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1841
				LiteralField::create('Warning', $snapshotDeleteWarning)
1842
			),
1843
			FieldList::create(
1844
				FormAction::create('doDelete', 'Delete')
1845
					->addExtraClass('btn')
1846
			)
1847
		);
1848
		$form->setFormAction($project->Link() . '/DeleteForm');
1849
1850
		return $form;
1851
	}
1852
1853
	/**
1854
	 * @param array $data
1855
	 * @param Form $form
1856
	 *
1857
	 * @return bool|SS_HTTPResponse
1858
	 * @throws SS_HTTPResponse_Exception
1859
	 */
1860
	public function doDelete($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1861
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1862
1863
		// Performs canView permission check by limiting visible projects
1864
		$project = $this->getCurrentProject();
1865
		if(!$project) {
1866
			return $this->project404Response();
1867
		}
1868
1869
		$dataArchive = null;
1870
1871
		if(
1872
			isset($data['DataArchiveID'])
1873
			&& is_numeric($data['DataArchiveID'])
1874
		) {
1875
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1876
		}
1877
1878
		if(!$dataArchive) {
1879
			throw new LogicException('Invalid data archive');
1880
		}
1881
1882
		if(!$dataArchive->canDelete()) {
1883
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1884
		}
1885
1886
		$dataArchive->delete();
1887
1888
		return $this->redirectBack();
1889
	}
1890
1891
	/**
1892
	 * View a form to move a specific {@link DataArchive}.
1893
	 *
1894
	 * @param SS_HTTPRequest $request
1895
	 *
1896
	 * @return HTMLText
1897
	 * @throws SS_HTTPResponse_Exception
1898
	 */
1899 View Code Duplication
	public function movesnapshot(SS_HTTPRequest $request) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1900
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1901
1902
		/** @var DNDataArchive $dataArchive */
1903
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1904
1905
		if(!$dataArchive) {
1906
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1907
		}
1908
1909
		// We check for canDownload because that implies access to the data.
1910
		if(!$dataArchive->canDownload()) {
1911
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1912
		}
1913
1914
		$form = $this->getMoveForm($this->request, $dataArchive);
1915
1916
		// View currently only available via ajax
1917
		return $form->forTemplate();
0 ignored issues
show
Bug introduced by
The method forTemplate does only exist in Form, but not in SS_HTTPResponse.

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1918
	}
1919
1920
	/**
1921
	 * Build snapshot move form.
1922
	 *
1923
	 * @param SS_HTTPRequest $request
1924
	 * @param DNDataArchive|null $dataArchive
1925
	 *
1926
	 * @return Form|SS_HTTPResponse
1927
	 */
1928
	public function getMoveForm(SS_HTTPRequest $request, DNDataArchive $dataArchive = null) {
1929
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1930
1931
		$envs = $dataArchive->validTargetEnvironments();
1932
		if(!$envs) {
1933
			return $this->environment404Response();
1934
		}
1935
1936
		$warningMessage = '<div class="alert alert-warning"><strong>Warning:</strong> This will make the snapshot '
1937
			. 'available to people with access to the target environment.<br>By pressing "Change ownership" you '
1938
			. 'confirm that you have considered data confidentiality regulations.</div>';
1939
1940
		$form = Form::create(
1941
			$this,
1942
			'MoveForm',
1943
			FieldList::create(
1944
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1945
				LiteralField::create('Warning', $warningMessage),
1946
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1947
					->setEmptyString('Select an environment')
1948
			),
1949
			FieldList::create(
1950
				FormAction::create('doMove', 'Change ownership')
1951
					->addExtraClass('btn')
1952
			)
1953
		);
1954
		$form->setFormAction($this->getCurrentProject()->Link() . '/MoveForm');
1955
1956
		return $form;
1957
	}
1958
1959
	/**
1960
	 * @param array $data
1961
	 * @param Form $form
1962
	 *
1963
	 * @return bool|SS_HTTPResponse
1964
	 * @throws SS_HTTPResponse_Exception
1965
	 * @throws ValidationException
1966
	 * @throws null
1967
	 */
1968
	public function doMove($data, Form $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1969
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1970
1971
		// Performs canView permission check by limiting visible projects
1972
		$project = $this->getCurrentProject();
1973
		if(!$project) {
1974
			return $this->project404Response();
1975
		}
1976
1977
		/** @var DNDataArchive $dataArchive */
1978
		$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1979
		if(!$dataArchive) {
1980
			throw new LogicException('Invalid data archive');
1981
		}
1982
1983
		// We check for canDownload because that implies access to the data.
1984
		if(!$dataArchive->canDownload()) {
1985
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1986
		}
1987
1988
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1989
		$validEnvs = $dataArchive->validTargetEnvironments();
1990
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1991
		if(!$environment) {
1992
			throw new LogicException('Invalid environment');
1993
		}
1994
1995
		$dataArchive->EnvironmentID = $environment->ID;
1996
		$dataArchive->write();
1997
1998
		return $this->redirectBack();
1999
	}
2000
2001
	/**
2002
	 * Returns an error message if redis is unavailable
2003
	 *
2004
	 * @return string
2005
	 */
2006
	public static function RedisUnavailable() {
2007
		try {
2008
			Resque::queues();
2009
		} catch(Exception $e) {
2010
			return $e->getMessage();
2011
		}
2012
		return '';
2013
	}
2014
2015
	/**
2016
	 * Returns the number of connected Redis workers
2017
	 *
2018
	 * @return int
2019
	 */
2020
	public static function RedisWorkersCount() {
2021
		return count(Resque_Worker::all());
2022
	}
2023
2024
	/**
2025
	 * @return array
2026
	 */
2027
	public function providePermissions() {
2028
		return array(
2029
			self::DEPLOYNAUT_BYPASS_PIPELINE => array(
2030
				'name' => "Bypass Pipeline",
2031
				'category' => "Deploynaut",
2032
				'help' => "Enables users to directly initiate deployments, bypassing any pipeline",
2033
			),
2034
			self::DEPLOYNAUT_DRYRUN_PIPELINE => array(
2035
				'name' => 'Dry-run Pipeline',
2036
				'category' => 'Deploynaut',
2037
				'help' => 'Enable dry-run execution of pipelines for testing'
2038
			),
2039
			self::DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS => array(
2040
				'name' => "Access to advanced deploy options",
2041
				'category' => "Deploynaut",
2042
			),
2043
2044
			// Permissions that are intended to be added to the roles.
2045
			self::ALLOW_PROD_DEPLOYMENT => array(
2046
				'name' => "Ability to deploy to production environments",
2047
				'category' => "Deploynaut",
2048
			),
2049
			self::ALLOW_NON_PROD_DEPLOYMENT => array(
2050
				'name' => "Ability to deploy to non-production environments",
2051
				'category' => "Deploynaut",
2052
			),
2053
			self::ALLOW_PROD_SNAPSHOT => array(
2054
				'name' => "Ability to make production snapshots",
2055
				'category' => "Deploynaut",
2056
			),
2057
			self::ALLOW_NON_PROD_SNAPSHOT => array(
2058
				'name' => "Ability to make non-production snapshots",
2059
				'category' => "Deploynaut",
2060
			),
2061
			self::ALLOW_CREATE_ENVIRONMENT => array(
2062
				'name' => "Ability to create environments",
2063
				'category' => "Deploynaut",
2064
			),
2065
		);
2066
	}
2067
2068
	/**
2069
	 * @return DNProject|null
2070
	 */
2071
	public function getCurrentProject() {
2072
		$projectName = trim($this->getRequest()->param('Project'));
2073
		if(!$projectName) {
2074
			return null;
2075
		}
2076
		if(empty(self::$_project_cache[$projectName])) {
2077
			self::$_project_cache[$projectName] = $this->DNProjectList()->filter('Name', $projectName)->First();
2078
		}
2079
		return self::$_project_cache[$projectName];
2080
	}
2081
2082
	/**
2083
	 * @param DNProject|null $project
2084
	 * @return DNEnvironment|null
2085
	 */
2086
	public function getCurrentEnvironment(DNProject $project = null) {
2087
		if($this->getRequest()->param('Environment') === null) {
2088
			return null;
2089
		}
2090
		if($project === null) {
2091
			$project = $this->getCurrentProject();
2092
		}
2093
		// project can still be null
2094
		if($project === null) {
2095
			return null;
2096
		}
2097
		return $project->DNEnvironmentList()->filter('Name', $this->getRequest()->param('Environment'))->First();
2098
	}
2099
2100
	/**
2101
	 * This will return a const that indicates the class of action currently being performed
2102
	 *
2103
	 * Until DNRoot is de-godded, it does a bunch of different actions all in the same class.
2104
	 * So we just have each action handler calll setCurrentActionType to define what sort of
2105
	 * action it is.
2106
	 *
2107
	 * @return string - one of the consts from self::$action_types
2108
	 */
2109
	public function getCurrentActionType() {
2110
		return $this->actionType;
2111
	}
2112
2113
	/**
2114
	 * Sets the current action type
2115
	 *
2116
	 * @param string $actionType string - one of the consts from self::$action_types
2117
	 */
2118
	public function setCurrentActionType($actionType) {
2119
		$this->actionType = $actionType;
2120
	}
2121
2122
	/**
2123
	 * Helper method to allow templates to know whether they should show the 'Archive List' include or not.
2124
	 * The actual permissions are set on a per-environment level, so we need to find out if this $member can upload to
2125
	 * or download from *any* {@link DNEnvironment} that (s)he has access to.
2126
	 *
2127
	 * TODO To be replaced with a method that just returns the list of archives this {@link Member} has access to.
2128
	 *
2129
	 * @param Member|null $member The {@link Member} to check (or null to check the currently logged in Member)
2130
	 * @return boolean|null true if $member has access to upload or download to at least one {@link DNEnvironment}.
2131
	 */
2132
	public function CanViewArchives(Member $member = null) {
2133
		if($member === null) {
2134
			$member = Member::currentUser();
2135
		}
2136
2137
		if(Permission::checkMember($member, 'ADMIN')) {
2138
			return true;
2139
		}
2140
2141
		$allProjects = $this->DNProjectList();
2142
		if(!$allProjects) {
2143
			return false;
2144
		}
2145
2146
		foreach($allProjects as $project) {
2147
			if($project->Environments()) {
2148
				foreach($project->Environments() as $environment) {
2149
					if(
2150
						$environment->canRestore($member) ||
2151
						$environment->canBackup($member) ||
2152
						$environment->canUploadArchive($member) ||
2153
						$environment->canDownloadArchive($member)
2154
					) {
2155
						// We can return early as we only need to know that we can access one environment
2156
						return true;
2157
					}
2158
				}
2159
			}
2160
		}
2161
	}
2162
2163
	/**
2164
	 * Returns a list of attempted environment creations.
2165
	 *
2166
	 * @return PaginatedList
2167
	 */
2168
	public function CreateEnvironmentList() {
2169
		$project = $this->getCurrentProject();
2170
		if($project) {
2171
			return new PaginatedList($project->CreateEnvironments()->sort("Created DESC"), $this->request);
0 ignored issues
show
Bug introduced by
The method CreateEnvironments() does not exist on DNProject. Did you maybe mean canCreateEnvironments()?

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

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

Loading history...
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2172
		}
2173
		return new PaginatedList(new ArrayList(), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2174
	}
2175
2176
	/**
2177
	 * Returns a list of all archive files that can be accessed by the currently logged-in {@link Member}
2178
	 *
2179
	 * @return PaginatedList
2180
	 */
2181
	public function CompleteDataArchives() {
2182
		$project = $this->getCurrentProject();
2183
		$archives = new ArrayList();
2184
2185
		$archiveList = $project->Environments()->relation("DataArchives");
2186
		if($archiveList->count() > 0) {
2187
			foreach($archiveList as $archive) {
2188
				if($archive->canView() && !$archive->isPending()) {
2189
					$archives->push($archive);
2190
				}
2191
			}
2192
		}
2193
		return new PaginatedList($archives->sort("Created", "DESC"), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2194
	}
2195
2196
	/**
2197
	 * @return PaginatedList The list of "pending" data archives which are waiting for a file
2198
	 * to be delivered offline by post, and manually uploaded into the system.
2199
	 */
2200
	public function PendingDataArchives() {
2201
		$project = $this->getCurrentProject();
2202
		$archives = new ArrayList();
2203
		foreach($project->DNEnvironmentList() as $env) {
2204
			foreach($env->DataArchives() as $archive) {
2205
				if($archive->canView() && $archive->isPending()) {
2206
					$archives->push($archive);
2207
				}
2208
			}
2209
		}
2210
		return new PaginatedList($archives->sort("Created", "DESC"), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2211
	}
2212
2213
	/**
2214
	 * @return PaginatedList
2215
	 */
2216
	public function DataTransferLogs() {
2217
		$project = $this->getCurrentProject();
2218
2219
		$transfers = DNDataTransfer::get()->filterByCallback(function($record) use($project) {
2220
			return
2221
				$record->Environment()->Project()->ID == $project->ID && // Ensure only the current Project is shown
2222
				(
2223
					$record->Environment()->canRestore() || // Ensure member can perform an action on the transfers env
2224
					$record->Environment()->canBackup() ||
2225
					$record->Environment()->canUploadArchive() ||
2226
					$record->Environment()->canDownloadArchive()
2227
				);
2228
		});
2229
2230
		return new PaginatedList($transfers->sort("Created", "DESC"), $this->request);
0 ignored issues
show
Documentation introduced by
$this->request is of type object<SS_HTTPRequest>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2231
	}
2232
2233
	/**
2234
	 * @return null|PaginatedList
2235
	 */
2236
	public function DeployHistory() {
2237
		if($env = $this->getCurrentEnvironment()) {
2238
			$history = $env->DeployHistory();
2239
			if($history->count() > 0) {
2240
				$pagination = new PaginatedList($history, $this->getRequest());
0 ignored issues
show
Documentation introduced by
$this->getRequest() is of type object<SS_HTTPRequest>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2241
				$pagination->setPageLength(8);
2242
				return $pagination;
2243
			}
2244
		}
2245
		return null;
2246
	}
2247
2248
	/**
2249
	 * @return SS_HTTPResponse
2250
	 */
2251
	protected function project404Response() {
2252
		return new SS_HTTPResponse(
2253
			"Project '" . Convert::raw2xml($this->getRequest()->param('Project')) . "' not found.",
2254
			404
2255
		);
2256
	}
2257
2258
	/**
2259
	 * @return SS_HTTPResponse
2260
	 */
2261
	protected function environment404Response() {
2262
		$envName = Convert::raw2xml($this->getRequest()->param('Environment'));
2263
		return new SS_HTTPResponse("Environment '" . $envName . "' not found.", 404);
2264
	}
2265
2266
	/**
2267
	 * @param string $status
2268
	 * @param string $content
2269
	 *
2270
	 * @return string
2271
	 */
2272
	protected function sendResponse($status, $content) {
2273
		// strip excessive newlines
2274
		$content = preg_replace('/(?:(?:\r\n|\r|\n)\s*){2}/s', "\n", $content);
2275
2276
		$sendJSON = (strpos($this->getRequest()->getHeader('Accept'), 'application/json') !== false)
2277
			|| $this->getRequest()->getExtension() == 'json';
2278
2279
		if(!$sendJSON) {
2280
			$this->response->addHeader("Content-type", "text/plain");
2281
			return $content;
2282
		}
2283
		$this->response->addHeader("Content-type", "application/json");
2284
		return json_encode(array(
2285
			'status' => $status,
2286
			'content' => $content,
2287
		));
2288
	}
2289
2290
	/**
2291
	 * Validate the snapshot mode
2292
	 *
2293
	 * @param string $mode
2294
	 */
2295
	protected function validateSnapshotMode($mode) {
2296
		if(!in_array($mode, array('all', 'assets', 'db'))) {
2297
			throw new LogicException('Invalid mode');
2298
		}
2299
	}
2300
2301
	/**
2302
	 * @param string $sectionName
2303
	 * @param string $title
2304
	 *
2305
	 * @return SS_HTTPResponse
2306
	 */
2307
	protected function getCustomisedViewSection($sectionName, $title = '', $data = array()) {
2308
		// Performs canView permission check by limiting visible projects
2309
		$project = $this->getCurrentProject();
2310
		if(!$project) {
2311
			return $this->project404Response();
2312
		}
2313
		$data[$sectionName] = 1;
2314
2315
		if($this !== '') {
2316
			$data['Title'] = $title;
2317
		}
2318
2319
		return $this->render($data);
2320
	}
2321
2322
	/**
2323
	 * Get items for the ambient menu that should be accessible from all pages.
2324
	 *
2325
	 * @return ArrayList
2326
	 */
2327
	public function AmbientMenu() {
2328
		$list = new ArrayList();
2329
2330
		if (Member::currentUserID()) {
2331
			$list->push(new ArrayData(array(
2332
				'Classes' => 'logout',
2333
				'FaIcon' => 'sign-out',
2334
				'Link' => 'Security/logout',
2335
				'Title' => 'Log out',
2336
				'IsCurrent' => false,
2337
				'IsSection' => false
2338
			)));
2339
		}
2340
2341
		$this->extend('updateAmbientMenu', $list);
2342
		return $list;
2343
	}
2344
2345
	/**
2346
	 * Create project action.
2347
	 *
2348
	 * @return SS_HTTPResponse
2349
	 */
2350
	public function createproject(SS_HTTPRequest $request) {
2351
		if($this->canCreateProjects()) {
2352
			return $this->render(['CurrentTitle' => 'Create Stack']);
2353
		}
2354
		return $this->httpError(403);
2355
	}
2356
2357
	/**
2358
	 * Checks whether the user can create a project.
2359
	 *
2360
	 * @return bool
2361
	 */
2362
	public function canCreateProjects($member = null) {
2363
		if(!$member) $member = Member::currentUser();
2364
		if(!$member) return false;
2365
2366
		return singleton('DNProject')->canCreate($member);
2367
	}
2368
2369
	/**
2370
	 * @return Form
2371
	 */
2372
	public function CreateProjectForm() {
2373
		$form = Form::create(
2374
			$this,
2375
			__FUNCTION__,
2376
			$this->getCreateProjectFormFields(),
2377
			$this->getCreateProjectFormActions(),
2378
			new RequiredFields('Name', 'CVSPath')
2379
		);
2380
		$this->extend('updateCreateProjectForm', $form);
2381
		return $form;
2382
	}
2383
2384
	/**
2385
	 * @return FieldList
2386
	 */
2387
	protected function getCreateProjectFormFields() {
2388
		$fields = FieldList::create();
2389
		$fields->merge([
2390
			TextField::create('Name', 'Title')->setDescription('Limited to alphanumeric characters, underscores and hyphens.'),
2391
			TextField::create('CVSPath', 'Git URL')->setDescription('Your repository URL so we can clone your code (eg. [email protected]:silverstripe/silverstripe-installer.git)')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 168 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...
2392
		]);
2393
		$this->extend('updateCreateProjectFormFields', $fields);
2394
		return $fields;
2395
	}
2396
2397
	/**
2398
	 * @return FieldList
2399
	 */
2400
	protected function getCreateProjectFormActions() {
2401
		$fields = FieldList::create(
2402
			FormAction::create('doCreateProject', 'Create Stack')
2403
		);
2404
		$this->extend('updateCreateProjectFormActions', $fields);
2405
		return $fields;
2406
	}
2407
2408
	/**
2409
	 * Does the actual project creation.
2410
	 *
2411
	 * @param $data array
2412
	 * @param $form Form
2413
	 *
2414
	 * @return SS_HTTPResponse
2415
	 */
2416
	public function doCreateProject($data, $form) {
2417
		$form->loadDataFrom($data);
2418
		$project = DNProject::create();
2419
2420
		$form->saveInto($project);
2421
		$this->extend('onBeforeCreateProject', $project, $data, $form);
2422
		try {
2423
			if($project->write() > 0) {
2424
				$this->extend('onAfterCreateProject', $project, $data, $form);
2425
2426
				// If an extension hasn't redirected us, we'll redirect to the project.
2427
				if(!$this->redirectedTo()) {
2428
					return $this->redirect($project->Link());
2429
				} else {
2430
					return $this->response;
2431
				}
2432
			} else {
2433
				$form->sessionMessage('Unable to write the stack to the database.', 'bad');
2434
			}
2435
		} catch (ValidationException $e) {
2436
			$form->sessionMessage($e->getMessage(), 'bad');
2437
		}
2438
		return $this->redirectBack();
2439
	}
2440
2441
	/**
2442
	 * Returns the state of a current project build.
2443
	 *
2444
	 * @param SS_HTTPRequest $request
2445
	 *
2446
	 * @return SS_HTTPResponse
2447
	 */
2448
	public function createprojectprogress(SS_HTTPRequest $request) {
2449
		$project = $this->getCurrentProject();
2450
		if(!$project) {
2451
			return $this->httpError(404);
2452
		}
2453
2454
		$envCreations = $project->getInitialEnvironmentCreations();
2455
		$complete = array();
2456
		$inProgress = array();
2457
		$failed = array();
2458
		if($envCreations->count() > 0) {
2459
			foreach($envCreations as $env) {
2460
				$data = unserialize($env->Data);
2461
				if(!isset($data['Name'])) {
2462
					$data['Name'] = 'Unknown';
2463
				}
2464
				switch($env->ResqueStatus()) {
2465
					case "Queued":
2466
					case "Running":
2467
						$inProgress[$env->ID] = Convert::raw2xml($env->ResqueStatus());
2468
						break;
2469
					case "Complete":
2470
						$complete[$env->ID] = Convert::raw2xml($data['Name']);
2471
						break;
2472
					case "Failed":
2473
					case "Invalid":
2474
					default:
2475
						$failed[$env->ID] = Convert::raw2xml($data['Name']);
2476
				}
2477
			}
2478
		}
2479
2480
		$data = [
2481
			'complete' => $project->isProjectReady(),
2482
			'progress' => [
2483
				'environments' => [
2484
					'complete' => $complete,
2485
					'inProgress' => $inProgress,
2486
					'failed' => $failed,
2487
				]
2488
			]
2489
		];
2490
		$this->extend('updateCreateProjectProgressData', $data);
2491
2492
		$response = $this->getResponse();
2493
		$response->addHeader('Content-Type', 'application/json');
2494
		$response->setBody(json_encode($data));
2495
		return $response;
2496
	}
2497
2498
	public function checkrepoaccess(SS_HTTPRequest $request) {
2499
		$project = $this->getCurrentProject();
2500
		if(!$project) {
2501
			return $this->httpError(404);
2502
		}
2503
2504
		if($project->CVSPath) {
2505
			$fetch = new FetchJob();
2506
			$fetch->args = array('projectID' => $project->ID);
2507
			try {
2508
				$fetch->perform();
2509
				$canAccessRepo = true;
2510
			} catch (RuntimeException $e) {
2511
				$canAccessRepo = false;
2512
			}
2513
			$data = ['canAccessRepo' => $canAccessRepo];
2514
		} else {
2515
			$data = ['canAccessRepo' => false];
2516
		}
2517
2518
		$response = $this->getResponse();
2519
		$response->addHeader("Content-Type", "application/json");
2520
		$response->setBody(json_encode($data));
2521
		return $response;
2522
	}
2523
2524
}
2525
2526