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

DeployForm_ValidatorBase::validationError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4286
cc 1
eloc 3
nc 1
nop 3
1
<?php
2
3
/**
4
 * Base class for a pipeline initiation validator
5
 *
6
 * @package deploynaut
7
 * @subpackage control
8
 */
9
abstract class DeployForm_ValidatorBase extends Validator
1 ignored issue
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
10
{
11
12
    /**
13
     * @param string $fieldName
14
     * @param string $message
15
     * @param string $messageType
16
     */
17
    public function validationError($fieldName, $message, $messageType = '')
18
    {
19
        // Just make any error use the form message
20
        $this->form->sessionMessage($message, $messageType);
21
        parent::validationError($fieldName, $message, $messageType);
22
    }
23
24
    /**
25
     * Validate a commit sha
26
     *
27
     * @param string $sha
28
     * @param string $field
29
     * @return boolean
30
     */
31
    protected function validateCommit($sha, $field)
32
    {
33
        // Check selected commit
34
        if (empty($sha)) {
35
            $this->validationError(
36
                $field,
37
                "No valid release selected",
38
                "required"
39
            );
40
            return false;
41
        }
42
43
        // Check validity of commit
44
        if (!preg_match('/^[a-f0-9]{40}$/', $sha)) {
45
            $this->validationError(
46
                $field,
47
                "Invalid release SHA: " . Convert::raw2xml($sha),
48
                "error"
49
            );
50
            return false;
51
        }
52
53
        return true;
54
    }
55
}
56
57
/**
58
 * Validates a multi-source commit selector
59
 *
60
 * @package deploynaut
61
 * @subpackage control
62
 */
63
class DeployForm_CommitValidator extends DeployForm_ValidatorBase
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
64
{
65
66
    public function php($data)
67
    {
68
        // Check release method
69
        if (empty($data['SelectRelease'])
70
            || !in_array($data['SelectRelease'], array('Tag', 'Branch', 'Redeploy', 'SHA', 'FilteredCommits'))
71
        ) {
72
            $method = empty($data['SelectRelease']) ? '(blank)' : $data['SelectRelease'];
73
            $this->validationError(
74
                'SelectRelease',
75
                "Bad release selection method: $method",
76
                "error"
77
            );
78
            return false;
79
        }
80
81
        // Check sha
82
        return $this->validateCommit(
83
            $this->form->getSelectedBuild($data),
84
            'SelectRelease'
85
        );
86
    }
87
}
88
89
/**
90
 * Validates a pipeline commit selector
91
 *
92
 * @package deploynaut
93
 * @subpackage control
94
 */
95
class DeployForm_PipelineValidator extends DeployForm_ValidatorBase
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
96
{
97
98
    public function php($data)
99
    {
100
        return $this->validateCommit(
101
            $this->form->getSelectedBuild($data),
102
            'FilteredCommits'
103
        );
104
    }
105
}
106
107
/**
108
 * Form for generating deployments from a specified commit
109
 *
110
 * @package deploynaut
111
 * @subpackage control
112
 */
113
class DeployForm extends Form
114
{
115
116
    /**
117
     * @param DNRoot $controller
118
     * @param string $name
119
     * @param DNEnvironment $environment
120
     * @param DNProject $project
121
     */
122
    public function __construct($controller, $name, DNEnvironment $environment, DNProject $project)
123
    {
124
        if ($environment->HasPipelineSupport()) {
125
            list($field, $validator, $actions) = $this->setupPipeline($environment, $project);
126
        } else {
127
            list($field, $validator, $actions) = $this->setupSimpleDeploy($project);
128
        }
129
        parent::__construct($controller, $name, new FieldList($field), $actions, $validator);
130
    }
131
132
    /**
133
     * @param DNProject $project
134
     *
135
     * @return array
136
     */
137
    protected function setupSimpleDeploy(DNProject $project)
138
    {
139
        // without a pipeline simply allow any commit to be selected
140
        $field = $this->buildCommitSelector($project);
141
        $validator = new DeployForm_CommitValidator();
142
        $actions = new FieldList(
143
            new FormAction('showDeploySummary', 'Plan deployment', 'Show deployment plan'),
0 ignored issues
show
Documentation introduced by
'Show deployment plan' is of type string, but the function expects a object<Form>|null.

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...
144
            new FormAction('doDeploy', 'Do deploy', 'Do deploy')
0 ignored issues
show
Documentation introduced by
'Do deploy' is of type string, but the function expects a object<Form>|null.

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...
145
        );
146
        return array($field, $validator, $actions);
147
    }
148
149
    /**
150
     * @param DNEnvironment $environment
151
     * @param DNProject $project
152
     *
153
     * @return array
154
     * @throws Exception
155
     */
156
    protected function setupPipeline(DNEnvironment $environment, DNProject $project)
157
    {
158
        // Determine if commits are filtered
159
        $canBypass = Permission::check(DNRoot::DEPLOYNAUT_BYPASS_PIPELINE);
160
        $canDryrun = $environment->DryRunEnabled && Permission::check(DNRoot::DEPLOYNAUT_DRYRUN_PIPELINE);
161
        $commits = $environment->getDependentFilteredCommits();
162
        if (empty($commits)) {
163
            // There are no filtered commits, so show all commits
164
            $field = $this->buildCommitSelector($project);
165
            $validator = new DeployForm_CommitValidator();
166
        } elseif ($canBypass) {
167
            // Build hybrid selector that allows users to follow pipeline or use any commit
168
            $field = $this->buildCommitSelector($project, $commits);
169
            $validator = new DeployForm_CommitValidator();
170
        } else {
171
            // Restrict user to only select pipeline filtered commits
172
            $field = $this->buildPipelineField($commits);
173
            $validator = new DeployForm_PipelineValidator();
174
        }
175
176
        // Generate actions allowed for this user
177
        $actions = new FieldList(
178
            FormAction::create('startPipeline', "Begin the release process on " . $environment->Name)
179
                ->addExtraClass('btn btn-primary')
180
                ->setAttribute('onclick', "return confirm('This will begin a release pipeline. Continue?');")
181
        );
182
        if ($canDryrun) {
183
            $actions->push(
184
                FormAction::create('doDryRun', "Dry-run release process")
185
                    ->addExtraClass('btn btn-info')
186
                    ->setAttribute(
187
                        'onclick',
188
                        "return confirm('This will begin a release pipeline, but with the following exclusions:\\n" .
189
                        " - No messages will be sent\\n" .
190
                        " - No capistrano actions will be invoked\\n" .
191
                        " - No deployments or snapshots will be created.');"
192
                    )
193
            );
194
        }
195
        if ($canBypass) {
196
            $actions->push(
197
                FormAction::create('showDeploySummary', "Direct deployment (bypass pipeline)")
198
                    ->addExtraClass('btn btn-warning')
199
                    ->setAttribute(
200
                        'onclick',
201
                        "return confirm('This will start a direct deployment, bypassing the pipeline " .
202
                        "process in place.\\n\\nAre you sure this is necessary?');"
203
                    )
204
            );
205
            return array($field, $validator, $actions);
206
        }
207
        return array($field, $validator, $actions);
208
    }
209
210
    /**
211
     * Construct fields to select any commit
212
     *
213
     * @param DNProject $project
214
     * @param DataList|null $pipelineCommits Optional list of pipeline-filtered commits to include
215
     * @return FormField
216
     */
217
    protected function buildCommitSelector($project, $pipelineCommits = null)
218
    {
219
        // Branches
220
        $branches = array();
221 View Code Duplication
        foreach ($project->DNBranchList() as $branch) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
222
            $sha = $branch->SHA();
223
            $name = $branch->Name();
224
            $branchValue = sprintf("%s (%s, %s old)",
225
                $name,
226
                substr($sha, 0, 8),
227
                $branch->LastUpdated()->TimeDiff()
228
            );
229
            $branches[$sha . '-' . $name] = $branchValue;
230
        }
231
232
        // Tags
233
        $tags = array();
234 View Code Duplication
        foreach ($project->DNTagList()->setLimit(null) as $tag) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
235
            $sha = $tag->SHA();
236
            $name = $tag->Name();
237
            $tagValue = sprintf("%s (%s, %s old)",
238
                $name,
239
                substr($sha, 0, 8),
240
                $branch->LastUpdated()->TimeDiff()
0 ignored issues
show
Bug introduced by
The variable $branch seems to be defined by a foreach iteration on line 221. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
241
            );
242
            $tags[$sha . '-' . $tag] = $tagValue;
243
        }
244
        $tags = array_reverse($tags);
245
246
        // Past deployments
247
        $redeploy = array();
248
        foreach ($project->DNEnvironmentList() as $dnEnvironment) {
249
            $envName = $dnEnvironment->Name;
250
            foreach ($dnEnvironment->DeployHistory() as $deploy) {
251
                $sha = $deploy->SHA;
252
                if (!isset($redeploy[$envName])) {
253
                    $redeploy[$envName] = array();
254
                }
255
                if (!isset($redeploy[$envName][$sha])) {
256
                    $pastValue = sprintf("%s (deployed %s)",
257
                        substr($sha, 0, 8),
258
                        $deploy->obj('LastEdited')->Ago()
259
                    );
260
                    $redeploy[$envName][$sha] = $pastValue;
261
                }
262
            }
263
        }
264
265
        // Merge fields
266
        $releaseMethods = array();
267
        if ($pipelineCommits) {
268
            $releaseMethods[] = new SelectionGroup_Item(
269
                'FilteredCommits',
270
                $this->buildPipelineField($pipelineCommits),
271
                'Deploy a commit prepared for this pipeline'
272
            );
273
        }
274
        if (!empty($branches)) {
275
            $releaseMethods[] = new SelectionGroup_Item(
276
                'Branch',
277
                new DropdownField('Branch', 'Select a branch', $branches),
278
                'Deploy the latest version of a branch'
279
            );
280
        }
281
        if ($tags) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $tags 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...
282
            $releaseMethods[] = new SelectionGroup_Item(
283
                'Tag',
284
                new DropdownField('Tag', 'Select a tag', $tags),
285
                'Deploy a tagged release'
286
            );
287
        }
288
        if ($redeploy) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $redeploy 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...
289
            $releaseMethods[] = new SelectionGroup_Item(
290
                'Redeploy',
291
                new GroupedDropdownField('Redeploy', 'Redeploy', $redeploy),
292
                'Redeploy a release that was previously deployed (to any environment)'
293
            );
294
        }
295
296
        $releaseMethods[] = new SelectionGroup_Item(
297
            'SHA',
298
            new Textfield('SHA', 'Please specify the full SHA'),
299
            'Deploy a specific SHA'
300
        );
301
302
        $field = new TabbedSelectionGroup('SelectRelease', $releaseMethods);
303
        $field->setValue(reset($releaseMethods)->getValue());
304
        return $field;
305
    }
306
307
    /**
308
     * Generate fields necessary to select from a filtered commit list
309
     *
310
     * @param DataList $commits List of commits
311
     * @return FormField
312
     */
313
    protected function buildPipelineField($commits)
314
    {
315
        // Get filtered commits
316
        $filteredCommits = array();
317
        foreach ($commits as $commit) {
318
            $title = sprintf(
319
                "%s (%s, %s old)",
320
                $commit->Message,
321
                substr($commit->SHA, 0, 8),
322
                $commit->dbObject('Created')->TimeDiff()
323
            );
324
            $filteredCommits[$commit->SHA] = $title;
325
        }
326
        if ($filteredCommits) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $filteredCommits 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...
327
            return new DropdownField('FilteredCommits', '', $filteredCommits);
328
        } else {
329
            return DropdownField::create('FilteredCommits)', '')
330
                ->setEmptyString('No deployments available')
331
                ->performDisabledTransformation();
332
        }
333
    }
334
335
    /**
336
     * Get the build selected from the given data
337
     *
338
     * @param array $data
339
     * @return string SHA of selected build
340
     */
341
    public function getSelectedBuild($data)
342
    {
343
        if (isset($data['SelectRelease']) && !empty($data[$data['SelectRelease']])) {
344
            // Filter out the tag/branch name if required
345
            $array = explode('-', $data[$data['SelectRelease']]);
346
            return reset($array);
347
        }
348
        if (isset($data['FilteredCommits']) && !empty($data['FilteredCommits'])) {
349
            return $data['FilteredCommits'];
350
        }
351
    }
352
}
353