Completed
Push — master ( 24f56c...117288 )
by Stig
04:12
created

DNEnvironment::envFileExists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
/**
4
 * DNEnvironment
5
 *
6
 * This dataobject represents a target environment that source code can be deployed to.
7
 * Permissions are controlled by environment, see the various many-many relationships.
8
 *
9
 * @property string $Filename
10
 * @property string $Name
11
 * @property string $URL
12
 * @property string $BackendIdentifier
13
 * @property bool $Usage
14
 *
15
 * @method DNProject Project()
16
 * @property int $ProjectID
17
 *
18
 * @method HasManyList Deployments()
19
 * @method HasManyList DataArchives()
20
 *
21
 * @method ManyManyList Viewers()
22
 * @method ManyManyList ViewerGroups()
23
 * @method ManyManyList Deployers()
24
 * @method ManyManyList DeployerGroups()
25
 * @method ManyManyList CanRestoreMembers()
26
 * @method ManyManyList CanRestoreGroups()
27
 * @method ManyManyList CanBackupMembers()
28
 * @method ManyManyList CanBackupGroups()
29
 * @method ManyManyList ArchiveUploaders()
30
 * @method ManyManyList ArchiveUploaderGroups()
31
 * @method ManyManyList ArchiveDownloaders()
32
 * @method ManyManyList ArchiveDownloaderGroups()
33
 * @method ManyManyList ArchiveDeleters()
34
 * @method ManyManyList ArchiveDeleterGroups()
35
 */
36
class DNEnvironment extends DataObject {
0 ignored issues
show
Coding Style introduced by
The property $template_file is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $allow_web_editing is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $allowed_backends is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $has_one is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $has_many is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $many_many is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $summary_fields is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $singular_name is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $plural_name is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $searchable_fields is not named in camelCase.

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

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

Loading history...
Coding Style introduced by
The property $default_sort is not named in camelCase.

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

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

Loading history...
37
38
	const UAT = 'UAT';
39
40
	const PRODUCTION = 'Production';
41
42
	const UNSPECIFIED = 'Unspecified';
43
44
	/**
45
	 * @var array
46
	 */
47
	public static $db = [
48
		"Filename" => "Varchar(255)",
49
		"Name" => "Varchar(255)",
50
		"URL" => "Varchar(255)",
51
		"BackendIdentifier" => "Varchar(255)", // Injector identifier of the DeploymentBackend
52
		"Usage" => "Enum('Production, UAT, Test, Unspecified', 'Unspecified')",
53
	];
54
55
	/**
56
	 * @var array
57
	 */
58
	public static $has_many = [
59
		"Deployments" => "DNDeployment",
60
		"DataArchives" => "DNDataArchive",
61
		"DataTransfers" => "DNDataTransfer",
62
		"Pings" => "DNPing"
63
	];
64
65
	/**
66
	 * @var array
67
	 */
68
	public static $many_many = [
69
		"Viewers" => "Member", // Who can view this environment
70
		"ViewerGroups" => "Group",
71
		"Deployers" => "Member", // Who can deploy to this environment
72
		"DeployerGroups" => "Group",
73
		"CanRestoreMembers" => "Member", // Who can restore archive files to this environment
74
		"CanRestoreGroups" => "Group",
75
		"CanBackupMembers" => "Member", // Who can backup archive files from this environment
76
		"CanBackupGroups" => "Group",
77
		"ArchiveUploaders" => "Member", // Who can upload archive files linked to this environment
78
		"ArchiveUploaderGroups" => "Group",
79
		"ArchiveDownloaders" => "Member", // Who can download archive files from this environment
80
		"ArchiveDownloaderGroups" => "Group",
81
		"ArchiveDeleters" => "Member", // Who can delete archive files from this environment,
82
		"ArchiveDeleterGroups" => "Group",
83
	];
84
85
	/**
86
	 * @var array
87
	 */
88
	public static $summary_fields = [
89
		"Name" => "Environment Name",
90
		"Usage" => "Usage",
91
		"URL" => "URL",
92
		"DeployersList" => "Can Deploy List",
93
		"CanRestoreMembersList" => "Can Restore List",
94
		"CanBackupMembersList" => "Can Backup List",
95
		"ArchiveUploadersList" => "Can Upload List",
96
		"ArchiveDownloadersList" => "Can Download List",
97
		"ArchiveDeletersList" => "Can Delete List",
98
	];
99
100
	/**
101
	 * @var array
102
	 */
103
	public static $searchable_fields = [
104
		"Name",
105
	];
106
107
	private static $singular_name = 'Capistrano Environment';
0 ignored issues
show
Unused Code introduced by
The property $singular_name is not used and could be removed.

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

Loading history...
108
109
	private static $plural_name = 'Capistrano Environments';
110
111
	/**
112
	 * @var string
113
	 */
114
	private static $default_sort = 'Name';
115
116
	/**
117
	 * @var array
118
	 */
119
	public static $has_one = [
120
		"Project" => "DNProject",
121
		"CreateEnvironment" => "DNCreateEnvironment"
122
	];
123
124
	/**
125
	 * If this is set to a full pathfile, it will be used as template
126
	 * file when creating a new capistrano environment config file.
127
	 *
128
	 * If not set, the default 'environment.template' from the module
129
	 * root is used
130
	 *
131
	 * @config
132
	 * @var string
133
	 */
134
	private static $template_file = '';
0 ignored issues
show
Unused Code introduced by
The property $template_file is not used and could be removed.

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

Loading history...
135
136
	/**
137
	 * Set this to true to allow editing of the environment files via the web admin
138
	 *
139
	 * @var bool
140
	 */
141
	private static $allow_web_editing = false;
0 ignored issues
show
Unused Code introduced by
The property $allow_web_editing is not used and could be removed.

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

Loading history...
142
143
	/**
144
	 * @var array
145
	 */
146
	private static $casting = [
0 ignored issues
show
Unused Code introduced by
The property $casting is not used and could be removed.

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

Loading history...
147
		'DeployHistory' => 'Text'
148
	];
149
150
	/**
151
	 * Allowed backends. A map of Injector identifier to human-readable label.
152
	 *
153
	 * @config
154
	 * @var array
155
	 */
156
	private static $allowed_backends = [];
0 ignored issues
show
Unused Code introduced by
The property $allowed_backends is not used and could be removed.

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

Loading history...
157
158
	/**
159
	 * Used by the sync task
160
	 *
161
	 * @param string $path
162
	 * @return \DNEnvironment
163
	 */
164
	public static function create_from_path($path) {
165
		$e = DNEnvironment::create();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
166
		$e->Filename = $path;
167
		$e->Name = basename($e->Filename, '.rb');
168
169
		// add each administrator member as a deployer of the new environment
170
		$adminGroup = Group::get()->filter('Code', 'administrators')->first();
171
		$e->DeployerGroups()->add($adminGroup);
0 ignored issues
show
Bug introduced by
It seems like $adminGroup defined by \Group::get()->filter('C...ministrators')->first() on line 170 can be null; however, ManyManyList::add() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
172
		return $e;
173
	}
174
175
	/**
176
	 * Get the deployment backend used for this environment.
177
	 *
178
	 * Enforces compliance with the allowed_backends setting; if the DNEnvironment.BackendIdentifier value is
179
	 * illegal then that value is ignored.
180
	 *
181
	 * @return DeploymentBackend
182
	 */
183
	public function Backend() {
184
		$backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET));
185
		switch (sizeof($backends)) {
186
			// Nothing allowed, use the default value "DeploymentBackend"
187
			case 0:
188
				$backend = "DeploymentBackend";
189
				break;
190
191
			// Only 1 thing allowed, use that
192
			case 1:
193
				$backend = $backends[0];
194
				break;
195
196
			// Multiple choices, use our choice if it's legal, otherwise default to the first item on the list
197
			default:
198
				$backend = $this->BackendIdentifier;
199
				if (!in_array($backend, $backends)) {
200
					$backend = $backends[0];
201
				}
202
		}
203
204
		return Injector::inst()->get($backend);
205
	}
206
207
	/**
208
	 * @param SS_HTTPRequest $request
209
	 *
210
	 * @return DeploymentStrategy
211
	 */
212
	public function getDeployStrategy(\SS_HTTPRequest $request) {
213
		return $this->Backend()->planDeploy($this, $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...
214
	}
215
216
	public function Menu() {
217
		$list = new ArrayList();
218
219
		$controller = Controller::curr();
220
		$actionType = $controller->getField('CurrentActionType');
221
222
		$list->push(new ArrayData([
223
			'Link' => sprintf('naut/project/%s/environment/%s', $this->Project()->Name, $this->Name),
224
			'Title' => 'Deployments',
225
			'IsCurrent' => $this->isCurrent(),
226
			'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_DEPLOY
227
		]));
228
229
		$this->extend('updateMenu', $list);
230
231
		return $list;
232
	}
233
234
	/**
235
	 * Return the current object from $this->Menu()
236
	 * Good for making titles and things
237
	 */
238
	public function CurrentMenu() {
239
		return $this->Menu()->filter('IsSection', true)->First();
240
	}
241
242
	/**
243
	 * Return a name for this environment.
244
	 *
245
	 * @param string $separator The string used when concatenating project with env name
246
	 * @return string
247
	 */
248
	public function getFullName($separator = ':') {
249
		return sprintf('%s%s%s', $this->Project()->Name, $separator, $this->Name);
250
	}
251
252
	/**
253
	 * URL for the environment that can be used if no explicit URL is set.
254
	 */
255
	public function getDefaultURL() {
256
		return null;
257
	}
258
259
	public function getBareURL() {
260
		$url = parse_url($this->URL);
261
		if (isset($url['host'])) {
262
			return strtolower($url['host']);
263
		}
264
	}
265
266
	public function getBareDefaultURL() {
267
		$url = parse_url($this->getDefaultURL());
268
		if (isset($url['host'])) {
269
			return strtolower($url['host']);
270
		}
271
	}
272
273
	/**
274
	 * Environments are only viewable by people that can view the environment.
275
	 *
276
	 * @param Member|null $member
277
	 * @return boolean
278
	 */
279
	public function canView($member = null) {
280
		if (!$member) {
281
			$member = Member::currentUser();
282
		}
283
		if (!$member) {
284
			return false;
285
		}
286
		// Must be logged in to check permissions
287
288
		if (Permission::checkMember($member, 'ADMIN')) {
289
			return true;
290
		}
291
292
		// if no Viewers or ViewerGroups defined, fallback to DNProject::canView permissions
293
		if ($this->Viewers()->exists() || $this->ViewerGroups()->exists()) {
294
			return $this->Viewers()->byID($member->ID)
295
			|| $member->inGroups($this->ViewerGroups());
296
		}
297
298
		return $this->Project()->canView($member);
299
	}
300
301
	/**
302
	 * Allow deploy only to some people.
303
	 *
304
	 * @param Member|null $member
305
	 * @return boolean
306
	 */
307 View Code Duplication
	public function canDeploy($member = null) {
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...
308
		if (!$member) {
309
			$member = Member::currentUser();
310
		}
311
		if (!$member) {
312
			return false;
313
		}
314
		// Must be logged in to check permissions
315
316
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
317
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_DEPLOYMENT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
318
				return true;
319
			}
320
		} else {
321
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_DEPLOYMENT, $member)) {
322
				return true;
323
			}
324
		}
325
326
		return $this->Deployers()->byID($member->ID)
327
		|| $member->inGroups($this->DeployerGroups());
328
	}
329
330
	/**
331
	 * Provide reason why the user cannot deploy.
332
	 *
333
	 * @return string
334
	 */
335
	public function getCannotDeployMessage() {
336
		return 'You cannot deploy to this environment.';
337
	}
338
339
	/**
340
	 * Allows only selected {@link Member} objects to restore {@link DNDataArchive} objects into this
341
	 * {@link DNEnvironment}.
342
	 *
343
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
344
	 * @return boolean true if $member can restore, and false if they can't.
345
	 */
346 View Code Duplication
	public function canRestore($member = null) {
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...
347
		if (!$member) {
348
			$member = Member::currentUser();
349
		}
350
		if (!$member) {
351
			return false;
352
		}
353
		// Must be logged in to check permissions
354
355
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
356
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
357
				return true;
358
			}
359
		} else {
360
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
361
				return true;
362
			}
363
		}
364
365
		return $this->CanRestoreMembers()->byID($member->ID)
366
		|| $member->inGroups($this->CanRestoreGroups());
367
	}
368
369
	/**
370
	 * Allows only selected {@link Member} objects to backup this {@link DNEnvironment} to a {@link DNDataArchive}
371
	 * file.
372
	 *
373
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
374
	 * @return boolean true if $member can backup, and false if they can't.
375
	 */
376 View Code Duplication
	public function canBackup($member = null) {
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...
377
		$project = $this->Project();
378
		if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
379
			return false;
380
		}
381
382
		if (!$member) {
383
			$member = Member::currentUser();
384
		}
385
		// Must be logged in to check permissions
386
		if (!$member) {
387
			return false;
388
		}
389
390
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
391
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
392
				return true;
393
			}
394
		} else {
395
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
396
				return true;
397
			}
398
		}
399
400
		return $this->CanBackupMembers()->byID($member->ID)
401
		|| $member->inGroups($this->CanBackupGroups());
402
	}
403
404
	/**
405
	 * Allows only selected {@link Member} objects to upload {@link DNDataArchive} objects linked to this
406
	 * {@link DNEnvironment}.
407
	 *
408
	 * Note: This is not uploading them to the actual environment itself (e.g. uploading to the live site) - it is the
409
	 * process of uploading a *.sspak file into Deploynaut for later 'restoring' to an environment. See
410
	 * {@link self::canRestore()}.
411
	 *
412
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
413
	 * @return boolean true if $member can upload archives linked to this environment, false if they can't.
414
	 */
415 View Code Duplication
	public function canUploadArchive($member = null) {
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...
416
		$project = $this->Project();
417
		if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
418
			return false;
419
		}
420
421
		if (!$member) {
422
			$member = Member::currentUser();
423
		}
424
		if (!$member) {
425
			return false;
426
		}
427
		// Must be logged in to check permissions
428
429
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
430
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
431
				return true;
432
			}
433
		} else {
434
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
435
				return true;
436
			}
437
		}
438
439
		return $this->ArchiveUploaders()->byID($member->ID)
440
		|| $member->inGroups($this->ArchiveUploaderGroups());
441
	}
442
443
	/**
444
	 * Allows only selected {@link Member} objects to download {@link DNDataArchive} objects from this
445
	 * {@link DNEnvironment}.
446
	 *
447
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
448
	 * @return boolean true if $member can download archives from this environment, false if they can't.
449
	 */
450 View Code Duplication
	public function canDownloadArchive($member = null) {
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...
451
		if (!$member) {
452
			$member = Member::currentUser();
453
		}
454
		if (!$member) {
455
			return false;
456
		}
457
		// Must be logged in to check permissions
458
459
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
460
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
461
				return true;
462
			}
463
		} else {
464
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
465
				return true;
466
			}
467
		}
468
469
		return $this->ArchiveDownloaders()->byID($member->ID)
470
		|| $member->inGroups($this->ArchiveDownloaderGroups());
471
	}
472
473
	/**
474
	 * Allows only selected {@link Member} objects to delete {@link DNDataArchive} objects from this
475
	 * {@link DNEnvironment}.
476
	 *
477
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
478
	 * @return boolean true if $member can delete archives from this environment, false if they can't.
479
	 */
480 View Code Duplication
	public function canDeleteArchive($member = null) {
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...
481
		if (!$member) {
482
			$member = Member::currentUser();
483
		}
484
		if (!$member) {
485
			return false;
486
		}
487
		// Must be logged in to check permissions
488
489
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
490
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
491
				return true;
492
			}
493
		} else {
494
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
0 ignored issues
show
Documentation introduced by
$member is of type object<DataObject>, but the function expects a object<Member>|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...
495
				return true;
496
			}
497
		}
498
499
		return $this->ArchiveDeleters()->byID($member->ID)
500
		|| $member->inGroups($this->ArchiveDeleterGroups());
501
	}
502
503
	/**
504
	 * Get a string of groups/people that are allowed to deploy to this environment.
505
	 * Used in DNRoot_project.ss to list {@link Member}s who have permission to perform this action.
506
	 *
507
	 * @return string
508
	 */
509
	public function getDeployersList() {
510
		return implode(
511
			", ",
512
			array_merge(
513
				$this->DeployerGroups()->column("Title"),
514
				$this->Deployers()->column("FirstName")
515
			)
516
		);
517
	}
518
519
	/**
520
	 * Get a string of groups/people that are allowed to restore {@link DNDataArchive} objects into this environment.
521
	 *
522
	 * @return string
523
	 */
524
	public function getCanRestoreMembersList() {
525
		return implode(
526
			", ",
527
			array_merge(
528
				$this->CanRestoreGroups()->column("Title"),
529
				$this->CanRestoreMembers()->column("FirstName")
530
			)
531
		);
532
	}
533
534
	/**
535
	 * Get a string of groups/people that are allowed to backup {@link DNDataArchive} objects from this environment.
536
	 *
537
	 * @return string
538
	 */
539
	public function getCanBackupMembersList() {
540
		return implode(
541
			", ",
542
			array_merge(
543
				$this->CanBackupGroups()->column("Title"),
544
				$this->CanBackupMembers()->column("FirstName")
545
			)
546
		);
547
	}
548
549
	/**
550
	 * Get a string of groups/people that are allowed to upload {@link DNDataArchive}
551
	 *  objects linked to this environment.
552
	 *
553
	 * @return string
554
	 */
555
	public function getArchiveUploadersList() {
556
		return implode(
557
			", ",
558
			array_merge(
559
				$this->ArchiveUploaderGroups()->column("Title"),
560
				$this->ArchiveUploaders()->column("FirstName")
561
			)
562
		);
563
	}
564
565
	/**
566
	 * Get a string of groups/people that are allowed to download {@link DNDataArchive} objects from this environment.
567
	 *
568
	 * @return string
569
	 */
570
	public function getArchiveDownloadersList() {
571
		return implode(
572
			", ",
573
			array_merge(
574
				$this->ArchiveDownloaderGroups()->column("Title"),
575
				$this->ArchiveDownloaders()->column("FirstName")
576
			)
577
		);
578
	}
579
580
	/**
581
	 * Get a string of groups/people that are allowed to delete {@link DNDataArchive} objects from this environment.
582
	 *
583
	 * @return string
584
	 */
585
	public function getArchiveDeletersList() {
586
		return implode(
587
			", ",
588
			array_merge(
589
				$this->ArchiveDeleterGroups()->column("Title"),
590
				$this->ArchiveDeleters()->column("FirstName")
591
			)
592
		);
593
	}
594
595
	/**
596
	 * @return DNData
597
	 */
598
	public function DNData() {
599
		return DNData::inst();
600
	}
601
602
	/**
603
	 * Get the current deployed build for this environment
604
	 *
605
	 * Dear people of the future: If you are looking to optimize this, simply create a CurrentBuildSHA(), which can be
606
	 * a lot faster. I presume you came here because of the Project display template, which only needs a SHA.
607
	 *
608
	 * @return false|DNDeployment
609
	 */
610
	public function CurrentBuild() {
611
		// The DeployHistory function is far too slow to use for this
612
613
		/** @var DNDeployment $deploy */
614
		$deploy = DNDeployment::get()->filter([
615
			'EnvironmentID' => $this->ID,
616
			'State' => DNDeployment::STATE_COMPLETED
617
		])->sort('LastEdited DESC')->first();
618
619
		if (!$deploy || (!$deploy->SHA)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $deploy->SHA of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
620
			return false;
621
		}
622
623
		$repo = $this->Project()->getRepository();
624
		if (!$repo) {
625
			return $deploy;
626
		}
627
628
		try {
629
			$commit = $this->getCommit($deploy->SHA);
630
			if ($commit) {
631
				$deploy->Message = Convert::raw2xml($this->getCommitMessage($commit));
0 ignored issues
show
Documentation introduced by
The property Message does not exist on object<DNDeployment>. 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...
632
				$deploy->Committer = Convert::raw2xml($commit->getCommitterName());
0 ignored issues
show
Documentation introduced by
The property Committer does not exist on object<DNDeployment>. 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...
633
				$deploy->CommitDate = $commit->getCommitterDate()->Format('d/m/Y g:ia');
0 ignored issues
show
Documentation introduced by
The property CommitDate does not exist on object<DNDeployment>. 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...
634
				$deploy->Author = Convert::raw2xml($commit->getAuthorName());
0 ignored issues
show
Documentation introduced by
The property Author does not exist on object<DNDeployment>. 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...
635
				$deploy->AuthorDate = $commit->getAuthorDate()->Format('d/m/Y g:ia');
0 ignored issues
show
Documentation introduced by
The property AuthorDate does not exist on object<DNDeployment>. 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...
636
			}
637
			// We can't find this SHA, so we ignore adding a commit message to the deployment
638
		} catch (Exception $ex) {
639
		}
640
641
		return $deploy;
642
	}
643
644
	/**
645
	 * This is a proxy call to gitonmy that caches the information per project and sha
646
	 *
647
	 * @param string $sha
648
	 * @return \Gitonomy\Git\Commit
649
	 */
650
	public function getCommit($sha) {
651
		return $this->Project()->getCommit($sha);
652
	}
653
654
	public function getCommitMessage(\Gitonomy\Git\Commit $commit) {
655
		return $this->Project()->getCommitMessage($commit);
656
	}
657
658
	public function getCommitTags(\Gitonomy\Git\Commit $commit) {
659
		return $this->Project()->getCommitTags($commit);
660
	}
661
662
	/**
663
	 * A list of past deployments.
664
	 * @param string $orderBy - the name of a DB column to sort in descending order
665
	 * @return \ArrayList
666
	 */
667
	public function DeployHistory($orderBy = '') {
668
		$sort = [];
669
		if ($orderBy != '') {
670
			$sort[$orderBy] = 'DESC';
671
		}
672
		// default / fallback sort order
673
		$sort['LastEdited'] = 'DESC';
674
675
		return $this->Deployments()
676
			->where('"SHA" IS NOT NULL')
677
			->filter('State', [
678
				DNDeployment::STATE_COMPLETED,
679
				DNDeployment::STATE_FAILED,
680
				DNDeployment::STATE_INVALID
681
			])
682
			->sort($sort);
683
	}
684
685
	/**
686
	 * A list of upcoming or current deployments.
687
	 * @return ArrayList
688
	 */
689
	public function UpcomingDeployments() {
690
		return $this->Deployments()
691
			->where('"SHA" IS NOT NULL')
692
			->filter('State', [
693
				DNDeployment::STATE_NEW,
694
				DNDeployment::STATE_SUBMITTED,
695
				DNDeployment::STATE_APPROVED,
696
				DNDeployment::STATE_REJECTED,
697
				DNDeployment::STATE_ABORTING,
698
				DNDeployment::STATE_QUEUED,
699
				DNDeployment::STATE_DEPLOYING,
700
			])
701
			->sort('LastEdited DESC');
702
	}
703
704
	/**
705
	 * @param string $action
706
	 *
707
	 * @return string
708
	 */
709
	public function Link($action = '') {
710
		return \Controller::join_links($this->Project()->Link(), "environment", $this->Name, $action);
711
	}
712
713
	/**
714
	 * Is this environment currently at the root level of the controller that handles it?
715
	 * @return bool
716
	 */
717
	public function isCurrent() {
718
		return $this->isSection() && Controller::curr()->getAction() == 'environment';
719
	}
720
721
	/**
722
	 * Is this environment currently in a controller that is handling it or performing a sub-task?
723
	 * @return bool
724
	 */
725
	public function isSection() {
726
		$controller = Controller::curr();
727
		$environment = $controller->getField('CurrentEnvironment');
728
		return $environment && $environment->ID == $this->ID;
729
	}
730
731
	/**
732
	 * @return FieldList
733
	 */
734
	public function getCMSFields() {
735
		$fields = new FieldList(new TabSet('Root'));
736
737
		$project = $this->Project();
738
		if ($project && $project->exists()) {
739
			$viewerGroups = $project->Viewers();
740
			$groups = $viewerGroups->sort('Title')->map()->toArray();
741
			$members = [];
742
			foreach ($viewerGroups as $group) {
743
				foreach ($group->Members()->map() as $k => $v) {
744
					$members[$k] = $v;
745
				}
746
			}
747
			asort($members);
748
		} else {
749
			$groups = [];
750
			$members = [];
751
		}
752
753
		// Main tab
754
		$fields->addFieldsToTab('Root.Main', [
755
			// The Main.ProjectID
756
			TextField::create('ProjectName', 'Project')
757
				->setValue(($project = $this->Project()) ? $project->Name : null)
758
				->performReadonlyTransformation(),
759
760
			// The Main.Name
761
			TextField::create('Name', 'Environment name')
762
				->setDescription('A descriptive name for this environment, e.g. staging, uat, production'),
763
764
			$this->obj('Usage')->scaffoldFormField('Environment usage'),
765
766
			// The Main.URL field
767
			TextField::create('URL', 'Server URL')
768
				->setDescription('This url will be used to provide the front-end with a link to this environment'),
769
770
			// The Main.Filename
771
			TextField::create('Filename')
772
				->setDescription('The capistrano environment file name')
773
				->performReadonlyTransformation(),
774
		]);
775
776
		// Backend identifier - pick from a named list of configurations specified in YML config
777
		$backends = $this->config()->get('allowed_backends', Config::FIRST_SET);
778
		// If there's only 1 backend, then user selection isn't needed
779
		if (sizeof($backends) > 1) {
780
			$fields->addFieldToTab('Root.Main', DropdownField::create('BackendIdentifier', 'Deployment backend')
781
				->setSource($backends)
782
				->setDescription('What kind of deployment system should be used to deploy to this environment'));
783
		}
784
785
		$fields->addFieldsToTab('Root.UserPermissions', [
786
			// The viewers of the environment
787
			$this
788
				->buildPermissionField('ViewerGroups', 'Viewers', $groups, $members)
789
				->setTitle('Who can view this environment?')
790
				->setDescription('Groups or Users who can view this environment'),
791
792
			// The Main.Deployers
793
			$this
794
				->buildPermissionField('DeployerGroups', 'Deployers', $groups, $members)
795
				->setTitle('Who can deploy?')
796
				->setDescription('Groups or Users who can deploy to this environment'),
797
798
			// A box to select all snapshot options.
799
			$this
800
				->buildPermissionField('TickAllSnapshotGroups', 'TickAllSnapshot', $groups, $members)
801
				->setTitle("<em>All snapshot permissions</em>")
802
				->addExtraClass('tickall')
803
				->setDescription('UI shortcut to select all snapshot-related options - not written to the database.'),
804
805
			// The Main.CanRestoreMembers
806
			$this
807
				->buildPermissionField('CanRestoreGroups', 'CanRestoreMembers', $groups, $members)
808
				->setTitle('Who can restore?')
809
				->setDescription('Groups or Users who can restore archives from Deploynaut into this environment'),
810
811
			// The Main.CanBackupMembers
812
			$this
813
				->buildPermissionField('CanBackupGroups', 'CanBackupMembers', $groups, $members)
814
				->setTitle('Who can backup?')
815
				->setDescription('Groups or Users who can backup archives from this environment into Deploynaut'),
816
817
			// The Main.ArchiveDeleters
818
			$this
819
				->buildPermissionField('ArchiveDeleterGroups', 'ArchiveDeleters', $groups, $members)
820
				->setTitle('Who can delete?')
821
				->setDescription("Groups or Users who can delete archives from this environment's staging area."),
822
823
			// The Main.ArchiveUploaders
824
			$this
825
				->buildPermissionField('ArchiveUploaderGroups', 'ArchiveUploaders', $groups, $members)
826
				->setTitle('Who can upload?')
827
				->setDescription(
828
					'Users who can upload archives linked to this environment into Deploynaut.<br />' .
829
					'Linking them to an environment allows limiting download permissions (see below).'
830
				),
831
832
			// The Main.ArchiveDownloaders
833
			$this
834
				->buildPermissionField('ArchiveDownloaderGroups', 'ArchiveDownloaders', $groups, $members)
835
				->setTitle('Who can download?')
836
				->setDescription(<<<PHP
837
Users who can download archives from this environment to their computer.<br />
838
Since this implies access to the snapshot, it is also a prerequisite for restores
839
to other environments, alongside the "Who can restore" permission.<br>
840
Should include all users with upload permissions, otherwise they can't download
841
their own uploads.
842
PHP
843
				)
844
845
		]);
846
847
		// The Main.DeployConfig
848
		if ($this->Project()->exists()) {
849
			$this->setDeployConfigurationFields($fields);
850
		}
851
852
		// The DataArchives
853
		$dataArchiveConfig = GridFieldConfig_RecordViewer::create();
854
		$dataArchiveConfig->removeComponentsByType('GridFieldAddNewButton');
855
		if (class_exists('GridFieldBulkManager')) {
856
			$dataArchiveConfig->addComponent(new GridFieldBulkManager());
857
		}
858
		$dataArchive = GridField::create('DataArchives', 'Data Archives', $this->DataArchives(), $dataArchiveConfig);
859
		$fields->addFieldToTab('Root.DataArchive', $dataArchive);
860
861
		// Deployments
862
		$deploymentsConfig = GridFieldConfig_RecordEditor::create();
863
		$deploymentsConfig->removeComponentsByType('GridFieldAddNewButton');
864
		if (class_exists('GridFieldBulkManager')) {
865
			$deploymentsConfig->addComponent(new GridFieldBulkManager());
866
		}
867
		$deployments = GridField::create('Deployments', 'Deployments', $this->Deployments(), $deploymentsConfig);
868
		$fields->addFieldToTab('Root.Deployments', $deployments);
869
870
		Requirements::javascript('deploynaut/javascript/environment.js');
871
872
		// Add actions
873
		$action = new FormAction('check', 'Check Connection');
874
		$action->setUseButtonTag(true);
875
		$dataURL = Director::absoluteBaseURL() . 'naut/api/' . $this->Project()->Name . '/' . $this->Name . '/ping';
876
		$action->setAttribute('data-url', $dataURL);
877
		$fields->insertBefore($action, 'Name');
0 ignored issues
show
Documentation introduced by
'Name' is of type string, but the function expects a object<FormField>.

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...
878
879
		// Allow extensions
880
		$this->extend('updateCMSFields', $fields);
881
		return $fields;
882
	}
883
884
	/**
885
	 */
886
	public function onBeforeWrite() {
887
		parent::onBeforeWrite();
888
		if ($this->Name && $this->Name . '.rb' != $this->Filename) {
889
			$this->Filename = $this->Name . '.rb';
890
		}
891
		$this->checkEnvironmentPath();
892
		$this->writeConfigFile();
893
	}
894
895
	public function onAfterWrite() {
896
		parent::onAfterWrite();
897
898
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UAT) {
899
			$conflicting = DNEnvironment::get()
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
900
				->filter('ProjectID', $this->ProjectID)
901
				->filter('Usage', $this->Usage)
902
				->exclude('ID', $this->ID);
903
904
			foreach ($conflicting as $otherEnvironment) {
905
				$otherEnvironment->Usage = self::UNSPECIFIED;
906
				$otherEnvironment->write();
907
			}
908
		}
909
	}
910
911
	/**
912
	 * Delete any related config files
913
	 */
914
	public function onAfterDelete() {
915
		parent::onAfterDelete();
916
917
		// Create a basic new environment config from a template
918
		if ($this->config()->get('allow_web_editing') && $this->envFileExists()) {
919
			unlink($this->getConfigFilename());
920
		}
921
922
		$deployments = $this->Deployments();
923
		if ($deployments && $deployments->exists()) {
924
			foreach ($deployments as $deployment) {
925
				$deployment->delete();
926
			}
927
		}
928
929
		$archives = $this->DataArchives();
930
		if ($archives && $archives->exists()) {
931
			foreach ($archives as $archive) {
932
				$archive->delete();
933
			}
934
		}
935
936
		$transfers = $this->DataTransfers();
0 ignored issues
show
Documentation Bug introduced by
The method DataTransfers does not exist on object<DNEnvironment>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
937
		if ($transfers && $transfers->exists()) {
938
			foreach ($transfers as $transfer) {
939
				$transfer->delete();
940
			}
941
		}
942
943
		$pings = $this->Pings();
0 ignored issues
show
Documentation Bug introduced by
The method Pings does not exist on object<DNEnvironment>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
944
		if ($pings && $pings->exists()) {
945
			foreach ($pings as $ping) {
946
				$ping->delete();
947
			}
948
		}
949
950
		$create = $this->CreateEnvironment();
0 ignored issues
show
Documentation Bug introduced by
The method CreateEnvironment does not exist on object<DNEnvironment>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
951
		if ($create && $create->exists()) {
952
			$create->delete();
953
		}
954
	}
955
956
	/**
957
	 * Returns the path to the ruby config file
958
	 *
959
	 * @return string
960
	 */
961
	public function getConfigFilename() {
962
		if (!$this->Project()->exists()) {
963
			return '';
964
		}
965
		if (!$this->Filename) {
966
			return '';
967
		}
968
		return $this->DNData()->getEnvironmentDir() . '/' . $this->Project()->Name . '/' . $this->Filename;
969
	}
970
971
	/**
972
	 * Helper function to convert a multi-dimensional array (associative or indexed) to an {@link ArrayList} or
973
	 * {@link ArrayData} object structure, so that values can be used in templates.
974
	 *
975
	 * @param array $array The (single- or multi-dimensional) array to convert
976
	 * @return object Either an {@link ArrayList} or {@link ArrayData} object, or the original item ($array) if $array
977
	 * isn't an array.
978
	 */
979
	public static function array_to_viewabledata($array) {
980
		// Don't transform non-arrays
981
		if (!is_array($array)) {
982
			return $array;
983
		}
984
985
		// Figure out whether this is indexed or associative
986
		$keys = array_keys($array);
987
		$assoc = ($keys != array_keys($keys));
988
		if ($assoc) {
989
			// Treat as viewable data
990
			$data = new ArrayData([]);
991
			foreach ($array as $key => $value) {
992
				$data->setField($key, self::array_to_viewabledata($value));
993
			}
994
			return $data;
995
		} else {
996
			// Treat this as basic non-associative list
997
			$list = new ArrayList();
998
			foreach ($array as $value) {
999
				$list->push(self::array_to_viewabledata($value));
1000
			}
1001
			return $list;
1002
		}
1003
	}
1004
1005
	/**
1006
	 * Fetchs all deployments in progress. Limits to 1 hour to prevent deployments
1007
	 * if an old deployment is stuck.
1008
	 *
1009
	 * @return DataList
1010
	 */
1011
	public function runningDeployments() {
1012
		return DNDeployment::get()
1013
			->filter([
1014
				'EnvironmentID' => $this->ID,
1015
				'State' => [
1016
					DNDeployment::STATE_QUEUED,
1017
					DNDeployment::STATE_DEPLOYING,
1018
					DNDeployment::STATE_ABORTING
1019
				],
1020
				'Created:GreaterThan' => strtotime('-1 hour')
1021
			]);
1022
	}
1023
1024
	/**
1025
	 * @param string $sha
1026
	 * @return array
1027
	 */
1028
	protected function getCommitData($sha) {
1029
		try {
1030
			$repo = $this->Project()->getRepository();
1031
			if ($repo !== false) {
1032
				$commit = new \Gitonomy\Git\Commit($repo, $sha);
1033
				return [
1034
					'AuthorName' => (string) Convert::raw2xml($commit->getAuthorName()),
1035
					'AuthorEmail' => (string) Convert::raw2xml($commit->getAuthorEmail()),
1036
					'Message' => (string) Convert::raw2xml($this->getCommitMessage($commit)),
1037
					'ShortHash' => Convert::raw2xml($commit->getFixedShortHash(8)),
1038
					'Hash' => Convert::raw2xml($commit->getHash())
1039
				];
1040
			}
1041
		} catch (\Gitonomy\Git\Exception\ReferenceNotFoundException $exc) {
1042
			SS_Log::log($exc, SS_Log::WARN);
1043
		}
1044
		return [
1045
			'AuthorName' => '(unknown)',
1046
			'AuthorEmail' => '(unknown)',
1047
			'Message' => '(unknown)',
1048
			'ShortHash' => $sha,
1049
			'Hash' => '(unknown)',
1050
		];
1051
	}
1052
1053
	/**
1054
	 * Build a set of multi-select fields for assigning permissions to a pair of group and member many_many relations
1055
	 *
1056
	 * @param string $groupField Group field name
1057
	 * @param string $memberField Member field name
1058
	 * @param array $groups List of groups
1059
	 * @param array $members List of members
1060
	 * @return FieldGroup
1061
	 */
1062
	protected function buildPermissionField($groupField, $memberField, $groups, $members) {
1063
		return FieldGroup::create(
1064
			ListboxField::create($groupField, false, $groups)
1065
				->setMultiple(true)
1066
				->setAttribute('data-placeholder', 'Groups')
1067
				->setAttribute('placeholder', 'Groups')
1068
				->setAttribute('style', 'width: 400px;'),
1069
1070
			ListboxField::create($memberField, false, $members)
1071
				->setMultiple(true)
1072
				->setAttribute('data-placeholder', 'Members')
1073
				->setAttribute('placeholder', 'Members')
1074
				->setAttribute('style', 'width: 400px;')
1075
		);
1076
	}
1077
1078
	/**
1079
	 * @param FieldList $fields
1080
	 */
1081
	protected function setDeployConfigurationFields(&$fields) {
1082
		if (!$this->config()->get('allow_web_editing')) {
1083
			return;
1084
		}
1085
1086
		if ($this->envFileExists()) {
1087
			$deployConfig = new TextareaField('DeployConfig', 'Deploy config', $this->getEnvironmentConfig());
1088
			$deployConfig->setRows(40);
1089
			$fields->insertAfter($deployConfig, 'Filename');
0 ignored issues
show
Documentation introduced by
'Filename' is of type string, but the function expects a object<FormField>.

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...
1090
			return;
1091
		}
1092
1093
		$warning = 'Warning: This environment doesn\'t have deployment configuration.';
1094
		$noDeployConfig = new LabelField('noDeployConfig', $warning);
1095
		$noDeployConfig->addExtraClass('message warning');
1096
		$fields->insertAfter($noDeployConfig, 'Filename');
0 ignored issues
show
Documentation introduced by
'Filename' is of type string, but the function expects a object<FormField>.

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...
1097
		$createConfigField = new CheckboxField('CreateEnvConfig', 'Create Config');
1098
		$createConfigField->setDescription('Would you like to create the capistrano deploy configuration?');
1099
		$fields->insertAfter($createConfigField, 'noDeployConfig');
0 ignored issues
show
Documentation introduced by
'noDeployConfig' is of type string, but the function expects a object<FormField>.

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...
1100
	}
1101
1102
	/**
1103
	 * Ensure that environment paths are setup on the local filesystem
1104
	 */
1105
	protected function checkEnvironmentPath() {
1106
		// Create folder if it doesn't exist
1107
		$configDir = dirname($this->getConfigFilename());
1108
		if (!file_exists($configDir) && $configDir) {
1109
			mkdir($configDir, 0777, true);
1110
		}
1111
	}
1112
1113
	/**
1114
	 * Write the deployment config file to filesystem
1115
	 */
1116
	protected function writeConfigFile() {
1117
		if (!$this->config()->get('allow_web_editing')) {
1118
			return;
1119
		}
1120
1121
		// Create a basic new environment config from a template
1122
		if (!$this->envFileExists()
1123
			&& $this->Filename
1124
			&& $this->CreateEnvConfig
0 ignored issues
show
Documentation introduced by
The property CreateEnvConfig does not exist on object<DNEnvironment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
1125
		) {
1126
			$templateFile = $this->config()->template_file ?: BASE_PATH . '/deploynaut/environment.template';
1127
			file_put_contents($this->getConfigFilename(), file_get_contents($templateFile));
1128
		} else if ($this->envFileExists() && $this->DeployConfig) {
0 ignored issues
show
Documentation introduced by
The property DeployConfig does not exist on object<DNEnvironment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
1129
			file_put_contents($this->getConfigFilename(), $this->DeployConfig);
0 ignored issues
show
Documentation introduced by
The property DeployConfig does not exist on object<DNEnvironment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
1130
		}
1131
	}
1132
1133
	/**
1134
	 * @return string
1135
	 */
1136
	protected function getEnvironmentConfig() {
1137
		if (!$this->envFileExists()) {
1138
			return '';
1139
		}
1140
		return file_get_contents($this->getConfigFilename());
1141
	}
1142
1143
	/**
1144
	 * @return boolean
1145
	 */
1146
	protected function envFileExists() {
1147
		if (!$this->getConfigFilename()) {
1148
			return false;
1149
		}
1150
		return file_exists($this->getConfigFilename());
1151
	}
1152
1153
	protected function validate() {
1154
		$result = parent::validate();
1155
		$backend = $this->Backend();
1156
1157
		if (strcasecmp('test', $this->Name) === 0 && get_class($backend) == 'CapistranoDeploymentBackend') {
1158
			$result->error('"test" is not a valid environment name when using Capistrano backend.');
1159
		}
1160
1161
		return $result;
1162
	}
1163
1164
}
1165