Completed
Pull Request — master (#653)
by Sean
12:03
created

DNEnvironment::UpcomingDeployments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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