Completed
Push — master ( a9696d...2d5de8 )
by Sean
13:09 queued 07:41
created

DNEnvironment::canDeploy()   C

Complexity

Conditions 8
Paths 14

Size

Total Lines 22
Code Lines 13

Duplication

Lines 18
Ratio 81.82 %

Importance

Changes 0
Metric Value
dl 18
loc 22
rs 6.6037
c 0
b 0
f 0
cc 8
eloc 13
nc 14
nop 1
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
	];
62
63
	/**
64
	 * @var array
65
	 */
66
	public static $many_many = [
67
		"Viewers" => "Member", // Who can view this environment
68
		"ViewerGroups" => "Group",
69
		"Deployers" => "Member", // Who can deploy to this environment
70
		"DeployerGroups" => "Group",
71
		"CanRestoreMembers" => "Member", // Who can restore archive files to this environment
72
		"CanRestoreGroups" => "Group",
73
		"CanBackupMembers" => "Member", // Who can backup archive files from this environment
74
		"CanBackupGroups" => "Group",
75
		"ArchiveUploaders" => "Member", // Who can upload archive files linked to this environment
76
		"ArchiveUploaderGroups" => "Group",
77
		"ArchiveDownloaders" => "Member", // Who can download archive files from this environment
78
		"ArchiveDownloaderGroups" => "Group",
79
		"ArchiveDeleters" => "Member", // Who can delete archive files from this environment,
80
		"ArchiveDeleterGroups" => "Group",
81
	];
82
83
	/**
84
	 * @var array
85
	 */
86
	public static $summary_fields = [
87
		"Name" => "Environment Name",
88
		"Usage" => "Usage",
89
		"URL" => "URL",
90
		"DeployersList" => "Can Deploy List",
91
		"CanRestoreMembersList" => "Can Restore List",
92
		"CanBackupMembersList" => "Can Backup List",
93
		"ArchiveUploadersList" => "Can Upload List",
94
		"ArchiveDownloadersList" => "Can Download List",
95
		"ArchiveDeletersList" => "Can Delete List",
96
	];
97
98
	/**
99
	 * @var array
100
	 */
101
	public static $searchable_fields = [
102
		"Name",
103
	];
104
105
	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...
106
107
	private static $plural_name = 'Capistrano Environments';
108
109
	/**
110
	 * @var string
111
	 */
112
	private static $default_sort = 'Name';
113
114
	/**
115
	 * @var array
116
	 */
117
	public static $has_one = [
118
		"Project" => "DNProject",
119
		"CreateEnvironment" => "DNCreateEnvironment"
120
	];
121
122
	/**
123
	 * If this is set to a full pathfile, it will be used as template
124
	 * file when creating a new capistrano environment config file.
125
	 *
126
	 * If not set, the default 'environment.template' from the module
127
	 * root is used
128
	 *
129
	 * @config
130
	 * @var string
131
	 */
132
	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...
133
134
	/**
135
	 * Set this to true to allow editing of the environment files via the web admin
136
	 *
137
	 * @var bool
138
	 */
139
	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...
140
141
	/**
142
	 * @var array
143
	 */
144
	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...
145
		'DeployHistory' => 'Text'
146
	];
147
148
	/**
149
	 * Allowed backends. A map of Injector identifier to human-readable label.
150
	 *
151
	 * @config
152
	 * @var array
153
	 */
154
	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...
155
156
	/**
157
	 * Used by the sync task
158
	 *
159
	 * @param string $path
160
	 * @return \DNEnvironment
161
	 */
162
	public static function create_from_path($path) {
163
		$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...
164
		$e->Filename = $path;
165
		$e->Name = basename($e->Filename, '.rb');
166
167
		// add each administrator member as a deployer of the new environment
168
		$adminGroup = Group::get()->filter('Code', 'administrators')->first();
169
		$e->DeployerGroups()->add($adminGroup);
170
		return $e;
171
	}
172
173
	/**
174
	 * Get the deployment backend used for this environment.
175
	 *
176
	 * Enforces compliance with the allowed_backends setting; if the DNEnvironment.BackendIdentifier value is
177
	 * illegal then that value is ignored.
178
	 *
179
	 * @return DeploymentBackend
180
	 */
181
	public function Backend() {
182
		$backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET));
183
		switch (sizeof($backends)) {
184
			// Nothing allowed, use the default value "DeploymentBackend"
185
			case 0:
186
				$backend = "DeploymentBackend";
187
				break;
188
189
			// Only 1 thing allowed, use that
190
			case 1:
191
				$backend = $backends[0];
192
				break;
193
194
			// Multiple choices, use our choice if it's legal, otherwise default to the first item on the list
195
			default:
196
				$backend = $this->BackendIdentifier;
197
				if (!in_array($backend, $backends)) {
198
					$backend = $backends[0];
199
				}
200
		}
201
202
		return Injector::inst()->get($backend);
203
	}
204
205
	/**
206
	 * @param SS_HTTPRequest $request
207
	 *
208
	 * @return DeploymentStrategy
209
	 */
210
	public function getDeployStrategy(\SS_HTTPRequest $request) {
211
		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...
212
	}
213
214
	public function Menu() {
215
		$list = new ArrayList();
216
217
		$controller = Controller::curr();
218
		$actionType = $controller->getField('CurrentActionType');
219
220
		$list->push(new ArrayData([
221
			'Link' => sprintf('naut/project/%s/environment/%s', $this->Project()->Name, $this->Name),
222
			'Title' => 'Deployments',
223
			'IsCurrent' => $this->isCurrent(),
224
			'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_DEPLOY
225
		]));
226
227
		$this->extend('updateMenu', $list);
228
229
		return $list;
230
	}
231
232
	/**
233
	 * Return the current object from $this->Menu()
234
	 * Good for making titles and things
235
	 */
236
	public function CurrentMenu() {
237
		return $this->Menu()->filter('IsSection', true)->First();
238
	}
239
240
	/**
241
	 * Return a name for this environment.
242
	 *
243
	 * @param string $separator The string used when concatenating project with env name
244
	 * @return string
245
	 */
246
	public function getFullName($separator = ':') {
247
		return sprintf('%s%s%s', $this->Project()->Name, $separator, $this->Name);
248
	}
249
250
	/**
251
	 * URL for the environment that can be used if no explicit URL is set.
252
	 */
253
	public function getDefaultURL() {
254
		return null;
255
	}
256
257
	public function getBareURL() {
258
		$url = parse_url($this->URL);
259
		if (isset($url['host'])) {
260
			return strtolower($url['host']);
261
		}
262
	}
263
264
	public function getBareDefaultURL() {
265
		$url = parse_url($this->getDefaultURL());
266
		if (isset($url['host'])) {
267
			return strtolower($url['host']);
268
		}
269
	}
270
271
	/**
272
	 * Environments are only viewable by people that can view the environment.
273
	 *
274
	 * @param Member|null $member
275
	 * @return boolean
276
	 */
277
	public function canView($member = null) {
278
		if (!$member) {
279
			$member = Member::currentUser();
280
		}
281
		if (!$member) {
282
			return false;
283
		}
284
		// Must be logged in to check permissions
285
286
		if (Permission::checkMember($member, 'ADMIN')) {
287
			return true;
288
		}
289
290
		// if no Viewers or ViewerGroups defined, fallback to DNProject::canView permissions
291
		if ($this->Viewers()->exists() || $this->ViewerGroups()->exists()) {
292
			return $this->Viewers()->byID($member->ID)
293
			|| $member->inGroups($this->ViewerGroups());
294
		}
295
296
		return $this->Project()->canView($member);
297
	}
298
299
	/**
300
	 * Allow deploy only to some people.
301
	 *
302
	 * @param Member|null $member
303
	 * @return boolean
304
	 */
305 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...
306
		if (!$member) {
307
			$member = Member::currentUser();
308
		}
309
		if (!$member) {
310
			return false;
311
		}
312
		// Must be logged in to check permissions
313
314
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
315
			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...
316
				return true;
317
			}
318
		} else {
319
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_DEPLOYMENT, $member)) {
320
				return true;
321
			}
322
		}
323
324
		return $this->Deployers()->byID($member->ID)
325
		|| $member->inGroups($this->DeployerGroups());
326
	}
327
328
	/**
329
	 * Provide reason why the user cannot deploy.
330
	 *
331
	 * @return string
332
	 */
333
	public function getCannotDeployMessage() {
334
		return 'You cannot deploy to this environment.';
335
	}
336
337
	/**
338
	 * Allows only selected {@link Member} objects to restore {@link DNDataArchive} objects into this
339
	 * {@link DNEnvironment}.
340
	 *
341
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
342
	 * @return boolean true if $member can restore, and false if they can't.
343
	 */
344 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...
345
		if (!$member) {
346
			$member = Member::currentUser();
347
		}
348
		if (!$member) {
349
			return false;
350
		}
351
		// Must be logged in to check permissions
352
353
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
354
			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...
355
				return true;
356
			}
357
		} else {
358
			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...
359
				return true;
360
			}
361
		}
362
363
		return $this->CanRestoreMembers()->byID($member->ID)
364
		|| $member->inGroups($this->CanRestoreGroups());
365
	}
366
367
	/**
368
	 * Allows only selected {@link Member} objects to backup this {@link DNEnvironment} to a {@link DNDataArchive}
369
	 * file.
370
	 *
371
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
372
	 * @return boolean true if $member can backup, and false if they can't.
373
	 */
374 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...
375
		$project = $this->Project();
376
		if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
377
			return false;
378
		}
379
380
		if (!$member) {
381
			$member = Member::currentUser();
382
		}
383
		// Must be logged in to check permissions
384
		if (!$member) {
385
			return false;
386
		}
387
388
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
389
			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
390
				return true;
391
			}
392
		} else {
393
			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...
394
				return true;
395
			}
396
		}
397
398
		return $this->CanBackupMembers()->byID($member->ID)
399
		|| $member->inGroups($this->CanBackupGroups());
400
	}
401
402
	/**
403
	 * Allows only selected {@link Member} objects to upload {@link DNDataArchive} objects linked to this
404
	 * {@link DNEnvironment}.
405
	 *
406
	 * Note: This is not uploading them to the actual environment itself (e.g. uploading to the live site) - it is the
407
	 * process of uploading a *.sspak file into Deploynaut for later 'restoring' to an environment. See
408
	 * {@link self::canRestore()}.
409
	 *
410
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
411
	 * @return boolean true if $member can upload archives linked to this environment, false if they can't.
412
	 */
413 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...
414
		$project = $this->Project();
415
		if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
416
			return false;
417
		}
418
419
		if (!$member) {
420
			$member = Member::currentUser();
421
		}
422
		if (!$member) {
423
			return false;
424
		}
425
		// Must be logged in to check permissions
426
427
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
428
			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...
429
				return true;
430
			}
431
		} else {
432
			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...
433
				return true;
434
			}
435
		}
436
437
		return $this->ArchiveUploaders()->byID($member->ID)
438
		|| $member->inGroups($this->ArchiveUploaderGroups());
439
	}
440
441
	/**
442
	 * Allows only selected {@link Member} objects to download {@link DNDataArchive} objects from this
443
	 * {@link DNEnvironment}.
444
	 *
445
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
446
	 * @return boolean true if $member can download archives from this environment, false if they can't.
447
	 */
448 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...
449
		if (!$member) {
450
			$member = Member::currentUser();
451
		}
452
		if (!$member) {
453
			return false;
454
		}
455
		// Must be logged in to check permissions
456
457
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
458
			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...
459
				return true;
460
			}
461
		} else {
462
			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...
463
				return true;
464
			}
465
		}
466
467
		return $this->ArchiveDownloaders()->byID($member->ID)
468
		|| $member->inGroups($this->ArchiveDownloaderGroups());
469
	}
470
471
	/**
472
	 * Allows only selected {@link Member} objects to delete {@link DNDataArchive} objects from this
473
	 * {@link DNEnvironment}.
474
	 *
475
	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
476
	 * @return boolean true if $member can delete archives from this environment, false if they can't.
477
	 */
478 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...
479
		if (!$member) {
480
			$member = Member::currentUser();
481
		}
482
		if (!$member) {
483
			return false;
484
		}
485
		// Must be logged in to check permissions
486
487
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UNSPECIFIED) {
488
			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...
489
				return true;
490
			}
491
		} else {
492
			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
493
				return true;
494
			}
495
		}
496
497
		return $this->ArchiveDeleters()->byID($member->ID)
498
		|| $member->inGroups($this->ArchiveDeleterGroups());
499
	}
500
501
	/**
502
	 * Get a string of groups/people that are allowed to deploy to this environment.
503
	 * Used in DNRoot_project.ss to list {@link Member}s who have permission to perform this action.
504
	 *
505
	 * @return string
506
	 */
507
	public function getDeployersList() {
508
		return implode(
509
			", ",
510
			array_merge(
511
				$this->DeployerGroups()->column("Title"),
512
				$this->Deployers()->column("FirstName")
513
			)
514
		);
515
	}
516
517
	/**
518
	 * Get a string of groups/people that are allowed to restore {@link DNDataArchive} objects into this environment.
519
	 *
520
	 * @return string
521
	 */
522
	public function getCanRestoreMembersList() {
523
		return implode(
524
			", ",
525
			array_merge(
526
				$this->CanRestoreGroups()->column("Title"),
527
				$this->CanRestoreMembers()->column("FirstName")
528
			)
529
		);
530
	}
531
532
	/**
533
	 * Get a string of groups/people that are allowed to backup {@link DNDataArchive} objects from this environment.
534
	 *
535
	 * @return string
536
	 */
537
	public function getCanBackupMembersList() {
538
		return implode(
539
			", ",
540
			array_merge(
541
				$this->CanBackupGroups()->column("Title"),
542
				$this->CanBackupMembers()->column("FirstName")
543
			)
544
		);
545
	}
546
547
	/**
548
	 * Get a string of groups/people that are allowed to upload {@link DNDataArchive}
549
	 *  objects linked to this environment.
550
	 *
551
	 * @return string
552
	 */
553
	public function getArchiveUploadersList() {
554
		return implode(
555
			", ",
556
			array_merge(
557
				$this->ArchiveUploaderGroups()->column("Title"),
558
				$this->ArchiveUploaders()->column("FirstName")
559
			)
560
		);
561
	}
562
563
	/**
564
	 * Get a string of groups/people that are allowed to download {@link DNDataArchive} objects from this environment.
565
	 *
566
	 * @return string
567
	 */
568
	public function getArchiveDownloadersList() {
569
		return implode(
570
			", ",
571
			array_merge(
572
				$this->ArchiveDownloaderGroups()->column("Title"),
573
				$this->ArchiveDownloaders()->column("FirstName")
574
			)
575
		);
576
	}
577
578
	/**
579
	 * Get a string of groups/people that are allowed to delete {@link DNDataArchive} objects from this environment.
580
	 *
581
	 * @return string
582
	 */
583
	public function getArchiveDeletersList() {
584
		return implode(
585
			", ",
586
			array_merge(
587
				$this->ArchiveDeleterGroups()->column("Title"),
588
				$this->ArchiveDeleters()->column("FirstName")
589
			)
590
		);
591
	}
592
593
	/**
594
	 * @return DNData
595
	 */
596
	public function DNData() {
597
		return DNData::inst();
598
	}
599
600
	/**
601
	 * Get the current deployed build for this environment
602
	 *
603
	 * Dear people of the future: If you are looking to optimize this, simply create a CurrentBuildSHA(), which can be
604
	 * a lot faster. I presume you came here because of the Project display template, which only needs a SHA.
605
	 *
606
	 * @return false|DNDeployment
607
	 */
608
	public function CurrentBuild() {
609
		// The DeployHistory function is far too slow to use for this
610
611
		/** @var DNDeployment $deploy */
612
		$deploy = DNDeployment::get()->filter([
613
			'EnvironmentID' => $this->ID,
614
			'State' => DNDeployment::STATE_COMPLETED
615
		])->sort('LastEdited DESC')->first();
616
617
		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...
618
			return false;
619
		}
620
621
		$repo = $this->Project()->getRepository();
622
		if (!$repo) {
623
			return $deploy;
624
		}
625
626
		try {
627
			$commit = $repo->getCommit($deploy->SHA);
628
			if ($commit) {
629
				$deploy->Message = Convert::raw2xml($commit->getMessage());
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...
630
				$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...
631
				$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...
632
				$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...
633
				$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...
634
			}
635
			// We can't find this SHA, so we ignore adding a commit message to the deployment
636
		} catch (Exception $ex) {
637
		}
638
639
		return $deploy;
640
	}
641
642
	/**
643
	 * A list of past deployments.
644
	 * @param string $orderBy - the name of a DB column to sort in descending order
645
	 * @return \ArrayList
646
	 */
647
	public function DeployHistory($orderBy = '') {
648
		$sort = [];
649
		if ($orderBy != '') {
650
			$sort[$orderBy] = 'DESC';
651
		}
652
		// default / fallback sort order
653
		$sort['LastEdited'] = 'DESC';
654
655
		return $this->Deployments()
656
			->where('"SHA" IS NOT NULL')
657
			->filter('State', [
658
				DNDeployment::STATE_COMPLETED,
659
				DNDeployment::STATE_FAILED,
660
				DNDeployment::STATE_INVALID
661
			])
662
			->sort($sort);
663
	}
664
665
	/**
666
	 * A list of upcoming or current deployments.
667
	 * @return ArrayList
668
	 */
669
	public function UpcomingDeployments() {
670
		return $this->Deployments()
671
			->where('"SHA" IS NOT NULL')
672
			->filter('State', [
673
				DNDeployment::STATE_NEW,
674
				DNDeployment::STATE_SUBMITTED,
675
				DNDeployment::STATE_ABORTING,
676
				DNDeployment::STATE_QUEUED,
677
				DNDeployment::STATE_DEPLOYING,
678
			])
679
			->sort('LastEdited DESC');
680
	}
681
682
	/**
683
	 * @param string $action
684
	 *
685
	 * @return string
686
	 */
687
	public function Link($action = '') {
688
		return \Controller::join_links($this->Project()->Link(), "environment", $this->Name, $action);
689
	}
690
691
	/**
692
	 * Is this environment currently at the root level of the controller that handles it?
693
	 * @return bool
694
	 */
695
	public function isCurrent() {
696
		return $this->isSection() && Controller::curr()->getAction() == 'environment';
697
	}
698
699
	/**
700
	 * Is this environment currently in a controller that is handling it or performing a sub-task?
701
	 * @return bool
702
	 */
703
	public function isSection() {
704
		$controller = Controller::curr();
705
		$environment = $controller->getField('CurrentEnvironment');
706
		return $environment && $environment->ID == $this->ID;
707
	}
708
709
	/**
710
	 * @return FieldList
711
	 */
712
	public function getCMSFields() {
713
		$fields = new FieldList(new TabSet('Root'));
714
715
		$project = $this->Project();
716
		if ($project && $project->exists()) {
717
			$viewerGroups = $project->Viewers();
718
			$groups = $viewerGroups->sort('Title')->map()->toArray();
719
			$members = [];
720
			foreach ($viewerGroups as $group) {
721
				foreach ($group->Members()->map() as $k => $v) {
722
					$members[$k] = $v;
723
				}
724
			}
725
			asort($members);
726
		} else {
727
			$groups = [];
728
			$members = [];
729
		}
730
731
		// Main tab
732
		$fields->addFieldsToTab('Root.Main', [
733
			// The Main.ProjectID
734
			TextField::create('ProjectName', 'Project')
735
				->setValue(($project = $this->Project()) ? $project->Name : null)
736
				->performReadonlyTransformation(),
737
738
			// The Main.Name
739
			TextField::create('Name', 'Environment name')
740
				->setDescription('A descriptive name for this environment, e.g. staging, uat, production'),
741
742
			$this->obj('Usage')->scaffoldFormField('Environment usage'),
743
744
			// The Main.URL field
745
			TextField::create('URL', 'Server URL')
746
				->setDescription('This url will be used to provide the front-end with a link to this environment'),
747
748
			// The Main.Filename
749
			TextField::create('Filename')
750
				->setDescription('The capistrano environment file name')
751
				->performReadonlyTransformation(),
752
		]);
753
754
		// Backend identifier - pick from a named list of configurations specified in YML config
755
		$backends = $this->config()->get('allowed_backends', Config::FIRST_SET);
756
		// If there's only 1 backend, then user selection isn't needed
757
		if (sizeof($backends) > 1) {
758
			$fields->addFieldToTab('Root.Main', DropdownField::create('BackendIdentifier', 'Deployment backend')
759
				->setSource($backends)
760
				->setDescription('What kind of deployment system should be used to deploy to this environment'));
761
		}
762
763
		$fields->addFieldsToTab('Root.UserPermissions', [
764
			// The viewers of the environment
765
			$this
766
				->buildPermissionField('ViewerGroups', 'Viewers', $groups, $members)
767
				->setTitle('Who can view this environment?')
768
				->setDescription('Groups or Users who can view this environment'),
769
770
			// The Main.Deployers
771
			$this
772
				->buildPermissionField('DeployerGroups', 'Deployers', $groups, $members)
773
				->setTitle('Who can deploy?')
774
				->setDescription('Groups or Users who can deploy to this environment'),
775
776
			// A box to select all snapshot options.
777
			$this
778
				->buildPermissionField('TickAllSnapshotGroups', 'TickAllSnapshot', $groups, $members)
779
				->setTitle("<em>All snapshot permissions</em>")
780
				->addExtraClass('tickall')
781
				->setDescription('UI shortcut to select all snapshot-related options - not written to the database.'),
782
783
			// The Main.CanRestoreMembers
784
			$this
785
				->buildPermissionField('CanRestoreGroups', 'CanRestoreMembers', $groups, $members)
786
				->setTitle('Who can restore?')
787
				->setDescription('Groups or Users who can restore archives from Deploynaut into this environment'),
788
789
			// The Main.CanBackupMembers
790
			$this
791
				->buildPermissionField('CanBackupGroups', 'CanBackupMembers', $groups, $members)
792
				->setTitle('Who can backup?')
793
				->setDescription('Groups or Users who can backup archives from this environment into Deploynaut'),
794
795
			// The Main.ArchiveDeleters
796
			$this
797
				->buildPermissionField('ArchiveDeleterGroups', 'ArchiveDeleters', $groups, $members)
798
				->setTitle('Who can delete?')
799
				->setDescription("Groups or Users who can delete archives from this environment's staging area."),
800
801
			// The Main.ArchiveUploaders
802
			$this
803
				->buildPermissionField('ArchiveUploaderGroups', 'ArchiveUploaders', $groups, $members)
804
				->setTitle('Who can upload?')
805
				->setDescription(
806
					'Users who can upload archives linked to this environment into Deploynaut.<br />' .
807
					'Linking them to an environment allows limiting download permissions (see below).'
808
				),
809
810
			// The Main.ArchiveDownloaders
811
			$this
812
				->buildPermissionField('ArchiveDownloaderGroups', 'ArchiveDownloaders', $groups, $members)
813
				->setTitle('Who can download?')
814
				->setDescription(<<<PHP
815
Users who can download archives from this environment to their computer.<br />
816
Since this implies access to the snapshot, it is also a prerequisite for restores
817
to other environments, alongside the "Who can restore" permission.<br>
818
Should include all users with upload permissions, otherwise they can't download
819
their own uploads.
820
PHP
821
				)
822
823
		]);
824
825
		// The Main.DeployConfig
826
		if ($this->Project()->exists()) {
827
			$this->setDeployConfigurationFields($fields);
828
		}
829
830
		// The DataArchives
831
		$dataArchiveConfig = GridFieldConfig_RecordViewer::create();
832
		$dataArchiveConfig->removeComponentsByType('GridFieldAddNewButton');
833
		if (class_exists('GridFieldBulkManager')) {
834
			$dataArchiveConfig->addComponent(new GridFieldBulkManager());
835
		}
836
		$dataArchive = GridField::create('DataArchives', 'Data Archives', $this->DataArchives(), $dataArchiveConfig);
837
		$fields->addFieldToTab('Root.DataArchive', $dataArchive);
838
839
		// Deployments
840
		$deploymentsConfig = GridFieldConfig_RecordEditor::create();
841
		$deploymentsConfig->removeComponentsByType('GridFieldAddNewButton');
842
		if (class_exists('GridFieldBulkManager')) {
843
			$deploymentsConfig->addComponent(new GridFieldBulkManager());
844
		}
845
		$deployments = GridField::create('Deployments', 'Deployments', $this->Deployments(), $deploymentsConfig);
846
		$fields->addFieldToTab('Root.Deployments', $deployments);
847
848
		Requirements::javascript('deploynaut/javascript/environment.js');
849
850
		// Add actions
851
		$action = new FormAction('check', 'Check Connection');
852
		$action->setUseButtonTag(true);
853
		$dataURL = Director::absoluteBaseURL() . 'naut/api/' . $this->Project()->Name . '/' . $this->Name . '/ping';
854
		$action->setAttribute('data-url', $dataURL);
855
		$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...
856
857
		// Allow extensions
858
		$this->extend('updateCMSFields', $fields);
859
		return $fields;
860
	}
861
862
	/**
863
	 */
864
	public function onBeforeWrite() {
865
		parent::onBeforeWrite();
866
		if ($this->Name && $this->Name . '.rb' != $this->Filename) {
867
			$this->Filename = $this->Name . '.rb';
868
		}
869
		$this->checkEnvironmentPath();
870
		$this->writeConfigFile();
871
	}
872
873
	public function onAfterWrite() {
874
		parent::onAfterWrite();
875
876
		if ($this->Usage === self::PRODUCTION || $this->Usage === self::UAT) {
877
			$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...
878
				->filter('ProjectID', $this->ProjectID)
879
				->filter('Usage', $this->Usage)
880
				->exclude('ID', $this->ID);
881
882
			foreach ($conflicting as $otherEnvironment) {
883
				$otherEnvironment->Usage = self::UNSPECIFIED;
884
				$otherEnvironment->write();
885
			}
886
		}
887
	}
888
889
	/**
890
	 * Delete any related config files
891
	 */
892
	public function onAfterDelete() {
893
		parent::onAfterDelete();
894
		// Create a basic new environment config from a template
895
		if ($this->config()->get('allow_web_editing') && $this->envFileExists()) {
896
			unlink($this->getConfigFilename());
897
		}
898
899
		$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...
900
		if ($create && $create->exists()) {
901
			$create->delete();
902
		}
903
	}
904
905
	/**
906
	 * Returns the path to the ruby config file
907
	 *
908
	 * @return string
909
	 */
910
	public function getConfigFilename() {
911
		if (!$this->Project()->exists()) {
912
			return '';
913
		}
914
		if (!$this->Filename) {
915
			return '';
916
		}
917
		return $this->DNData()->getEnvironmentDir() . '/' . $this->Project()->Name . '/' . $this->Filename;
918
	}
919
920
	/**
921
	 * Helper function to convert a multi-dimensional array (associative or indexed) to an {@link ArrayList} or
922
	 * {@link ArrayData} object structure, so that values can be used in templates.
923
	 *
924
	 * @param array $array The (single- or multi-dimensional) array to convert
925
	 * @return object Either an {@link ArrayList} or {@link ArrayData} object, or the original item ($array) if $array
926
	 * isn't an array.
927
	 */
928
	public static function array_to_viewabledata($array) {
929
		// Don't transform non-arrays
930
		if (!is_array($array)) {
931
			return $array;
932
		}
933
934
		// Figure out whether this is indexed or associative
935
		$keys = array_keys($array);
936
		$assoc = ($keys != array_keys($keys));
937
		if ($assoc) {
938
			// Treat as viewable data
939
			$data = new ArrayData([]);
940
			foreach ($array as $key => $value) {
941
				$data->setField($key, self::array_to_viewabledata($value));
942
			}
943
			return $data;
944
		} else {
945
			// Treat this as basic non-associative list
946
			$list = new ArrayList();
947
			foreach ($array as $value) {
948
				$list->push(self::array_to_viewabledata($value));
949
			}
950
			return $list;
951
		}
952
	}
953
954
	/**
955
	 * Fetchs all deployments in progress. Limits to 1 hour to prevent deployments
956
	 * if an old deployment is stuck.
957
	 *
958
	 * @return DataList
959
	 */
960
	public function runningDeployments() {
961
		return DNDeployment::get()
962
			->filter([
963
				'EnvironmentID' => $this->ID,
964
				'State' => [
965
					DNDeployment::STATE_QUEUED,
966
					DNDeployment::STATE_DEPLOYING,
967
					DNDeployment::STATE_ABORTING
968
				],
969
				'Created:GreaterThan' => strtotime('-1 hour')
970
			]);
971
	}
972
973
	/**
974
	 * @param string $sha
975
	 * @return array
976
	 */
977
	protected function getCommitData($sha) {
978
		try {
979
			$repo = $this->Project()->getRepository();
980
			if ($repo !== false) {
981
				$commit = new \Gitonomy\Git\Commit($repo, $sha);
982
				return [
983
					'AuthorName' => (string) Convert::raw2xml($commit->getAuthorName()),
984
					'AuthorEmail' => (string) Convert::raw2xml($commit->getAuthorEmail()),
985
					'Message' => (string) Convert::raw2xml($commit->getMessage()),
986
					'ShortHash' => Convert::raw2xml($commit->getFixedShortHash(8)),
987
					'Hash' => Convert::raw2xml($commit->getHash())
988
				];
989
			}
990
		} catch (\Gitonomy\Git\Exception\ReferenceNotFoundException $exc) {
991
			SS_Log::log($exc, SS_Log::WARN);
992
		}
993
		return [
994
			'AuthorName' => '(unknown)',
995
			'AuthorEmail' => '(unknown)',
996
			'Message' => '(unknown)',
997
			'ShortHash' => $sha,
998
			'Hash' => '(unknown)',
999
		];
1000
	}
1001
1002
	/**
1003
	 * Build a set of multi-select fields for assigning permissions to a pair of group and member many_many relations
1004
	 *
1005
	 * @param string $groupField Group field name
1006
	 * @param string $memberField Member field name
1007
	 * @param array $groups List of groups
1008
	 * @param array $members List of members
1009
	 * @return FieldGroup
1010
	 */
1011
	protected function buildPermissionField($groupField, $memberField, $groups, $members) {
1012
		return FieldGroup::create(
1013
			ListboxField::create($groupField, false, $groups)
1014
				->setMultiple(true)
1015
				->setAttribute('data-placeholder', 'Groups')
1016
				->setAttribute('placeholder', 'Groups')
1017
				->setAttribute('style', 'width: 400px;'),
1018
1019
			ListboxField::create($memberField, false, $members)
1020
				->setMultiple(true)
1021
				->setAttribute('data-placeholder', 'Members')
1022
				->setAttribute('placeholder', 'Members')
1023
				->setAttribute('style', 'width: 400px;')
1024
		);
1025
	}
1026
1027
	/**
1028
	 * @param FieldList $fields
1029
	 */
1030
	protected function setDeployConfigurationFields(&$fields) {
1031
		if (!$this->config()->get('allow_web_editing')) {
1032
			return;
1033
		}
1034
1035
		if ($this->envFileExists()) {
1036
			$deployConfig = new TextareaField('DeployConfig', 'Deploy config', $this->getEnvironmentConfig());
1037
			$deployConfig->setRows(40);
1038
			$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...
1039
			return;
1040
		}
1041
1042
		$warning = 'Warning: This environment doesn\'t have deployment configuration.';
1043
		$noDeployConfig = new LabelField('noDeployConfig', $warning);
1044
		$noDeployConfig->addExtraClass('message warning');
1045
		$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...
1046
		$createConfigField = new CheckboxField('CreateEnvConfig', 'Create Config');
1047
		$createConfigField->setDescription('Would you like to create the capistrano deploy configuration?');
1048
		$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...
1049
	}
1050
1051
	/**
1052
	 * Ensure that environment paths are setup on the local filesystem
1053
	 */
1054
	protected function checkEnvironmentPath() {
1055
		// Create folder if it doesn't exist
1056
		$configDir = dirname($this->getConfigFilename());
1057
		if (!file_exists($configDir) && $configDir) {
1058
			mkdir($configDir, 0777, true);
1059
		}
1060
	}
1061
1062
	/**
1063
	 * Write the deployment config file to filesystem
1064
	 */
1065
	protected function writeConfigFile() {
1066
		if (!$this->config()->get('allow_web_editing')) {
1067
			return;
1068
		}
1069
1070
		// Create a basic new environment config from a template
1071
		if (!$this->envFileExists()
1072
			&& $this->Filename
1073
			&& $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...
1074
		) {
1075
			$templateFile = $this->config()->template_file ?: BASE_PATH . '/deploynaut/environment.template';
1076
			file_put_contents($this->getConfigFilename(), file_get_contents($templateFile));
1077
		} 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...
1078
			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...
1079
		}
1080
	}
1081
1082
	/**
1083
	 * @return string
1084
	 */
1085
	protected function getEnvironmentConfig() {
1086
		if (!$this->envFileExists()) {
1087
			return '';
1088
		}
1089
		return file_get_contents($this->getConfigFilename());
1090
	}
1091
1092
	/**
1093
	 * @return boolean
1094
	 */
1095
	protected function envFileExists() {
1096
		if (!$this->getConfigFilename()) {
1097
			return false;
1098
		}
1099
		return file_exists($this->getConfigFilename());
1100
	}
1101
1102
	protected function validate() {
1103
		$result = parent::validate();
1104
		$backend = $this->Backend();
1105
1106
		if (strcasecmp('test', $this->Name) === 0 && get_class($backend) == 'CapistranoDeploymentBackend') {
1107
			$result->error('"test" is not a valid environment name when using Capistrano backend.');
1108
		}
1109
1110
		return $result;
1111
	}
1112
1113
}
1114