Completed
Push — master ( d37947...f5a873 )
by Mateusz
1297:50 queued 1294:31
created

DNRoot::deploySummary()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 45
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1260
		$redeploy = array();
1261
		foreach($project->DNEnvironmentList() as $dnEnvironment) {
1262
			$envName = $dnEnvironment->Name;
1263
			$perEnvDeploys = array();
1264
1265
			foreach($dnEnvironment->DeployHistory() as $deploy) {
1266
				$sha = $deploy->SHA;
1267
1268
				// Check if exists to make sure the newest deployment date is used.
1269
				if(!isset($perEnvDeploys[$sha])) {
1270
					$pastValue = sprintf("%s (deployed %s)",
1271
						substr($sha, 0, 8),
1272
						$deploy->obj('LastEdited')->Ago()
1273
					);
1274
					$perEnvDeploys[$sha] = array(
1275
						'id' => $sha,
1276
						'text' => $pastValue
1277
					);
1278
				}
1279
			}
1280
1281
			if(!empty($perEnvDeploys)) {
1282
				$redeploy[$envName] = array_values($perEnvDeploys);
1283
			}
1284
		}
1285
		// Convert the array to the frontend format (i.e. keyed to regular array)
1286
		foreach($redeploy as $env => $descr) {
1287
			$data['field_data'][] = array('text'=>$env, 'children'=>$descr);
1288
		}
1289
		$tabs[] = $data;
1290
1291
		$data = array(
1292
			'id' => ++$id,
1293
			'name' => 'Deploy a specific SHA',
1294
			'field_type' => 'textfield',
1295
			'field_label' => 'Choose a SHA',
1296
			'field_id' => 'SHA',
1297
			'field_data' => array(),
1298
			'advanced_opts' => $advanced
1299
		);
1300
		$tabs[] = $data;
1301
1302
		// get the last time git fetch was run
1303
		$lastFetched = 'never';
1304
		$fetch = DNGitFetch::get()
1305
			->filter('ProjectID', $project->ID)
1306
			->sort('LastEdited', 'DESC')
1307
			->first();
1308
		if($fetch) {
1309
			$lastFetched = $fetch->dbObject('LastEdited')->Ago();
1310
		}
1311
1312
		$data = array(
1313
			'Tabs' => $tabs,
1314
			'last_fetched' => $lastFetched
1315
		);
1316
1317
		return json_encode($data, JSON_PRETTY_PRINT);
1318
	}
1319
1320
	/**
1321
	 * Check and regenerate a global CSRF token
1322
	 *
1323
	 * @param SS_HTTPRequest $request
1324
	 * @param bool $resetToken
1325
	 *
1326
	 * @return bool
1327
	 */
1328
	protected function checkCsrfToken(SS_HTTPRequest $request, $resetToken = true) {
1329
		$token = SecurityToken::inst();
1330
1331
		// Ensure the submitted token has a value
1332
		$submittedToken = $request->postVar('SecurityID');
1333
		if(!$submittedToken) {
1334
			return false;
1335
		}
1336
1337
		// Do the actual check.
1338
		$check = $token->check($submittedToken);
1339
1340
		// Reset the token after we've checked the existing token
1341
		if($resetToken) {
1342
			$token->reset();
1343
		}
1344
1345
		// Return whether the token was correct or not
1346
		return $check;
1347
	}
1348
1349
	/**
1350
	 * @param SS_HTTPRequest $request
1351
	 *
1352
	 * @return string
1353
	 */
1354
	public function deploySummary(SS_HTTPRequest $request) {
1355
1356
		// Performs canView permission check by limiting visible projects
1357
		$project = $this->getCurrentProject();
1358
		if(!$project) {
1359
			return $this->project404Response();
1360
		}
1361
1362
		// Performs canView permission check by limiting visible projects
1363
		$environment = $this->getCurrentEnvironment($project);
1364
		if(!$environment) {
1365
			return $this->environment404Response();
1366
		}
1367
1368
		// Plan the deployment.
1369
		$strategy = $environment->Backend()->planDeploy(
1370
			$environment,
1371
			$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...
1372
		);
1373
		$data = $strategy->toArray();
1374
1375
		// Add in a URL for comparing from->to code changes. Ensure that we have
1376
		// two proper 40 character SHAs, otherwise we can't show the compare link.
1377
		if(
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
			$interface = $project->getRepositoryInterface();
1384
			$compareurl = sprintf(
1385
				'%s/compare/%s...%s',
1386
				$interface->URL,
1387
				$data['changes']['Code version']['from'],
1388
				$data['changes']['Code version']['to']
1389
			);
1390
			$data['changes']['Code version']['compareUrl'] = $compareurl;
1391
		}
1392
1393
		// Append json to response
1394
		$token = SecurityToken::inst();
1395
		$data['SecurityID'] = $token->getValue();
1396
1397
		return json_encode($data);
1398
	}
1399
1400
	/**
1401
	 * Deployment form submission handler.
1402
	 *
1403
	 * Initiate a DNDeployment record and redirect to it for status polling
1404
	 *
1405
	 * @param SS_HTTPRequest $request
1406
	 *
1407
	 * @return SS_HTTPResponse
1408
	 * @throws ValidationException
1409
	 * @throws null
1410
	 */
1411
	public function startDeploy(SS_HTTPRequest $request) {
1412
1413
		// Ensure the CSRF Token is correct
1414
		if(!$this->checkCsrfToken($request)) {
1415
			// CSRF token didn't match
1416
			return $this->httpError(400, 'Bad Request');
1417
		}
1418
1419
		// Performs canView permission check by limiting visible projects
1420
		$project = $this->getCurrentProject();
1421
		if(!$project) {
1422
			return $this->project404Response();
1423
		}
1424
1425
		// Performs canView permission check by limiting visible projects
1426
		$environment = $this->getCurrentEnvironment($project);
1427
		if(!$environment) {
1428
			return $this->environment404Response();
1429
		}
1430
1431
		// Initiate the deployment
1432
		// The extension point should pass in: Project, Environment, SelectRelease, buildName
1433
		$this->extend('doDeploy', $project, $environment, $buildName, $data);
1434
1435
		// Start the deployment based on the approved strategy.
1436
		$strategy = new DeploymentStrategy($environment);
1437
		$strategy->fromArray($request->requestVar('strategy'));
1438
		$deployment = $strategy->createDeployment();
1439
		$deployment->start();
1440
1441
		return json_encode(array(
1442
			'url' => Director::absoluteBaseURL() . $deployment->Link()
1443
		), JSON_PRETTY_PRINT);
1444
	}
1445
1446
	/**
1447
	 * Action - Do the actual deploy
1448
	 *
1449
	 * @param SS_HTTPRequest $request
1450
	 *
1451
	 * @return SS_HTTPResponse|string
1452
	 * @throws SS_HTTPResponse_Exception
1453
	 */
1454
	public function deploy(SS_HTTPRequest $request) {
1455
		$params = $request->params();
1456
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1457
1458
		if(!$deployment || !$deployment->ID) {
1459
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1460
		}
1461
		if(!$deployment->canView()) {
1462
			return Security::permissionFailure();
1463
		}
1464
1465
		$environment = $deployment->Environment();
1466
		$project = $environment->Project();
1467
1468
		if($environment->Name != $params['Environment']) {
1469
			throw new LogicException("Environment in URL doesn't match this deploy");
1470
		}
1471
		if($project->Name != $params['Project']) {
1472
			throw new LogicException("Project in URL doesn't match this deploy");
1473
		}
1474
1475
		return $this->render(array(
1476
			'Deployment' => $deployment,
1477
		));
1478
	}
1479
1480
1481
	/**
1482
	 * Action - Get the latest deploy log
1483
	 *
1484
	 * @param SS_HTTPRequest $request
1485
	 *
1486
	 * @return string
1487
	 * @throws SS_HTTPResponse_Exception
1488
	 */
1489
	public function deploylog(SS_HTTPRequest $request) {
1490
		$params = $request->params();
1491
		$deployment = DNDeployment::get()->byId($params['Identifier']);
1492
1493
		if(!$deployment || !$deployment->ID) {
1494
			throw new SS_HTTPResponse_Exception('Deployment not found', 404);
1495
		}
1496
		if(!$deployment->canView()) {
1497
			return Security::permissionFailure();
1498
		}
1499
1500
		$environment = $deployment->Environment();
1501
		$project = $environment->Project();
1502
1503
		if($environment->Name != $params['Environment']) {
1504
			throw new LogicException("Environment in URL doesn't match this deploy");
1505
		}
1506
		if($project->Name != $params['Project']) {
1507
			throw new LogicException("Project in URL doesn't match this deploy");
1508
		}
1509
1510
		$log = $deployment->log();
1511
		if($log->exists()) {
1512
			$content = $log->content();
1513
		} else {
1514
			$content = 'Waiting for action to start';
1515
		}
1516
1517
		return $this->sendResponse($deployment->ResqueStatus(), $content);
1518
	}
1519
1520
	/**
1521
	 * @param SS_HTTPRequest|null $request
1522
	 *
1523
	 * @return Form
1524
	 */
1525
	public function getDataTransferForm(SS_HTTPRequest $request = null) {
1526
		// Performs canView permission check by limiting visible projects
1527
		$envs = $this->getCurrentProject()->DNEnvironmentList()->filterByCallback(function($item) {
1528
			return $item->canBackup();
1529
		});
1530
1531
		if(!$envs) {
1532
			return $this->environment404Response();
1533
		}
1534
1535
		$form = Form::create(
1536
			$this,
1537
			'DataTransferForm',
1538
			FieldList::create(
1539
				HiddenField::create('Direction', null, 'get'),
1540
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1541
					->setEmptyString('Select an environment'),
1542
				DropdownField::create('Mode', 'Transfer', DNDataArchive::get_mode_map())
1543
			),
1544
			FieldList::create(
1545
				FormAction::create('doDataTransfer', 'Create')
1546
					->addExtraClass('btn')
1547
			)
1548
		);
1549
		$form->setFormAction($this->getRequest()->getURL() . '/DataTransferForm');
1550
1551
		return $form;
1552
	}
1553
1554
	/**
1555
	 * @param array $data
1556
	 * @param Form $form
1557
	 *
1558
	 * @return SS_HTTPResponse
1559
	 * @throws SS_HTTPResponse_Exception
1560
	 */
1561
	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...
1562
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1563
1564
		// Performs canView permission check by limiting visible projects
1565
		$project = $this->getCurrentProject();
1566
		if(!$project) {
1567
			return $this->project404Response();
1568
		}
1569
1570
		$dataArchive = null;
1571
1572
		// Validate direction.
1573
		if($data['Direction'] == 'get') {
1574
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1575
				->filterByCallback(function($item) {
1576
					return $item->canBackup();
1577
				});
1578
		} else if($data['Direction'] == 'push') {
1579
			$validEnvs = $this->getCurrentProject()->DNEnvironmentList()
1580
				->filterByCallback(function($item) {
1581
					return $item->canRestore();
1582
				});
1583
		} else {
1584
			throw new LogicException('Invalid direction');
1585
		}
1586
1587
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1588
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1589
		if(!$environment) {
1590
			throw new LogicException('Invalid environment');
1591
		}
1592
1593
		$this->validateSnapshotMode($data['Mode']);
1594
1595
1596
		// Only 'push' direction is allowed an association with an existing archive.
1597
		if(
1598
			$data['Direction'] == 'push'
1599
			&& isset($data['DataArchiveID'])
1600
			&& is_numeric($data['DataArchiveID'])
1601
		) {
1602
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1603
			if(!$dataArchive) {
1604
				throw new LogicException('Invalid data archive');
1605
			}
1606
1607
			if(!$dataArchive->canDownload()) {
1608
				throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1609
			}
1610
		}
1611
1612
		$transfer = DNDataTransfer::create();
1613
		$transfer->EnvironmentID = $environment->ID;
1614
		$transfer->Direction = $data['Direction'];
1615
		$transfer->Mode = $data['Mode'];
1616
		$transfer->DataArchiveID = $dataArchive ? $dataArchive->ID : null;
1617
		if($data['Direction'] == 'push') {
1618
			$transfer->setBackupBeforePush(!empty($data['BackupBeforePush']));
1619
		}
1620
		$transfer->write();
1621
		$transfer->start();
1622
1623
		return $this->redirect($transfer->Link());
1624
	}
1625
1626
	/**
1627
	 * View into the log for a {@link DNDataTransfer}.
1628
	 *
1629
	 * @param SS_HTTPRequest $request
1630
	 *
1631
	 * @return SS_HTTPResponse|string
1632
	 * @throws SS_HTTPResponse_Exception
1633
	 */
1634
	public function transfer(SS_HTTPRequest $request) {
1635
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1636
1637
		$params = $request->params();
1638
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1639
1640
		if(!$transfer || !$transfer->ID) {
1641
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1642
		}
1643
		if(!$transfer->canView()) {
1644
			return Security::permissionFailure();
1645
		}
1646
1647
		$environment = $transfer->Environment();
1648
		$project = $environment->Project();
1649
1650
		if($project->Name != $params['Project']) {
1651
			throw new LogicException("Project in URL doesn't match this deploy");
1652
		}
1653
1654
		return $this->render(array(
1655
			'CurrentTransfer' => $transfer,
1656
			'SnapshotsSection' => 1,
1657
		));
1658
	}
1659
1660
	/**
1661
	 * Action - Get the latest deploy log
1662
	 *
1663
	 * @param SS_HTTPRequest $request
1664
	 *
1665
	 * @return string
1666
	 * @throws SS_HTTPResponse_Exception
1667
	 */
1668 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...
1669
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1670
1671
		$params = $request->params();
1672
		$transfer = DNDataTransfer::get()->byId($params['Identifier']);
1673
1674
		if(!$transfer || !$transfer->ID) {
1675
			throw new SS_HTTPResponse_Exception('Transfer not found', 404);
1676
		}
1677
		if(!$transfer->canView()) {
1678
			return Security::permissionFailure();
1679
		}
1680
1681
		$environment = $transfer->Environment();
1682
		$project = $environment->Project();
1683
1684
		if($project->Name != $params['Project']) {
1685
			throw new LogicException("Project in URL doesn't match this deploy");
1686
		}
1687
1688
		$log = $transfer->log();
1689
		if($log->exists()) {
1690
			$content = $log->content();
1691
		} else {
1692
			$content = 'Waiting for action to start';
1693
		}
1694
1695
		return $this->sendResponse($transfer->ResqueStatus(), $content);
1696
	}
1697
1698
	/**
1699
	 * Note: Submits to the same action as {@link getDataTransferForm()},
1700
	 * but with a Direction=push and an archive reference.
1701
	 *
1702
	 * @param SS_HTTPRequest $request
1703
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually in {@link restore()},
1704
	 *                            otherwise the state is inferred from the request data.
1705
	 * @return Form
1706
	 */
1707
	public function getDataTransferRestoreForm(SS_HTTPRequest $request, DNDataArchive $dataArchive = null) {
1708
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1709
1710
		// Performs canView permission check by limiting visible projects
1711
		$project = $this->getCurrentProject();
1712
		$envs = $project->DNEnvironmentList()->filterByCallback(function($item) {
1713
			return $item->canRestore();
1714
		});
1715
1716
		if(!$envs) {
1717
			return $this->environment404Response();
1718
		}
1719
1720
		$modesMap = array();
1721
		if(in_array($dataArchive->Mode, array('all'))) {
1722
			$modesMap['all'] = 'Database and Assets';
1723
		};
1724
		if(in_array($dataArchive->Mode, array('all', 'db'))) {
1725
			$modesMap['db'] = 'Database only';
1726
		};
1727
		if(in_array($dataArchive->Mode, array('all', 'assets'))) {
1728
			$modesMap['assets'] = 'Assets only';
1729
		};
1730
1731
		$alertMessage = '<div class="alert alert-warning"><strong>Warning:</strong> '
1732
			. 'This restore will overwrite the data on the chosen environment below</div>';
1733
1734
		$form = Form::create(
1735
			$this,
1736
			'DataTransferRestoreForm',
1737
			FieldList::create(
1738
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1739
				HiddenField::create('Direction', null, 'push'),
1740
				LiteralField::create('Warning', $alertMessage),
1741
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1742
					->setEmptyString('Select an environment'),
1743
				DropdownField::create('Mode', 'Transfer', $modesMap),
1744
				CheckboxField::create('BackupBeforePush', 'Backup existing data', '1')
1745
			),
1746
			FieldList::create(
1747
				FormAction::create('doDataTransfer', 'Restore Data')
1748
					->addExtraClass('btn')
1749
			)
1750
		);
1751
		$form->setFormAction($project->Link() . '/DataTransferRestoreForm');
1752
1753
		return $form;
1754
	}
1755
1756
	/**
1757
	 * View a form to restore a specific {@link DataArchive}.
1758
	 * Permission checks are handled in {@link DataArchives()}.
1759
	 * Submissions are handled through {@link doDataTransfer()}, same as backup operations.
1760
	 *
1761
	 * @param SS_HTTPRequest $request
1762
	 *
1763
	 * @return HTMLText
1764
	 * @throws SS_HTTPResponse_Exception
1765
	 */
1766 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...
1767
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1768
1769
		/** @var DNDataArchive $dataArchive */
1770
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1771
1772
		if(!$dataArchive) {
1773
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1774
		}
1775
1776
		// We check for canDownload because that implies access to the data.
1777
		// canRestore is later checked on the actual restore action per environment.
1778
		if(!$dataArchive->canDownload()) {
1779
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1780
		}
1781
1782
		$form = $this->getDataTransferRestoreForm($this->request, $dataArchive);
1783
1784
		// View currently only available via ajax
1785
		return $form->forTemplate();
1786
	}
1787
1788
	/**
1789
	 * View a form to delete a specific {@link DataArchive}.
1790
	 * Permission checks are handled in {@link DataArchives()}.
1791
	 * Submissions are handled through {@link doDelete()}.
1792
	 *
1793
	 * @param SS_HTTPRequest $request
1794
	 *
1795
	 * @return HTMLText
1796
	 * @throws SS_HTTPResponse_Exception
1797
	 */
1798 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...
1799
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1800
1801
		/** @var DNDataArchive $dataArchive */
1802
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1803
1804
		if(!$dataArchive) {
1805
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1806
		}
1807
1808
		if(!$dataArchive->canDelete()) {
1809
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1810
		}
1811
1812
		$form = $this->getDeleteForm($this->request, $dataArchive);
1813
1814
		// View currently only available via ajax
1815
		return $form->forTemplate();
1816
	}
1817
1818
	/**
1819
	 * @param SS_HTTPRequest $request
1820
	 * @param DNDataArchive|null $dataArchive Only set when method is called manually, otherwise the state is inferred
1821
	 *        from the request data.
1822
	 * @return Form
1823
	 */
1824
	public function getDeleteForm(SS_HTTPRequest $request, DNDataArchive $dataArchive = null) {
1825
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1826
1827
		// Performs canView permission check by limiting visible projects
1828
		$project = $this->getCurrentProject();
1829
		if(!$project) {
1830
			return $this->project404Response();
1831
		}
1832
1833
		$snapshotDeleteWarning = '<div class="alert alert-warning">'
1834
			. 'Are you sure you want to permanently delete this snapshot from this archive area?'
1835
			. '</div>';
1836
1837
		$form = Form::create(
1838
			$this,
1839
			'DeleteForm',
1840
			FieldList::create(
1841
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1842
				LiteralField::create('Warning', $snapshotDeleteWarning)
1843
			),
1844
			FieldList::create(
1845
				FormAction::create('doDelete', 'Delete')
1846
					->addExtraClass('btn')
1847
			)
1848
		);
1849
		$form->setFormAction($project->Link() . '/DeleteForm');
1850
1851
		return $form;
1852
	}
1853
1854
	/**
1855
	 * @param array $data
1856
	 * @param Form $form
1857
	 *
1858
	 * @return bool|SS_HTTPResponse
1859
	 * @throws SS_HTTPResponse_Exception
1860
	 */
1861
	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...
1862
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1863
1864
		// Performs canView permission check by limiting visible projects
1865
		$project = $this->getCurrentProject();
1866
		if(!$project) {
1867
			return $this->project404Response();
1868
		}
1869
1870
		$dataArchive = null;
1871
1872
		if(
1873
			isset($data['DataArchiveID'])
1874
			&& is_numeric($data['DataArchiveID'])
1875
		) {
1876
			$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1877
		}
1878
1879
		if(!$dataArchive) {
1880
			throw new LogicException('Invalid data archive');
1881
		}
1882
1883
		if(!$dataArchive->canDelete()) {
1884
			throw new SS_HTTPResponse_Exception('Not allowed to delete archive', 403);
1885
		}
1886
1887
		$dataArchive->delete();
1888
1889
		return $this->redirectBack();
1890
	}
1891
1892
	/**
1893
	 * View a form to move a specific {@link DataArchive}.
1894
	 *
1895
	 * @param SS_HTTPRequest $request
1896
	 *
1897
	 * @return HTMLText
1898
	 * @throws SS_HTTPResponse_Exception
1899
	 */
1900 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...
1901
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1902
1903
		/** @var DNDataArchive $dataArchive */
1904
		$dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
1905
1906
		if(!$dataArchive) {
1907
			throw new SS_HTTPResponse_Exception('Archive not found', 404);
1908
		}
1909
1910
		// We check for canDownload because that implies access to the data.
1911
		if(!$dataArchive->canDownload()) {
1912
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1913
		}
1914
1915
		$form = $this->getMoveForm($this->request, $dataArchive);
1916
1917
		// View currently only available via ajax
1918
		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...
1919
	}
1920
1921
	/**
1922
	 * Build snapshot move form.
1923
	 *
1924
	 * @param SS_HTTPRequest $request
1925
	 * @param DNDataArchive|null $dataArchive
1926
	 *
1927
	 * @return Form|SS_HTTPResponse
1928
	 */
1929
	public function getMoveForm(SS_HTTPRequest $request, DNDataArchive $dataArchive = null) {
1930
		$dataArchive = $dataArchive ? $dataArchive : DNDataArchive::get()->byId($request->requestVar('DataArchiveID'));
1931
1932
		$envs = $dataArchive->validTargetEnvironments();
1933
		if(!$envs) {
1934
			return $this->environment404Response();
1935
		}
1936
1937
		$warningMessage = '<div class="alert alert-warning"><strong>Warning:</strong> This will make the snapshot '
1938
			. 'available to people with access to the target environment.<br>By pressing "Change ownership" you '
1939
			. 'confirm that you have considered data confidentiality regulations.</div>';
1940
1941
		$form = Form::create(
1942
			$this,
1943
			'MoveForm',
1944
			FieldList::create(
1945
				HiddenField::create('DataArchiveID', null, $dataArchive->ID),
1946
				LiteralField::create('Warning', $warningMessage),
1947
				DropdownField::create('EnvironmentID', 'Environment', $envs->map())
1948
					->setEmptyString('Select an environment')
1949
			),
1950
			FieldList::create(
1951
				FormAction::create('doMove', 'Change ownership')
1952
					->addExtraClass('btn')
1953
			)
1954
		);
1955
		$form->setFormAction($this->getCurrentProject()->Link() . '/MoveForm');
1956
1957
		return $form;
1958
	}
1959
1960
	/**
1961
	 * @param array $data
1962
	 * @param Form $form
1963
	 *
1964
	 * @return bool|SS_HTTPResponse
1965
	 * @throws SS_HTTPResponse_Exception
1966
	 * @throws ValidationException
1967
	 * @throws null
1968
	 */
1969
	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...
1970
		$this->setCurrentActionType(self::ACTION_SNAPSHOT);
1971
1972
		// Performs canView permission check by limiting visible projects
1973
		$project = $this->getCurrentProject();
1974
		if(!$project) {
1975
			return $this->project404Response();
1976
		}
1977
1978
		/** @var DNDataArchive $dataArchive */
1979
		$dataArchive = DNDataArchive::get()->byId($data['DataArchiveID']);
1980
		if(!$dataArchive) {
1981
			throw new LogicException('Invalid data archive');
1982
		}
1983
1984
		// We check for canDownload because that implies access to the data.
1985
		if(!$dataArchive->canDownload()) {
1986
			throw new SS_HTTPResponse_Exception('Not allowed to access archive', 403);
1987
		}
1988
1989
		// Validate $data['EnvironmentID'] by checking against $validEnvs.
1990
		$validEnvs = $dataArchive->validTargetEnvironments();
1991
		$environment = $validEnvs->find('ID', $data['EnvironmentID']);
1992
		if(!$environment) {
1993
			throw new LogicException('Invalid environment');
1994
		}
1995
1996
		$dataArchive->EnvironmentID = $environment->ID;
1997
		$dataArchive->write();
1998
1999
		return $this->redirectBack();
2000
	}
2001
2002
	/**
2003
	 * Returns an error message if redis is unavailable
2004
	 *
2005
	 * @return string
2006
	 */
2007
	public static function RedisUnavailable() {
2008
		try {
2009
			Resque::queues();
2010
		} catch(Exception $e) {
2011
			return $e->getMessage();
2012
		}
2013
		return '';
2014
	}
2015
2016
	/**
2017
	 * Returns the number of connected Redis workers
2018
	 *
2019
	 * @return int
2020
	 */
2021
	public static function RedisWorkersCount() {
2022
		return count(Resque_Worker::all());
2023
	}
2024
2025
	/**
2026
	 * @return array
2027
	 */
2028
	public function providePermissions() {
2029
		return array(
2030
			self::DEPLOYNAUT_BYPASS_PIPELINE => array(
2031
				'name' => "Bypass Pipeline",
2032
				'category' => "Deploynaut",
2033
				'help' => "Enables users to directly initiate deployments, bypassing any pipeline",
2034
			),
2035
			self::DEPLOYNAUT_DRYRUN_PIPELINE => array(
2036
				'name' => 'Dry-run Pipeline',
2037
				'category' => 'Deploynaut',
2038
				'help' => 'Enable dry-run execution of pipelines for testing'
2039
			),
2040
			self::DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS => array(
2041
				'name' => "Access to advanced deploy options",
2042
				'category' => "Deploynaut",
2043
			),
2044
2045
			// Permissions that are intended to be added to the roles.
2046
			self::ALLOW_PROD_DEPLOYMENT => array(
2047
				'name' => "Ability to deploy to production environments",
2048
				'category' => "Deploynaut",
2049
			),
2050
			self::ALLOW_NON_PROD_DEPLOYMENT => array(
2051
				'name' => "Ability to deploy to non-production environments",
2052
				'category' => "Deploynaut",
2053
			),
2054
			self::ALLOW_PROD_SNAPSHOT => array(
2055
				'name' => "Ability to make production snapshots",
2056
				'category' => "Deploynaut",
2057
			),
2058
			self::ALLOW_NON_PROD_SNAPSHOT => array(
2059
				'name' => "Ability to make non-production snapshots",
2060
				'category' => "Deploynaut",
2061
			),
2062
			self::ALLOW_CREATE_ENVIRONMENT => array(
2063
				'name' => "Ability to create environments",
2064
				'category' => "Deploynaut",
2065
			),
2066
		);
2067
	}
2068
2069
	/**
2070
	 * @return DNProject|null
2071
	 */
2072
	public function getCurrentProject() {
2073
		$projectName = trim($this->getRequest()->param('Project'));
2074
		if(!$projectName) {
2075
			return null;
2076
		}
2077
		if(empty(self::$_project_cache[$projectName])) {
2078
			self::$_project_cache[$projectName] = $this->DNProjectList()->filter('Name', $projectName)->First();
2079
		}
2080
		return self::$_project_cache[$projectName];
2081
	}
2082
2083
	/**
2084
	 * @param DNProject|null $project
2085
	 * @return DNEnvironment|null
2086
	 */
2087
	public function getCurrentEnvironment(DNProject $project = null) {
2088
		if($this->getRequest()->param('Environment') === null) {
2089
			return null;
2090
		}
2091
		if($project === null) {
2092
			$project = $this->getCurrentProject();
2093
		}
2094
		// project can still be null
2095
		if($project === null) {
2096
			return null;
2097
		}
2098
		return $project->DNEnvironmentList()->filter('Name', $this->getRequest()->param('Environment'))->First();
2099
	}
2100
2101
	/**
2102
	 * This will return a const that indicates the class of action currently being performed
2103
	 *
2104
	 * Until DNRoot is de-godded, it does a bunch of different actions all in the same class.
2105
	 * So we just have each action handler calll setCurrentActionType to define what sort of
2106
	 * action it is.
2107
	 *
2108
	 * @return string - one of the consts from self::$action_types
2109
	 */
2110
	public function getCurrentActionType() {
2111
		return $this->actionType;
2112
	}
2113
2114
	/**
2115
	 * Sets the current action type
2116
	 *
2117
	 * @param string $actionType string - one of the consts from self::$action_types
2118
	 */
2119
	public function setCurrentActionType($actionType) {
2120
		$this->actionType = $actionType;
2121
	}
2122
2123
	/**
2124
	 * Helper method to allow templates to know whether they should show the 'Archive List' include or not.
2125
	 * The actual permissions are set on a per-environment level, so we need to find out if this $member can upload to
2126
	 * or download from *any* {@link DNEnvironment} that (s)he has access to.
2127
	 *
2128
	 * TODO To be replaced with a method that just returns the list of archives this {@link Member} has access to.
2129
	 *
2130
	 * @param Member|null $member The {@link Member} to check (or null to check the currently logged in Member)
2131
	 * @return boolean|null true if $member has access to upload or download to at least one {@link DNEnvironment}.
2132
	 */
2133
	public function CanViewArchives(Member $member = null) {
2134
		if($member === null) {
2135
			$member = Member::currentUser();
2136
		}
2137
2138
		if(Permission::checkMember($member, 'ADMIN')) {
2139
			return true;
2140
		}
2141
2142
		$allProjects = $this->DNProjectList();
2143
		if(!$allProjects) {
2144
			return false;
2145
		}
2146
2147
		foreach($allProjects as $project) {
2148
			if($project->Environments()) {
2149
				foreach($project->Environments() as $environment) {
2150
					if(
2151
						$environment->canRestore($member) ||
2152
						$environment->canBackup($member) ||
2153
						$environment->canUploadArchive($member) ||
2154
						$environment->canDownloadArchive($member)
2155
					) {
2156
						// We can return early as we only need to know that we can access one environment
2157
						return true;
2158
					}
2159
				}
2160
			}
2161
		}
2162
	}
2163
2164
	/**
2165
	 * Returns a list of attempted environment creations.
2166
	 *
2167
	 * @return PaginatedList
2168
	 */
2169
	public function CreateEnvironmentList() {
2170
		$project = $this->getCurrentProject();
2171
		if($project) {
2172
			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...
2173
		}
2174
		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...
2175
	}
2176
2177
	/**
2178
	 * Returns a list of all archive files that can be accessed by the currently logged-in {@link Member}
2179
	 *
2180
	 * @return PaginatedList
2181
	 */
2182
	public function CompleteDataArchives() {
2183
		$project = $this->getCurrentProject();
2184
		$archives = new ArrayList();
2185
2186
		$archiveList = $project->Environments()->relation("DataArchives");
2187
		if($archiveList->count() > 0) {
2188
			foreach($archiveList as $archive) {
2189
				if($archive->canView() && !$archive->isPending()) {
2190
					$archives->push($archive);
2191
				}
2192
			}
2193
		}
2194
		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...
2195
	}
2196
2197
	/**
2198
	 * @return PaginatedList The list of "pending" data archives which are waiting for a file
2199
	 * to be delivered offline by post, and manually uploaded into the system.
2200
	 */
2201
	public function PendingDataArchives() {
2202
		$project = $this->getCurrentProject();
2203
		$archives = new ArrayList();
2204
		foreach($project->DNEnvironmentList() as $env) {
2205
			foreach($env->DataArchives() as $archive) {
2206
				if($archive->canView() && $archive->isPending()) {
2207
					$archives->push($archive);
2208
				}
2209
			}
2210
		}
2211
		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...
2212
	}
2213
2214
	/**
2215
	 * @return PaginatedList
2216
	 */
2217
	public function DataTransferLogs() {
2218
		$project = $this->getCurrentProject();
2219
2220
		$transfers = DNDataTransfer::get()->filterByCallback(function($record) use($project) {
2221
			return
2222
				$record->Environment()->Project()->ID == $project->ID && // Ensure only the current Project is shown
2223
				(
2224
					$record->Environment()->canRestore() || // Ensure member can perform an action on the transfers env
2225
					$record->Environment()->canBackup() ||
2226
					$record->Environment()->canUploadArchive() ||
2227
					$record->Environment()->canDownloadArchive()
2228
				);
2229
		});
2230
2231
		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...
2232
	}
2233
2234
	/**
2235
	 * @return null|PaginatedList
2236
	 */
2237
	public function DeployHistory() {
2238
		if($env = $this->getCurrentEnvironment()) {
2239
			$history = $env->DeployHistory();
2240
			if($history->count() > 0) {
2241
				$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...
2242
				$pagination->setPageLength(8);
2243
				return $pagination;
2244
			}
2245
		}
2246
		return null;
2247
	}
2248
2249
	/**
2250
	 * @return SS_HTTPResponse
2251
	 */
2252
	protected function project404Response() {
2253
		return new SS_HTTPResponse(
2254
			"Project '" . Convert::raw2xml($this->getRequest()->param('Project')) . "' not found.",
2255
			404
2256
		);
2257
	}
2258
2259
	/**
2260
	 * @return SS_HTTPResponse
2261
	 */
2262
	protected function environment404Response() {
2263
		$envName = Convert::raw2xml($this->getRequest()->param('Environment'));
2264
		return new SS_HTTPResponse("Environment '" . $envName . "' not found.", 404);
2265
	}
2266
2267
	/**
2268
	 * @param string $status
2269
	 * @param string $content
2270
	 *
2271
	 * @return string
2272
	 */
2273
	protected function sendResponse($status, $content) {
2274
		// strip excessive newlines
2275
		$content = preg_replace('/(?:(?:\r\n|\r|\n)\s*){2}/s', "\n", $content);
2276
2277
		$sendJSON = (strpos($this->getRequest()->getHeader('Accept'), 'application/json') !== false)
2278
			|| $this->getRequest()->getExtension() == 'json';
2279
2280
		if(!$sendJSON) {
2281
			$this->response->addHeader("Content-type", "text/plain");
2282
			return $content;
2283
		}
2284
		$this->response->addHeader("Content-type", "application/json");
2285
		return json_encode(array(
2286
			'status' => $status,
2287
			'content' => $content,
2288
		));
2289
	}
2290
2291
	/**
2292
	 * Validate the snapshot mode
2293
	 *
2294
	 * @param string $mode
2295
	 */
2296
	protected function validateSnapshotMode($mode) {
2297
		if(!in_array($mode, array('all', 'assets', 'db'))) {
2298
			throw new LogicException('Invalid mode');
2299
		}
2300
	}
2301
2302
	/**
2303
	 * @param string $sectionName
2304
	 * @param string $title
2305
	 *
2306
	 * @return SS_HTTPResponse
2307
	 */
2308
	protected function getCustomisedViewSection($sectionName, $title = '', $data = array()) {
2309
		// Performs canView permission check by limiting visible projects
2310
		$project = $this->getCurrentProject();
2311
		if(!$project) {
2312
			return $this->project404Response();
2313
		}
2314
		$data[$sectionName] = 1;
2315
2316
		if($this !== '') {
2317
			$data['Title'] = $title;
2318
		}
2319
2320
		return $this->render($data);
2321
	}
2322
2323
	/**
2324
	 * Get items for the ambient menu that should be accessible from all pages.
2325
	 *
2326
	 * @return ArrayList
2327
	 */
2328
	public function AmbientMenu() {
2329
		$list = new ArrayList();
2330
2331
		if (Member::currentUserID()) {
2332
			$list->push(new ArrayData(array(
2333
				'Classes' => 'logout',
2334
				'FaIcon' => 'sign-out',
2335
				'Link' => 'Security/logout',
2336
				'Title' => 'Log out',
2337
				'IsCurrent' => false,
2338
				'IsSection' => false
2339
			)));
2340
		}
2341
2342
		$this->extend('updateAmbientMenu', $list);
2343
		return $list;
2344
	}
2345
2346
	/**
2347
	 * Create project action.
2348
	 *
2349
	 * @return SS_HTTPResponse
2350
	 */
2351
	public function createproject(SS_HTTPRequest $request) {
2352
		if($this->canCreateProjects()) {
2353
			return $this->render(['CurrentTitle' => 'Create Stack']);
2354
		}
2355
		return $this->httpError(403);
2356
	}
2357
2358
	/**
2359
	 * Checks whether the user can create a project.
2360
	 *
2361
	 * @return bool
2362
	 */
2363
	protected function canCreateProjects($member = null) {
2364
		if(!$member) $member = Member::currentUser();
2365
		if(!$member) return false;
2366
2367
		return Permission::checkMember($member, 'ADMIN');
2368
	}
2369
		
2370
	/**
2371
	 * @return Form
2372
	 */
2373
	public function CreateProjectForm() {
2374
		$form = Form::create(
2375
			$this, 
2376
			__FUNCTION__, 
2377
			$this->getCreateProjectFormFields(), 
2378
			$this->getCreateProjectFormActions(),
2379
			new RequiredFields('Name', 'CVSPath')
2380
		);
2381
		$this->extend('updateCreateProjectForm', $form);
2382
		return $form;
2383
	}
2384
2385
	/**
2386
	 * @return FieldList
2387
	 */
2388
	protected function getCreateProjectFormFields() {
2389
		$fields = FieldList::create();
2390
		$fields->merge([
2391
			TextField::create('Name', 'Title'),
2392
			TextField::create('CVSPath', 'Git URL'),
2393
			TextField::create('Client', 'Client'),
2394
		]);
2395
		$this->extend('updateCreateProjectFormFields', $fields);
2396
		return $fields;
2397
	}
2398
2399
	/**
2400
	 * @return FieldList
2401
	 */
2402
	protected function getCreateProjectFormActions() {
2403
		$fields = FieldList::create(
2404
			FormAction::create('doCreateProject', 'Create Stack')
2405
		);
2406
		$this->extend('updateCreateProjectFormActions', $fields);
2407
		return $fields;
2408
	}
2409
2410
	/**
2411
	 * Does the actual project creation.
2412
	 *
2413
	 * @param $data array
2414
	 * @param $form Form
2415
	 *
2416
	 * @return SS_HTTPResponse
2417
	 */
2418
	public function doCreateProject($data, $form) {
2419
		$form->loadDataFrom($data);
2420
		$project = DNProject::create();
2421
2422
		$form->saveInto($project);
2423
		$this->extend('onBeforeCreateProject', $project, $data, $form);
2424
		try {
2425
			if($project->write() > 0) {
2426
				$this->extend('onAfterCreateProject', $project, $data, $form);
2427
2428
				// If an extension hasn't redirected us, we'll redirect to the project.
2429
				if(!$this->redirectedTo()) {
2430
					return $this->redirect($project->Link());
2431
				} else {
2432
					return $this->response;
2433
				}
2434
			} else {
2435
				$form->sessionMessage('Unable to write the stack to the database.', 'bad');
2436
			}
2437
		} catch (ValidationException $e) {
2438
			$form->sessionMessage($e->getMessage(), 'bad');
2439
		}
2440
		return $this->redirectBack();
2441
	}
2442
2443
	/**
2444
	 * Returns the state of a current project build.
2445
	 *
2446
	 * @param SS_HTTPRequest $request
2447
	 *
2448
	 * @return SS_HTTPResponse
2449
	 */
2450
	public function createprojectprogress(SS_HTTPRequest $request) {
2451
		$project = $this->getCurrentProject();
2452
		if(!$project) {
2453
			return $this->httpError(404);
2454
		}
2455
2456
		$envCreations = $project->getInitialEnvironmentCreations();
2457
		$complete = array();
2458
		$inProgress = array();
2459
		$failed = array();
2460
		if($envCreations->count() > 0) {
2461
			foreach($envCreations as $env) {
2462
				$data = unserialize($env->Data);
2463
				if(!isset($data['Name'])) {
2464
					$data['Name'] = 'Unknown';
2465
				}
2466
				switch($env->ResqueStatus()) {
2467
					case "Queued":
2468
					case "Running":
2469
						$inProgress[$env->ID] = Convert::raw2xml($env->ResqueStatus());
2470
						break;
2471
					case "Complete":
2472
						$complete[$env->ID] = Convert::raw2xml($data['Name']);
2473
						break;
2474
					case "Failed":
2475
					case "Invalid":
2476
					default:
2477
						$failed[$env->ID] = Convert::raw2xml($data['Name']);
2478
				}
2479
			}
2480
		}
2481
2482
		$data = [
2483
			'complete' => $project->isProjectReady(),
2484
			'progress' => [
2485
				'environments' => [
2486
					'complete' => $complete,
2487
					'inProgress' => $inProgress,
2488
					'failed' => $failed,
2489
				]
2490
			]
2491
		];
2492
		$this->extend('updateCreateProjectProgressData', $data);	
2493
2494
		$response = $this->getResponse();
2495
		$response->addHeader('Content-Type', 'application/json');
2496
		$response->setBody(json_encode($data));
2497
		return $response;
2498
	}
2499
2500
	public function checkrepoaccess(SS_HTTPRequest $request) {
2501
		$project = $this->getCurrentProject();
2502
		if(!$project) {
2503
			return $this->httpError(404);
2504
		}
2505
2506
		if($project->CVSPath) {
2507
			$clone = new CloneGitRepo();
2508
			$clone->args = array(
2509
				'repo' => $project->CVSPath,
2510
				'path' => $project->getLocalCVSPath(),
2511
				'env' => $project->getProcessEnv(),
2512
			);
2513
2514
			try {
2515
				$clone->perform();
2516
				$canAccessRepo = true;
2517
			} catch (RuntimeException $e) {
2518
				$canAccessRepo = false;
2519
			}
2520
			$data = ['canAccessRepo' => $canAccessRepo];
2521
		} else {
2522
			$data = ['canAccessRepo' => false];
2523
		}
2524
2525
		$response = $this->getResponse();
2526
		$response->addHeader("Content-Type", "application/json");
2527
		$response->setBody(json_encode($data));
2528
		return $response;
2529
	}
2530
2531
}
2532
2533