Completed
Pull Request — master (#489)
by Helpful
03:18
created

DNRoot::deletesnapshot()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 9

Duplication

Lines 19
Ratio 95 %
Metric Value
dl 19
loc 20
rs 9.4286
cc 3
eloc 9
nc 3
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
    /**
14
     * @const string - action type for actions that perform deployments
15
     */
16
    const ACTION_DEPLOY = 'deploy';
17
18
    /**
19
     * @const string - action type for actions that manipulate snapshots
20
     */
21
    const ACTION_SNAPSHOT = 'snapshot';
22
23
    const ACTION_ENVIRONMENTS = 'createenv';
24
25
    /**
26
     * @var string
27
     */
28
    private $actionType = self::ACTION_DEPLOY;
29
30
    /**
31
     * Bypass pipeline permission code
32
     */
33
    const DEPLOYNAUT_BYPASS_PIPELINE = 'DEPLOYNAUT_BYPASS_PIPELINE';
34
35
    /**
36
     * Allow dryrun of pipelines
37
     */
38
    const DEPLOYNAUT_DRYRUN_PIPELINE = 'DEPLOYNAUT_DRYRUN_PIPELINE';
39
40
    /**
41
     * Allow advanced options on deployments
42
     */
43
    const DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS = 'DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS';
44
45
    const ALLOW_PROD_DEPLOYMENT = 'ALLOW_PROD_DEPLOYMENT';
46
    const ALLOW_NON_PROD_DEPLOYMENT = 'ALLOW_NON_PROD_DEPLOYMENT';
47
    const ALLOW_PROD_SNAPSHOT = 'ALLOW_PROD_SNAPSHOT';
48
    const ALLOW_NON_PROD_SNAPSHOT = 'ALLOW_NON_PROD_SNAPSHOT';
49
    const ALLOW_CREATE_ENVIRONMENT = 'ALLOW_CREATE_ENVIRONMENT';
50
51
    /**
52
     * @var array
53
     */
54
    private static $allowed_actions = array(
55
        'projects',
56
        'nav',
57
        'update',
58
        'project',
59
        'toggleprojectstar',
60
        'branch',
61
        'environment',
62
        'abortpipeline',
63
        'pipeline',
64
        'pipelinelog',
65
        'metrics',
66
        'createenvlog',
67
        'createenv',
68
        'getDeployForm',
69
        'doDeploy',
70
        'deploy',
71
        'deploylog',
72
        'getDataTransferForm',
73
        'transfer',
74
        'transferlog',
75
        'snapshots',
76
        'createsnapshot',
77
        'snapshotslog',
78
        'uploadsnapshot',
79
        'getCreateEnvironmentForm',
80
        'getUploadSnapshotForm',
81
        'getPostSnapshotForm',
82
        'getDataTransferRestoreForm',
83
        'getDeleteForm',
84
        'getMoveForm',
85
        'restoresnapshot',
86
        'deletesnapshot',
87
        'movesnapshot',
88
        'postsnapshotsuccess',
89
        'gitRevisions',
90
        'deploySummary',
91
        'startDeploy',
92
        'createproject',
93
        'CreateProjectForm',
94
        'createprojectprogress',
95
        'checkrepoaccess',
96
    );
97
98
    /**
99
     * URL handlers pretending that we have a deep URL structure.
100
     */
101
    private static $url_handlers = array(
102
        'project/$Project/environment/$Environment/DeployForm' => 'getDeployForm',
103
        'project/$Project/createsnapshot/DataTransferForm' => 'getDataTransferForm',
104
        'project/$Project/DataTransferForm' => 'getDataTransferForm',
105
        'project/$Project/DataTransferRestoreForm' => 'getDataTransferRestoreForm',
106
        'project/$Project/DeleteForm' => 'getDeleteForm',
107
        'project/$Project/MoveForm' => 'getMoveForm',
108
        'project/$Project/UploadSnapshotForm' => 'getUploadSnapshotForm',
109
        'project/$Project/PostSnapshotForm' => 'getPostSnapshotForm',
110
        'project/$Project/environment/$Environment/metrics' => 'metrics',
111
        'project/$Project/environment/$Environment/pipeline/$Identifier//$Action/$ID/$OtherID' => 'pipeline',
112
        'project/$Project/environment/$Environment/deploy_summary' => 'deploySummary',
113
        'project/$Project/environment/$Environment/git_revisions' => 'gitRevisions',
114
        'project/$Project/environment/$Environment/start-deploy' => 'startDeploy',
115
        'project/$Project/environment/$Environment/deploy/$Identifier/log' => 'deploylog',
116
        'project/$Project/environment/$Environment/deploy/$Identifier' => 'deploy',
117
        'project/$Project/transfer/$Identifier/log' => 'transferlog',
118
        'project/$Project/transfer/$Identifier' => 'transfer',
119
        'project/$Project/environment/$Environment' => 'environment',
120
        'project/$Project/createenv/$Identifier/log' => 'createenvlog',
121
        'project/$Project/createenv/$Identifier' => 'createenv',
122
        'project/$Project/CreateEnvironmentForm' => 'getCreateEnvironmentForm',
123
        'project/$Project/branch' => 'branch',
124
        'project/$Project/build/$Build' => 'build',
125
        'project/$Project/restoresnapshot/$DataArchiveID' => 'restoresnapshot',
126
        'project/$Project/deletesnapshot/$DataArchiveID' => 'deletesnapshot',
127
        'project/$Project/movesnapshot/$DataArchiveID' => 'movesnapshot',
128
        'project/$Project/update' => 'update',
129
        'project/$Project/snapshots' => 'snapshots',
130
        'project/$Project/createsnapshot' => 'createsnapshot',
131
        'project/$Project/uploadsnapshot' => 'uploadsnapshot',
132
        'project/$Project/snapshotslog' => 'snapshotslog',
133
        'project/$Project/postsnapshotsuccess/$DataArchiveID' => 'postsnapshotsuccess',
134
        'project/$Project/star' => 'toggleprojectstar',
135
        'project/$Project/createprojectprogress' => 'createprojectprogress',
136
        'project/$Project/checkrepoaccess' => 'checkrepoaccess',
137
        'project/$Project' => 'project',
138
        'nav/$Project' => 'nav',
139
        'projects' => 'projects',
140
    );
141
142
    /**
143
     * @var array
144
     */
145
    protected static $_project_cache = array();
146
147
    /**
148
     * @var array
149
     */
150
    private static $support_links = array();
151
152
    /**
153
     * @var array
154
     */
155
    private static $platform_specific_strings = array();
156
157
    /**
158
     * @var array
159
     */
160
    private static $action_types = array(
161
        self::ACTION_DEPLOY,
162
        self::ACTION_SNAPSHOT
163
    );
164
165
    /**
166
     * @var DNData
167
     */
168
    protected $data;
169
170
    /**
171
     * Include requirements that deploynaut needs, such as javascript.
172
     */
173
    public static function include_requirements()
174
    {
175
176
        // JS should always go to the bottom, otherwise there's the risk that Requirements
177
        // puts them halfway through the page to the nearest <script> tag. We don't want that.
178
        Requirements::set_force_js_to_bottom(true);
179
180
        // todo these should be bundled into the same JS as the others in "static" below.
181
        // We've deliberately not used combined_files as it can mess with some of the JS used
182
        // here and cause sporadic errors.
183
        Requirements::javascript('deploynaut/javascript/jquery.js');
184
        Requirements::javascript('deploynaut/javascript/bootstrap.js');
185
        Requirements::javascript('deploynaut/javascript/q.js');
186
        Requirements::javascript('deploynaut/javascript/tablefilter.js');
187
        Requirements::javascript('deploynaut/javascript/deploynaut.js');
188
        Requirements::javascript('deploynaut/javascript/react-with-addons.js');
189
        Requirements::javascript('deploynaut/javascript/bootstrap.file-input.js');
190
        Requirements::javascript('deploynaut/thirdparty/select2/dist/js/select2.min.js');
191
        Requirements::javascript('deploynaut/javascript/material.js');
192
193
        // Load the buildable dependencies only if not loaded centrally.
194
        if (!is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'static')) {
195
            if (\Director::isDev()) {
196
                \Requirements::javascript('deploynaut/static/bundle-debug.js');
197
            } else {
198
                \Requirements::javascript('deploynaut/static/bundle.js');
199
            }
200
        }
201
202
        Requirements::css('deploynaut/static/style.css');
203
    }
204
205
    /**
206
     * Check for feature flags:
207
     * - FLAG_SNAPSHOTS_ENABLED: set to true to enable globally
208
     * - FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS: set to semicolon-separated list of email addresses of allowed users.
209
     *
210
     * @return boolean
211
     */
212
    public static function FlagSnapshotsEnabled()
213
    {
214
        if (defined('FLAG_SNAPSHOTS_ENABLED') && FLAG_SNAPSHOTS_ENABLED) {
215
            return true;
216
        }
217
        if (defined('FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS') && FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS) {
218
            $allowedMembers = explode(';', FLAG_SNAPSHOTS_ENABLED_FOR_MEMBERS);
219
            $member = Member::currentUser();
220
            if ($allowedMembers && $member && in_array($member->Email, $allowedMembers)) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $allowedMembers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
221
                return true;
222
            }
223
        }
224
        return false;
225
    }
226
227
    /**
228
     * @return ArrayList
229
     */
230
    public static function get_support_links()
231
    {
232
        $supportLinks = self::config()->support_links;
233
        if ($supportLinks) {
234
            return new ArrayList($supportLinks);
235
        }
236
    }
237
238
    /**
239
     * @return array
240
     */
241
    public static function get_template_global_variables()
242
    {
243
        return array(
244
            'RedisUnavailable' => 'RedisUnavailable',
245
            'RedisWorkersCount' => 'RedisWorkersCount',
246
            'SidebarLinks' => 'SidebarLinks',
247
            "SupportLinks" => 'get_support_links'
248
        );
249
    }
250
251
    /**
252
     */
253
    public function init()
254
    {
255
        parent::init();
256
257
        if (!Member::currentUser() && !Session::get('AutoLoginHash')) {
258
            return Security::permissionFailure();
259
        }
260
261
        // Block framework jquery
262
        Requirements::block(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
263
264
        self::include_requirements();
265
    }
266
267
    /**
268
     * @return string
269
     */
270
    public function Link()
271
    {
272
        return "naut/";
273
    }
274
275
    /**
276
     * Actions
277
     *
278
     * @param SS_HTTPRequest $request
279
     * @return \SS_HTTPResponse
280
     */
281
    public function index(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
282
    {
283
        return $this->redirect($this->Link() . 'projects/');
284
    }
285
286
    /**
287
     * Action
288
     *
289
     * @param SS_HTTPRequest $request
290
     * @return string - HTML
291
     */
292
    public function projects(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
293
    {
294
        // Performs canView permission check by limiting visible projects in DNProjectsList() call.
295
        return $this->customise(array(
0 ignored issues
show
Bug introduced by
The method render() does not exist on ViewableData_Customised. Did you maybe mean renderWith()?

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...
296
            'Title' => 'Projects',
297
        ))->render();
298
    }
299
300
    /**
301
     * @param SS_HTTPRequest $request
302
     * @return HTMLText
303
     */
304
    public function nav(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
305
    {
306
        return $this->renderWith('Nav');
307
    }
308
309
    /**
310
     * Return a link to the navigation template used for AJAX requests.
311
     * @return string
312
     */
313
    public function NavLink()
314
    {
315
        $currentProject = $this->getCurrentProject();
316
        $projectName = $currentProject ? $currentProject->Name : null;
317
        return Controller::join_links(Director::absoluteBaseURL(), 'naut', 'nav', $projectName);
318
    }
319
320
    /**
321
     * Action
322
     *
323
     * @param SS_HTTPRequest $request
324
     * @return SS_HTTPResponse - HTML
325
     */
326
    public function snapshots(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
327
    {
328
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
329
        return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots');
330
    }
331
332
    /**
333
     * Action
334
     *
335
     * @param SS_HTTPRequest $request
336
     * @return string - HTML
337
     */
338 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...
339
    {
340
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
341
342
        // Performs canView permission check by limiting visible projects
343
        $project = $this->getCurrentProject();
344
        if (!$project) {
345
            return $this->project404Response();
346
        }
347
348
        if (!$project->canBackup()) {
349
            return new SS_HTTPResponse("Not allowed to create snapshots on any environments", 401);
350
        }
351
352
        return $this->customise(array(
0 ignored issues
show
Bug introduced by
The method render() does not exist on ViewableData_Customised. Did you maybe mean renderWith()?

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...
353
            'Title' => 'Create Data Snapshot',
354
            'SnapshotsSection' => 1,
355
            'DataTransferForm' => $this->getDataTransferForm($request)
356
        ))->render();
357
    }
358
359
    /**
360
     * Action
361
     *
362
     * @param SS_HTTPRequest $request
363
     * @return string - HTML
364
     */
365 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...
366
    {
367
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
368
369
        // Performs canView permission check by limiting visible projects
370
        $project = $this->getCurrentProject();
371
        if (!$project) {
372
            return $this->project404Response();
373
        }
374
375
        if (!$project->canUploadArchive()) {
376
            return new SS_HTTPResponse("Not allowed to upload", 401);
377
        }
378
379
        return $this->customise(array(
0 ignored issues
show
Bug introduced by
The method render() does not exist on ViewableData_Customised. Did you maybe mean renderWith()?

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...
380
            'SnapshotsSection' => 1,
381
            'UploadSnapshotForm' => $this->getUploadSnapshotForm($request),
382
            'PostSnapshotForm' => $this->getPostSnapshotForm($request)
383
        ))->render();
384
    }
385
386
    /**
387
     * Return the upload limit for snapshot uploads
388
     * @return string
389
     */
390
    public function UploadLimit()
391
    {
392
        return File::format_size(min(
393
            File::ini2bytes(ini_get('upload_max_filesize')),
394
            File::ini2bytes(ini_get('post_max_size'))
395
        ));
396
    }
397
398
    /**
399
     * Construct the upload form.
400
     *
401
     * @param SS_HTTPRequest $request
402
     * @return Form
403
     */
404
    public function getUploadSnapshotForm(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
405
    {
406
        // Performs canView permission check by limiting visible projects
407
        $project = $this->getCurrentProject();
408
        if (!$project) {
409
            return $this->project404Response();
410
        }
411
412
        if (!$project->canUploadArchive()) {
413
            return new SS_HTTPResponse("Not allowed to upload", 401);
414
        }
415
416
        // Framing an environment as a "group of people with download access"
417
        // makes more sense to the user here, while still allowing us to enforce
418
        // environment specific restrictions on downloading the file later on.
419
        $envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
420
            return $item->canUploadArchive();
421
        });
422
        $envsMap = array();
423
        foreach ($envs as $env) {
424
            $envsMap[$env->ID] = $env->Name;
425
        }
426
427
        $maxSize = min(File::ini2bytes(ini_get('upload_max_filesize')), File::ini2bytes(ini_get('post_max_size')));
428
        $fileField = DataArchiveFileField::create('ArchiveFile', 'File');
429
        $fileField->getValidator()->setAllowedExtensions(array('sspak'));
430
        $fileField->getValidator()->setAllowedMaxFileSize(array('*' => $maxSize));
431
432
        $form = Form::create(
433
            $this,
434
            'UploadSnapshotForm',
435
            FieldList::create(
436
                $fileField,
437
                DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
438
                DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
439
                    ->setEmptyString('Select an environment')
440
            ),
441
            FieldList::create(
442
                FormAction::create('doUploadSnapshot', 'Upload File')
443
                    ->addExtraClass('btn')
444
            ),
445
            RequiredFields::create('ArchiveFile')
446
        );
447
448
        $form->disableSecurityToken();
449
        $form->addExtraClass('fields-wide');
450
        // Tweak the action so it plays well with our fake URL structure.
451
        $form->setFormAction($project->Link() . '/UploadSnapshotForm');
452
453
        return $form;
454
    }
455
456
    /**
457
     * @param array $data
458
     * @param Form $form
459
     *
460
     * @return bool|HTMLText|SS_HTTPResponse
461
     */
462
    public function doUploadSnapshot($data, Form $form)
463
    {
464
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
465
466
        // Performs canView permission check by limiting visible projects
467
        $project = $this->getCurrentProject();
468
        if (!$project) {
469
            return $this->project404Response();
470
        }
471
472
        $validEnvs = $project->DNEnvironmentList()
473
            ->filterByCallback(function ($item) {
474
                return $item->canUploadArchive();
475
            });
476
477
        // Validate $data['EnvironmentID'] by checking against $validEnvs.
478
        $environment = $validEnvs->find('ID', $data['EnvironmentID']);
479
        if (!$environment) {
480
            throw new LogicException('Invalid environment');
481
        }
482
483
        $this->validateSnapshotMode($data['Mode']);
484
485
        $dataArchive = DNDataArchive::create(array(
486
            'AuthorID' => Member::currentUserID(),
487
            'EnvironmentID' => $data['EnvironmentID'],
488
            'IsManualUpload' => true,
489
        ));
490
        // needs an ID and transfer to determine upload path
491
        $dataArchive->write();
492
        $dataTransfer = DNDataTransfer::create(array(
493
            'AuthorID' => Member::currentUserID(),
494
            'Mode' => $data['Mode'],
495
            'Origin' => 'ManualUpload',
496
            'EnvironmentID' => $data['EnvironmentID']
497
        ));
498
        $dataTransfer->write();
499
        $dataArchive->DataTransfers()->add($dataTransfer);
500
        $form->saveInto($dataArchive);
501
        $dataArchive->write();
502
        $workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
503
504
        $cleanupFn = function () use ($workingDir, $dataTransfer, $dataArchive) {
505
            $process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
506
            $process->run();
507
            $dataTransfer->delete();
508
            $dataArchive->delete();
509
        };
510
511
        // extract the sspak contents so we can inspect them
512
        try {
513
            $dataArchive->extractArchive($workingDir);
514
        } catch (Exception $e) {
515
            $cleanupFn();
516
            $form->sessionMessage(
517
                'There was a problem trying to open your snapshot for processing. Please try uploading again',
518
                'bad'
519
            );
520
            return $this->redirectBack();
521
        }
522
523
        // validate that the sspak contents match the declared contents
524
        $result = $dataArchive->validateArchiveContents();
525
        if (!$result->valid()) {
526
            $cleanupFn();
527
            $form->sessionMessage($result->message(), 'bad');
528
            return $this->redirectBack();
529
        }
530
531
        // fix file permissions of extracted sspak files then re-build the sspak
532
        try {
533
            $dataArchive->fixArchivePermissions($workingDir);
534
            $dataArchive->setArchiveFromFiles($workingDir);
535
        } catch (Exception $e) {
536
            $cleanupFn();
537
            $form->sessionMessage(
538
                'There was a problem processing your snapshot. Please try uploading again',
539
                'bad'
540
            );
541
            return $this->redirectBack();
542
        }
543
544
        // cleanup any extracted sspak contents lying around
545
        $process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
546
        $process->run();
547
548
        return $this->customise(array(
549
            'Project' => $project,
550
            'CurrentProject' => $project,
551
            'SnapshotsSection' => 1,
552
            'DataArchive' => $dataArchive,
553
            'DataTransferRestoreForm' => $this->getDataTransferRestoreForm($this->request, $dataArchive),
554
            'BackURL' => $project->Link('snapshots')
555
        ))->renderWith(array('DNRoot_uploadsnapshot', 'DNRoot'));
556
    }
557
558
    /**
559
     * @param SS_HTTPRequest $request
560
     * @return Form
561
     */
562
    public function getPostSnapshotForm(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
563
    {
564
        // Performs canView permission check by limiting visible projects
565
        $project = $this->getCurrentProject();
566
        if (!$project) {
567
            return $this->project404Response();
568
        }
569
570
        if (!$project->canUploadArchive()) {
571
            return new SS_HTTPResponse("Not allowed to upload", 401);
572
        }
573
574
        // Framing an environment as a "group of people with download access"
575
        // makes more sense to the user here, while still allowing us to enforce
576
        // environment specific restrictions on downloading the file later on.
577
        $envs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
578
            return $item->canUploadArchive();
579
        });
580
        $envsMap = array();
581
        foreach ($envs as $env) {
582
            $envsMap[$env->ID] = $env->Name;
583
        }
584
585
        $form = Form::create(
586
            $this,
587
            'PostSnapshotForm',
588
            FieldList::create(
589
                DropdownField::create('Mode', 'What does this file contain?', DNDataArchive::get_mode_map()),
590
                DropdownField::create('EnvironmentID', 'Initial ownership of the file', $envsMap)
591
                    ->setEmptyString('Select an environment')
592
            ),
593
            FieldList::create(
594
                FormAction::create('doPostSnapshot', 'Submit request')
595
                    ->addExtraClass('btn')
596
            ),
597
            RequiredFields::create('File')
598
        );
599
600
        $form->disableSecurityToken();
601
        $form->addExtraClass('fields-wide');
602
        // Tweak the action so it plays well with our fake URL structure.
603
        $form->setFormAction($project->Link() . '/PostSnapshotForm');
604
605
        return $form;
606
    }
607
608
    /**
609
     * @param array $data
610
     * @param Form $form
611
     *
612
     * @return SS_HTTPResponse
613
     */
614
    public function doPostSnapshot($data, $form)
615
    {
616
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
617
618
        $project = $this->getCurrentProject();
619
        if (!$project) {
620
            return $this->project404Response();
621
        }
622
623
        $validEnvs = $project->DNEnvironmentList()->filterByCallback(function ($item) {
624
                return $item->canUploadArchive();
625
        });
626
627
        // Validate $data['EnvironmentID'] by checking against $validEnvs.
628
        $environment = $validEnvs->find('ID', $data['EnvironmentID']);
629
        if (!$environment) {
630
            throw new LogicException('Invalid environment');
631
        }
632
633
        $dataArchive = DNDataArchive::create(array(
634
            'UploadToken' => DNDataArchive::generate_upload_token(),
635
        ));
636
        $form->saveInto($dataArchive);
637
        $dataArchive->write();
638
639
        return $this->redirect(Controller::join_links(
640
            $project->Link(),
641
            'postsnapshotsuccess',
642
            $dataArchive->ID
643
        ));
644
    }
645
646
    /**
647
     * Action
648
     *
649
     * @param SS_HTTPRequest $request
650
     * @return SS_HTTPResponse - HTML
651
     */
652
    public function snapshotslog(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
653
    {
654
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
655
        return $this->getCustomisedViewSection('SnapshotsSection', 'Data Snapshots Log');
656
    }
657
658
    /**
659
     * @param SS_HTTPRequest $request
660
     * @return SS_HTTPResponse|string
661
     * @throws SS_HTTPResponse_Exception
662
     */
663
    public function postsnapshotsuccess(SS_HTTPRequest $request)
664
    {
665
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
666
667
        // Performs canView permission check by limiting visible projects
668
        $project = $this->getCurrentProject();
669
        if (!$project) {
670
            return $this->project404Response();
671
        }
672
673
        if (!$project->canUploadArchive()) {
674
            return new SS_HTTPResponse("Not allowed to upload", 401);
675
        }
676
677
        $dataArchive = DNDataArchive::get()->byId($request->param('DataArchiveID'));
678
        if (!$dataArchive) {
679
            return new SS_HTTPResponse("Archive not found.", 404);
680
        }
681
682
        if (!$dataArchive->canRestore()) {
683
            throw new SS_HTTPResponse_Exception('Not allowed to restore archive', 403);
684
        }
685
686
        return $this->render(array(
687
                'Title' => 'How to send us your Data Snapshot by post',
688
                'DataArchive' => $dataArchive,
689
                'Address' => Config::inst()->get('Deploynaut', 'snapshot_post_address'),
690
                'BackURL' => $project->Link(),
691
            ));
692
    }
693
694
    /**
695
     * @param SS_HTTPRequest $request
696
     * @return \SS_HTTPResponse
697
     */
698
    public function project(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
699
    {
700
        return $this->getCustomisedViewSection('ProjectOverview', '', array('IsAdmin' => Permission::check('ADMIN')));
701
    }
702
703
    /**
704
     * This action will star / unstar a project for the current member
705
     *
706
     * @param SS_HTTPRequest $request
707
     *
708
     * @return SS_HTTPResponse
709
     */
710
    public function toggleprojectstar(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
711
    {
712
        $project = $this->getCurrentProject();
713
        if (!$project) {
714
            return $this->project404Response();
715
        }
716
717
        $member = Member::currentUser();
718
        if ($member === null) {
719
            return $this->project404Response();
720
        }
721
        $favProject = $member->StarredProjects()
722
            ->filter('DNProjectID', $project->ID)
723
            ->first();
724
725
        if ($favProject) {
726
            $member->StarredProjects()->remove($favProject);
727
        } else {
728
            $member->StarredProjects()->add($project);
729
        }
730
        return $this->redirectBack();
731
    }
732
733
    /**
734
     * @param SS_HTTPRequest $request
735
     * @return \SS_HTTPResponse
736
     */
737
    public function branch(SS_HTTPRequest $request)
738
    {
739
        $project = $this->getCurrentProject();
740
        if (!$project) {
741
            return $this->project404Response();
742
        }
743
744
        $branchName = $request->getVar('name');
745
        $branch = $project->DNBranchList()->byName($branchName);
746
        if (!$branch) {
747
            return new SS_HTTPResponse("Branch '" . Convert::raw2xml($branchName) . "' not found.", 404);
748
        }
749
750
        return $this->render(array(
751
            'CurrentBranch' => $branch,
752
        ));
753
    }
754
755
    /**
756
     * @param SS_HTTPRequest $request
757
     * @return \SS_HTTPResponse
758
     */
759
    public function environment(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
760
    {
761
        // Performs canView permission check by limiting visible projects
762
        $project = $this->getCurrentProject();
763
        if (!$project) {
764
            return $this->project404Response();
765
        }
766
767
        // Performs canView permission check by limiting visible projects
768
        $env = $this->getCurrentEnvironment($project);
769
        if (!$env) {
770
            return $this->environment404Response();
771
        }
772
773
        return $this->render(array(
774
            'DNEnvironmentList' => $this->getCurrentProject()->DNEnvironmentList(),
775
            'FlagSnapshotsEnabled' => $this->FlagSnapshotsEnabled(),
776
        ));
777
    }
778
779
780
    /**
781
     * Initiate a pipeline dry run
782
     *
783
     * @param array $data
784
     * @param DeployForm $form
785
     *
786
     * @return SS_HTTPResponse
787
     */
788
    public function doDryRun($data, DeployForm $form)
789
    {
790
        return $this->beginPipeline($data, $form, true);
791
    }
792
793
    /**
794
     * Initiate a pipeline
795
     *
796
     * @param array $data
797
     * @param DeployForm $form
798
     * @return \SS_HTTPResponse
799
     */
800
    public function startPipeline($data, $form)
801
    {
802
        return $this->beginPipeline($data, $form);
803
    }
804
805
    /**
806
     * Start a pipeline
807
     *
808
     * @param array $data
809
     * @param DeployForm $form
810
     * @param bool $isDryRun
811
     * @return \SS_HTTPResponse
812
     */
813
    protected function beginPipeline($data, DeployForm $form, $isDryRun = false)
814
    {
815
        $buildName = $form->getSelectedBuild($data);
816
817
        // Performs canView permission check by limiting visible projects
818
        $project = $this->getCurrentProject();
819
        if (!$project) {
820
            return $this->project404Response();
821
        }
822
823
        // Performs canView permission check by limiting visible projects
824
        $environment = $this->getCurrentEnvironment($project);
825
        if (!$environment) {
826
            return $this->environment404Response();
827
        }
828
829
        if (!$environment->DryRunEnabled && $isDryRun) {
830
            return new SS_HTTPResponse("Dry-run for pipelines is not enabled for this environment", 404);
831
        }
832
833
        // Initiate the pipeline
834
        $sha = $project->DNBuildList()->byName($buildName);
835
        $pipeline = Pipeline::create();
836
        $pipeline->DryRun = $isDryRun;
837
        $pipeline->EnvironmentID = $environment->ID;
838
        $pipeline->AuthorID = Member::currentUserID();
839
        $pipeline->SHA = $sha->FullName();
840
        // Record build at time of execution
841
        if ($currentBuild = $environment->CurrentBuild()) {
842
            $pipeline->PreviousDeploymentID = $currentBuild->ID;
843
        }
844
        $pipeline->start(); // start() will call write(), so no need to do it here as well.
845
        return $this->redirect($environment->Link());
846
    }
847
848
    /**
849
     * @param SS_HTTPRequest $request
850
     *
851
     * @return SS_HTTPResponse
852
     * @throws SS_HTTPResponse_Exception
853
     */
854
    public function pipeline(SS_HTTPRequest $request)
855
    {
856
        $params = $request->params();
857
        $pipeline = Pipeline::get()->byID($params['Identifier']);
858
859
        if (!$pipeline || !$pipeline->ID || !$pipeline->Environment()) {
860
            throw new SS_HTTPResponse_Exception('Pipeline not found', 404);
861
        }
862
        if (!$pipeline->Environment()->canView()) {
863
            return Security::permissionFailure();
864
        }
865
866
        $environment = $pipeline->Environment();
867
        $project = $pipeline->Environment()->Project();
868
869
        if ($environment->Name != $params['Environment']) {
870
            throw new LogicException("Environment in URL doesn't match this pipeline");
871
        }
872
        if ($project->Name != $params['Project']) {
873
            throw new LogicException("Project in URL doesn't match this pipeline");
874
        }
875
876
        // Delegate to sub-requesthandler
877
        return PipelineController::create($this, $pipeline);
878
    }
879
880
    /**
881
     * Shows the creation log.
882
     *
883
     * @param SS_HTTPRequest $request
884
     * @return string
885
     */
886
    public function createenv(SS_HTTPRequest $request)
887
    {
888
        $params = $request->params();
889
        if ($params['Identifier']) {
890
            $record = DNCreateEnvironment::get()->byId($params['Identifier']);
891
892
            if (!$record || !$record->ID) {
893
                throw new SS_HTTPResponse_Exception('Create environment not found', 404);
894
            }
895
            if (!$record->canView()) {
896
                return Security::permissionFailure();
897
            }
898
899
            $project = $this->getCurrentProject();
900
            if (!$project) {
901
                return $this->project404Response();
902
            }
903
904
            if ($project->Name != $params['Project']) {
905
                throw new LogicException("Project in URL doesn't match this creation");
906
            }
907
908
            return $this->render(array(
909
                'CreateEnvironment' => $record,
910
            ));
911
        }
912
        return $this->render(array('CurrentTitle' => 'Create an environment'));
913
    }
914
915
916 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...
917
    {
918
        $this->setCurrentActionType(self::ACTION_SNAPSHOT);
919
920
        $params = $request->params();
921
        $env = DNCreateEnvironment::get()->byId($params['Identifier']);
922
923
        if (!$env || !$env->ID) {
924
            throw new SS_HTTPResponse_Exception('Log not found', 404);
925
        }
926
        if (!$env->canView()) {
927
            return Security::permissionFailure();
928
        }
929
930
        $project = $env->Project();
931
932
        if ($project->Name != $params['Project']) {
933
            throw new LogicException("Project in URL doesn't match this deploy");
934
        }
935
936
        $log = $env->log();
937
        if ($log->exists()) {
938
            $content = $log->content();
939
        } else {
940
            $content = 'Waiting for action to start';
941
        }
942
943
        return $this->sendResponse($env->ResqueStatus(), $content);
944
    }
945
946
    /**
947
     * @param SS_HTTPRequest $request
948
     * @return Form
949
     */
950
    public function getCreateEnvironmentForm(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
951
    {
952
        $this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
953
954
        $project = $this->getCurrentProject();
955
        if (!$project) {
956
            return $this->project404Response();
957
        }
958
959
        $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...
960
        if (!$envType || !class_exists($envType)) {
961
            return null;
962
        }
963
964
        $backend = Injector::inst()->get($envType);
965
        if (!($backend instanceof EnvironmentCreateBackend)) {
966
            // Only allow this for supported backends.
967
            return null;
968
        }
969
970
        $fields = $backend->getCreateEnvironmentFields($project);
971
        if (!$fields) {
972
            return null;
973
        }
974
975
        if (!$project->canCreateEnvironments()) {
976
            return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
977
        }
978
979
        $form = Form::create(
980
            $this,
981
            'CreateEnvironmentForm',
982
            $fields,
983
            FieldList::create(
984
                FormAction::create('doCreateEnvironment', 'Create')
985
                    ->addExtraClass('btn')
986
            ),
987
            $backend->getCreateEnvironmentValidator()
988
        );
989
990
        // Tweak the action so it plays well with our fake URL structure.
991
        $form->setFormAction($project->Link() . '/CreateEnvironmentForm');
992
993
        return $form;
994
    }
995
996
    /**
997
     * @param array $data
998
     * @param Form $form
999
     *
1000
     * @return bool|HTMLText|SS_HTTPResponse
1001
     */
1002
    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...
1003
    {
1004
        $this->setCurrentActionType(self::ACTION_ENVIRONMENTS);
1005
1006
        $project = $this->getCurrentProject();
1007
        if (!$project) {
1008
            return $this->project404Response();
1009
        }
1010
1011
        if (!$project->canCreateEnvironments()) {
1012
            return new SS_HTTPResponse('Not allowed to create environments for this project', 401);
1013
        }
1014
1015
        // Set the environment type so we know what we're creating.
1016
        $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...
1017
1018
        $job = DNCreateEnvironment::create();
1019
1020
        $job->Data = serialize($data);
1021
        $job->ProjectID = $project->ID;
1022
        $job->write();
1023
        $job->start();
1024
1025
        return $this->redirect($project->Link('createenv') . '/' . $job->ID);
1026
    }
1027
1028
    /**
1029
     *
1030
     * @param SS_HTTPRequest $request
1031
     * @return \SS_HTTPResponse
1032
     */
1033
    public function metrics(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
1034
    {
1035
        // Performs canView permission check by limiting visible projects
1036
        $project = $this->getCurrentProject();
1037
        if (!$project) {
1038
            return $this->project404Response();
1039
        }
1040
1041
        // Performs canView permission check by limiting visible projects
1042
        $env = $this->getCurrentEnvironment($project);
1043
        if (!$env) {
1044
            return $this->environment404Response();
1045
        }
1046
1047
        return $this->render();
1048
    }
1049
1050
    /**
1051
     * Get the DNData object.
1052
     *
1053
     * @return DNData
1054
     */
1055
    public function DNData()
1056
    {
1057
        return DNData::inst();
1058
    }
1059
1060
    /**
1061
     * Provide a list of all projects.
1062
     *
1063
     * @return SS_List
1064
     */
1065
    public function DNProjectList()
1066
    {
1067
        $memberId = Member::currentUserID();
1068
        if (!$memberId) {
1069
            return new ArrayList();
1070
        }
1071
1072
        if (Permission::check('ADMIN')) {
1073
            return DNProject::get();
1074
        }
1075
1076
        return Member::get()->filter('ID', $memberId)
1077
            ->relation('Groups')
1078
            ->relation('Projects');
1079
    }
1080
1081
    /**
1082
     * @return ArrayList
1083
     */
1084
    public function getPlatformSpecificStrings()
1085
    {
1086
        $strings = $this->config()->platform_specific_strings;
1087
        if ($strings) {
1088
            return new ArrayList($strings);
1089
        }
1090
    }
1091
1092
    /**
1093
     * Provide a list of all starred projects for the currently logged in member
1094
     *
1095
     * @return SS_List
1096
     */
1097
    public function getStarredProjects()
1098
    {
1099
        $member = Member::currentUser();
1100
        if ($member === null) {
1101
            return new ArrayList();
1102
        }
1103
1104
        $favProjects = $member->StarredProjects();
1105
1106
        $list = new ArrayList();
1107
        foreach ($favProjects as $project) {
1108
            if ($project->canView($member)) {
1109
                $list->add($project);
1110
            }
1111
        }
1112
        return $list;
1113
    }
1114
1115
    /**
1116
     * Returns top level navigation of projects.
1117
     *
1118
     * @param int $limit
1119
     *
1120
     * @return ArrayList
1121
     */
1122
    public function Navigation($limit = 5)
1123
    {
1124
        $navigation = new ArrayList();
1125
1126
        $currentProject = $this->getCurrentProject();
1127
1128
        $projects = $this->getStarredProjects();
1129
        if ($projects->count() < 1) {
1130
            $projects = $this->DNProjectList();
1131
        } else {
1132
            $limit = -1;
1133
        }
1134
1135
        if ($projects->count() > 0) {
1136
            $activeProject = false;
1137
1138
            if ($limit > 0) {
1139
                $limitedProjects = $projects->limit($limit);
1140
            } else {
1141
                $limitedProjects = $projects;
1142
            }
1143
1144
            foreach ($limitedProjects as $project) {
1145
                $isActive = $currentProject && $currentProject->ID == $project->ID;
1146
                if ($isActive) {
1147
                    $activeProject = true;
1148
                }
1149
1150
                $navigation->push(array(
1151
                    'Project' => $project,
1152
                    'IsActive' => $currentProject && $currentProject->ID == $project->ID,
1153
                ));
1154
            }
1155
1156
            // Ensure the current project is in the list
1157
            if (!$activeProject && $currentProject) {
1158
                $navigation->unshift(array(
1159
                    'Project' => $currentProject,
1160
                    'IsActive' => true,
1161
                ));
1162
                if ($limit > 0 && $navigation->count() > $limit) {
1163
                    $navigation->pop();
1164
                }
1165
            }
1166
        }
1167
1168
        return $navigation;
1169
    }
1170
1171
    /**
1172
     * Construct the deployment form
1173
     *
1174
     * @return Form
1175
     */
1176
    public function getDeployForm($request = null)
1177
    {
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
        $environment = $this->getCurrentEnvironment($project);
1187
        if (!$environment) {
1188
            return $this->environment404Response();
1189
        }
1190
1191
        if (!$environment->canDeploy()) {
1192
            return new SS_HTTPResponse("Not allowed to deploy", 401);
1193
        }
1194
1195
        // Generate the form
1196
        $form = new DeployForm($this, 'DeployForm', $environment, $project);
1197
1198
        // If this is an ajax request we don't want to submit the form - we just want to retrieve the markup.
1199
        if (
1200
            $request &&
1201
            !$request->requestVar('action_showDeploySummary') &&
1202
            $this->getRequest()->isAjax() &&
1203
            $this->getRequest()->isGET()
1204
        ) {
1205
            // We can just use the URL we're accessing
1206
            $form->setFormAction($this->getRequest()->getURL());
1207
1208
            $body = json_encode(array('Content' => $form->forAjaxTemplate()->forTemplate()));
1209
            $this->getResponse()->addHeader('Content-Type', 'application/json');
1210
            $this->getResponse()->setBody($body);
1211
            return $body;
1212
        }
1213
1214
        $form->setFormAction($this->getRequest()->getURL() . '/DeployForm');
1215
        return $form;
1216
    }
1217
1218
    /**
1219
     * @param SS_HTTPRequest $request
1220
     *
1221
     * @return SS_HTTPResponse|string
1222
     */
1223
    public function gitRevisions(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
1224
    {
1225
1226
        // Performs canView permission check by limiting visible projects
1227
        $project = $this->getCurrentProject();
1228
        if (!$project) {
1229
            return $this->project404Response();
1230
        }
1231
1232
        // Performs canView permission check by limiting visible projects
1233
        $env = $this->getCurrentEnvironment($project);
1234
        if (!$env) {
1235
            return $this->environment404Response();
1236
        }
1237
1238
        // For now only permit advanced options on one environment type, because we hacked the "full-deploy"
1239
        // checkbox in. Other environments such as the fast or capistrano one wouldn't know what to do with it.
1240
        if (get_class($env) === 'RainforestEnvironment') {
1241
            $advanced = Permission::check('DEPLOYNAUT_ADVANCED_DEPLOY_OPTIONS') ? 'true' : 'false';
1242
        } else {
1243
            $advanced = 'false';
1244
        }
1245
1246
        $tabs = array();
1247
        $id = 0;
1248
        $data = array(
1249
            'id' => ++$id,
1250
            'name' => 'Deploy the latest version of a branch',
1251
            'field_type' => 'dropdown',
1252
            'field_label' => 'Choose a branch',
1253
            'field_id' => 'branch',
1254
            'field_data' => array(),
1255
            'advanced_opts' => $advanced
1256
        );
1257
        foreach ($project->DNBranchList() as $branch) {
1258
            $sha = $branch->SHA();
1259
            $name = $branch->Name();
1260
            $branchValue = sprintf("%s (%s, %s old)",
1261
                $name,
1262
                substr($sha, 0, 8),
1263
                $branch->LastUpdated()->TimeDiff()
1264
            );
1265
            $data['field_data'][] = array(
1266
                'id' => $sha,
1267
                'text' => $branchValue
1268
            );
1269
        }
1270
        $tabs[] = $data;
1271
1272
        $data = array(
1273
            'id' => ++$id,
1274
            'name' => 'Deploy a tagged release',
1275
            'field_type' => 'dropdown',
1276
            'field_label' => 'Choose a tag',
1277
            'field_id' => 'tag',
1278
            'field_data' => array(),
1279
            'advanced_opts' => $advanced
1280
        );
1281
1282
        foreach ($project->DNTagList()->setLimit(null) as $tag) {
1283
            $name = $tag->Name();
1284
            $data['field_data'][] = array(
1285
                'id' => $tag->SHA(),
1286
                'text' => sprintf("%s", $name)
1287
            );
1288
        }
1289
1290
        // show newest tags first.
1291
        $data['field_data'] = array_reverse($data['field_data']);
1292
1293
        $tabs[] = $data;
1294
1295
        // Past deployments
1296
        $data = array(
1297
            'id' => ++$id,
1298
            'name' => 'Redeploy a release that was previously deployed (to any environment)',
1299
            'field_type' => 'dropdown',
1300
            'field_label' => 'Choose a previously deployed release',
1301
            'field_id' => 'release',
1302
            'field_data' => array(),
1303
            'advanced_opts' => $advanced
1304
        );
1305
        // We are aiming at the format:
1306
        // [{text: 'optgroup text', children: [{id: '<sha>', text: '<inner text>'}]}]
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

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

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
2484
            TextField::create('CVSPath', 'Git URL')->setDescription('Your repository URL so we can clone your code (eg. [email protected]:silverstripe/silverstripe-installer.git)')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 177 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
2485
        ]);
2486
        $this->extend('updateCreateProjectFormFields', $fields);
2487
        return $fields;
2488
    }
2489
2490
    /**
2491
     * @return FieldList
2492
     */
2493
    protected function getCreateProjectFormActions()
2494
    {
2495
        $fields = FieldList::create(
2496
            FormAction::create('doCreateProject', 'Create Stack')
2497
        );
2498
        $this->extend('updateCreateProjectFormActions', $fields);
2499
        return $fields;
2500
    }
2501
2502
    /**
2503
     * Does the actual project creation.
2504
     *
2505
     * @param $data array
2506
     * @param $form Form
2507
     *
2508
     * @return SS_HTTPResponse
2509
     */
2510
    public function doCreateProject($data, $form)
2511
    {
2512
        $form->loadDataFrom($data);
2513
        $project = DNProject::create();
2514
2515
        $form->saveInto($project);
2516
        $this->extend('onBeforeCreateProject', $project, $data, $form);
2517
        try {
2518
            if ($project->write() > 0) {
2519
                $this->extend('onAfterCreateProject', $project, $data, $form);
2520
2521
                // If an extension hasn't redirected us, we'll redirect to the project.
2522
                if (!$this->redirectedTo()) {
2523
                    return $this->redirect($project->Link());
2524
                } else {
2525
                    return $this->response;
2526
                }
2527
            } else {
2528
                $form->sessionMessage('Unable to write the stack to the database.', 'bad');
2529
            }
2530
        } catch (ValidationException $e) {
2531
            $form->sessionMessage($e->getMessage(), 'bad');
2532
        }
2533
        return $this->redirectBack();
2534
    }
2535
2536
    /**
2537
     * Returns the state of a current project build.
2538
     *
2539
     * @param SS_HTTPRequest $request
2540
     *
2541
     * @return SS_HTTPResponse
2542
     */
2543
    public function createprojectprogress(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
2544
    {
2545
        $project = $this->getCurrentProject();
2546
        if (!$project) {
2547
            return $this->httpError(404);
2548
        }
2549
2550
        $envCreations = $project->getInitialEnvironmentCreations();
2551
        $complete = array();
2552
        $inProgress = array();
2553
        $failed = array();
2554
        if ($envCreations->count() > 0) {
2555
            foreach ($envCreations as $env) {
2556
                $data = unserialize($env->Data);
2557
                if (!isset($data['Name'])) {
2558
                    $data['Name'] = 'Unknown';
2559
                }
2560
                switch ($env->ResqueStatus()) {
2561
                    case "Queued":
2562
                    case "Running":
2563
                        $inProgress[$env->ID] = Convert::raw2xml($env->ResqueStatus());
2564
                        break;
2565
                    case "Complete":
2566
                        $complete[$env->ID] = Convert::raw2xml($data['Name']);
2567
                        break;
2568
                    case "Failed":
2569
                    case "Invalid":
2570
                    default:
2571
                        $failed[$env->ID] = Convert::raw2xml($data['Name']);
2572
                }
2573
            }
2574
        }
2575
2576
        $data = [
2577
            'complete' => $project->isProjectReady(),
2578
            'progress' => [
2579
                'environments' => [
2580
                    'complete' => $complete,
2581
                    'inProgress' => $inProgress,
2582
                    'failed' => $failed,
2583
                ]
2584
            ]
2585
        ];
2586
        $this->extend('updateCreateProjectProgressData', $data);
2587
2588
        $response = $this->getResponse();
2589
        $response->addHeader('Content-Type', 'application/json');
2590
        $response->setBody(json_encode($data));
2591
        return $response;
2592
    }
2593
2594
    public function checkrepoaccess(SS_HTTPRequest $request)
1 ignored issue
show
Unused Code introduced by
The parameter $request 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...
2595
    {
2596
        $project = $this->getCurrentProject();
2597
        if (!$project) {
2598
            return $this->httpError(404);
2599
        }
2600
2601
        if ($project->CVSPath) {
2602
            $fetch = new FetchJob();
2603
            $fetch->args = array('projectID' => $project->ID);
2604
            try {
2605
                $fetch->perform();
2606
                $canAccessRepo = true;
2607
            } catch (RuntimeException $e) {
2608
                $canAccessRepo = false;
2609
            }
2610
            $data = ['canAccessRepo' => $canAccessRepo];
2611
        } else {
2612
            $data = ['canAccessRepo' => false];
2613
        }
2614
2615
        $response = $this->getResponse();
2616
        $response->addHeader("Content-Type", "application/json");
2617
        $response->setBody(json_encode($data));
2618
        return $response;
2619
    }
2620
}
2621