Completed
Pull Request — master (#489)
by Helpful
03:34
created
code/model/DNDataArchive.php 3 patches
Indentation   +540 added lines, -540 removed lines patch added patch discarded remove patch
@@ -50,544 +50,544 @@
 block discarded – undo
50 50
 class DNDataArchive extends DataObject
51 51
 {
52 52
 
53
-    private static $db = array(
54
-        'UploadToken' => 'Varchar(8)',
55
-        'ArchiveFileHash' => 'Varchar(32)',
56
-        "Mode" => "Enum('all, assets, db', '')",
57
-        "IsBackup" => "Boolean",
58
-        "IsManualUpload" => "Boolean",
59
-    );
60
-
61
-    private static $has_one = array(
62
-        'Author' => 'Member',
63
-        'OriginalEnvironment' => 'DNEnvironment',
64
-        'Environment' => 'DNEnvironment',
65
-        'ArchiveFile' => 'File'
66
-    );
67
-
68
-    private static $has_many = array(
69
-        'DataTransfers' => 'DNDataTransfer',
70
-    );
71
-
72
-    private static $singular_name = 'Data Archive';
73
-
74
-    private static $plural_name = 'Data Archives';
75
-
76
-    private static $summary_fields = array(
77
-        'Created' => 'Created',
78
-        'Author.Title' => 'Author',
79
-        'Environment.Project.Name' => 'Project',
80
-        'OriginalEnvironment.Name' => 'Origin',
81
-        'Environment.Name' => 'Environment',
82
-        'ArchiveFile.Name' => 'File',
83
-    );
84
-
85
-    private static $searchable_fields = array(
86
-        'Environment.Project.Name' => array(
87
-            'title' => 'Project',
88
-        ),
89
-        'OriginalEnvironment.Name' => array(
90
-            'title' => 'Origin',
91
-        ),
92
-        'Environment.Name' => array(
93
-            'title' => 'Environment',
94
-        ),
95
-        'UploadToken' => array(
96
-            'title' => 'Upload Token',
97
-        ),
98
-        'Mode' => array(
99
-            'title' => 'Mode',
100
-        ),
101
-    );
102
-
103
-    private static $_cache_can_restore = array();
104
-
105
-    private static $_cache_can_download = array();
106
-
107
-    public static function get_mode_map()
108
-    {
109
-        return array(
110
-            'all' => 'Database and Assets',
111
-            'db' => 'Database only',
112
-            'assets' => 'Assets only',
113
-        );
114
-    }
115
-
116
-    /**
117
-     * Returns a unique token to correlate an offline item (posted DVD)
118
-     * with a specific archive placeholder.
119
-     *
120
-     * @return string
121
-     */
122
-    public static function generate_upload_token($chars = 8)
123
-    {
124
-        $generator = new RandomGenerator();
125
-        return strtoupper(substr($generator->randomToken(), 0, $chars));
126
-    }
127
-
128
-    public function onBeforeWrite()
129
-    {
130
-        if (!$this->AuthorID) {
131
-            $this->AuthorID = Member::currentUserID();
132
-        }
133
-
134
-        parent::onBeforeWrite();
135
-    }
136
-
137
-    public function onAfterDelete()
138
-    {
139
-        $this->ArchiveFile()->delete();
140
-    }
141
-
142
-    public function getCMSFields()
143
-    {
144
-        $fields = parent::getCMSFields();
145
-        $fields->removeByName('OriginalEnvironmentID');
146
-        $fields->removeByName('EnvironmentID');
147
-        $fields->removeByName('ArchiveFile');
148
-        $fields->addFieldsToTab(
149
-            'Root.Main',
150
-            array(
151
-                new ReadonlyField('ProjectName', 'Project', $this->Environment()->Project()->Name),
152
-                new ReadonlyField('OriginalEnvironmentName', 'OriginalEnvironment', $this->OriginalEnvironment()->Name),
153
-                new ReadonlyField('EnvironmentName', 'Environment', $this->Environment()->Name),
154
-                $linkField = new ReadonlyField(
155
-                    'DataArchive',
156
-                    'Archive File',
157
-                    sprintf(
158
-                        '<a href="%s">%s</a>',
159
-                        $this->ArchiveFile()->AbsoluteURL,
160
-                        $this->ArchiveFile()->Filename
161
-                    )
162
-                ),
163
-                new GridField(
164
-                    'DataTransfers',
165
-                    'Transfers',
166
-                    $this->DataTransfers()
167
-                ),
168
-            )
169
-        );
170
-        $linkField->dontEscape = true;
171
-        $fields = $fields->makeReadonly();
172
-
173
-        return $fields;
174
-    }
175
-
176
-    public function getDefaultSearchContext()
177
-    {
178
-        $context = parent::getDefaultSearchContext();
179
-        $context->getFields()->dataFieldByName('Mode')->setHasEmptyDefault(true);
180
-
181
-        return $context;
182
-    }
183
-
184
-    /**
185
-     * Calculates and returns a human-readable size of this archive file. If the file exists, it will determine
186
-     * whether to display the output in bytes, kilobytes, megabytes, or gigabytes.
187
-     *
188
-     * @return string The human-readable size of this archive file
189
-     */
190
-    public function FileSize()
191
-    {
192
-        if ($this->ArchiveFile()->exists()) {
193
-            return $this->ArchiveFile()->getSize();
194
-        } else {
195
-            return "N/A";
196
-        }
197
-    }
198
-
199
-    public function getModeNice()
200
-    {
201
-        if ($this->Mode == 'all') {
202
-            return 'database and assets';
203
-        } else {
204
-            return $this->Mode;
205
-        }
206
-    }
207
-
208
-    /**
209
-     * Some archives don't have files attached to them yet,
210
-     * because a file has been posted offline and is waiting to be uploaded
211
-     * against this "archive placeholder".
212
-     *
213
-     * @return boolean
214
-     */
215
-    public function isPending()
216
-    {
217
-        return !($this->ArchiveFileID);
218
-    }
219
-
220
-    /**
221
-     * Inferred from both restore and backup permissions.
222
-     *
223
-     * @param Member|null $member The {@link Member} object to test against.
224
-     */
225
-    public function canView($member = null)
226
-    {
227
-        return ($this->canRestore($member) || $this->canDownload($member));
228
-    }
229
-
230
-    /**
231
-     * Whether a {@link Member} can restore this archive to an environment.
232
-     * This only needs to be checked *once* per member and environment.
233
-     *
234
-     * @param Member|null $member The {@link Member} object to test against.
235
-     * @return true if $member (or the currently logged in member if null) can upload this archive
236
-     */
237
-    public function canRestore($member = null)
238
-    {
239
-        $memberID = $member ? $member->ID : Member::currentUserID();
240
-        if (!$memberID) {
241
-            return false;
242
-        }
243
-
244
-        $key = $memberID . '-' . $this->EnvironmentID;
245
-        if (!isset(self::$_cache_can_restore[$key])) {
246
-            self::$_cache_can_restore[$key] = $this->Environment()->canUploadArchive($member);
247
-        }
248
-
249
-        return self::$_cache_can_restore[$key];
250
-    }
251
-
252
-    /**
253
-     * Whether a {@link Member} can download this archive to their PC.
254
-     * This only needs to be checked *once* per member and environment.
255
-     *
256
-     * @param Member|null $member The {@link Member} object to test against.
257
-     * @return true if $member (or the currently logged in member if null) can download this archive
258
-     */
259
-    public function canDownload($member = null)
260
-    {
261
-        $memberID = $member ? $member->ID : Member::currentUserID();
262
-        if (!$memberID) {
263
-            return false;
264
-        }
265
-
266
-        $key = $memberID . '-' . $this->EnvironmentID;
267
-        if (!isset(self::$_cache_can_download[$key])) {
268
-            self::$_cache_can_download[$key] = $this->Environment()->canDownloadArchive($member);
269
-        }
270
-        return self::$_cache_can_download[$key];
271
-    }
272
-
273
-    /**
274
-     * Whether a {@link Member} can delete this archive from staging area.
275
-     *
276
-     * @param Member|null $member The {@link Member} object to test against.
277
-     * @return boolean if $member (or the currently logged in member if null) can delete this archive
278
-     */
279
-    public function canDelete($member = null)
280
-    {
281
-        return $this->Environment()->canDeleteArchive($member);
282
-    }
283
-
284
-    /**
285
-     * Check if this member can move archive into the environment.
286
-     *
287
-     * @param DNEnvironment $targetEnv Environment to check.
288
-     * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
289
-     *
290
-     * @return boolean true if $member can upload archives linked to this environment, false if they can't.
291
-     */
292
-    public function canMoveTo($targetEnv, $member = null)
293
-    {
294
-        if ($this->Environment()->Project()->ID != $targetEnv->Project()->ID) {
295
-            // We don't permit moving snapshots between projects at this stage.
296
-            return false;
297
-        }
298
-
299
-        if (!$member) {
300
-            $member = Member::currentUser();
301
-        }
302
-
303
-        // Must be logged in to check permissions
304
-        if (!$member) {
305
-            return false;
306
-        }
307
-
308
-        // Admin can always move.
309
-        if (Permission::checkMember($member, 'ADMIN')) {
310
-            return true;
311
-        }
312
-
313
-        // Checks if the user can actually access the archive.
314
-        if (!$this->canDownload($member)) {
315
-            return false;
316
-        }
317
-
318
-        // Hooks into ArchiveUploaders permission to prevent proliferation of permission checkboxes.
319
-        // Bypasses the quota check - we don't need to check for it as long as we move the snapshot within the project.
320
-        return $targetEnv->ArchiveUploaders()->byID($member->ID)
321
-            || $member->inGroups($targetEnv->ArchiveUploaderGroups());
322
-    }
323
-
324
-    /**
325
-     * Finds all environments within this project where the archive can be moved to.
326
-     * Excludes current environment automatically.
327
-     *
328
-     * @return ArrayList List of valid environments.
329
-     */
330
-    public function validTargetEnvironments()
331
-    {
332
-        $archive = $this;
333
-        $envs = $this->Environment()->Project()->DNEnvironmentList()
334
-            ->filterByCallback(function ($item) use ($archive) {
335
-                return $archive->EnvironmentID != $item->ID && $archive->canMoveTo($item);
336
-            });
337
-
338
-        return $envs;
339
-    }
340
-
341
-    /**
342
-     * Returns a unique filename, including project/environment/timestamp details.
343
-     * @return string
344
-     */
345
-    public function generateFilename(DNDataTransfer $dataTransfer)
346
-    {
347
-        $generator = new RandomGenerator();
348
-        $filter = FileNameFilter::create();
349
-
350
-        return sprintf(
351
-            '%s-%s-%s-%s-%s',
352
-            $filter->filter(strtolower($this->OriginalEnvironment()->Project()->Name)),
353
-            $filter->filter(strtolower($this->OriginalEnvironment()->Name)),
354
-            $dataTransfer->Mode,
355
-            date('Ymd'),
356
-            sha1($generator->generateEntropy())
357
-        );
358
-    }
359
-
360
-    /**
361
-     * Returns a path unique to a specific transfer, including project/environment details.
362
-     * Does not create the path on the filesystem. Can be used to store files related to this transfer.
363
-     *
364
-     * @param DNDataTransfer
365
-     * @return string Absolute file path
366
-     */
367
-    public function generateFilepath(DNDataTransfer $dataTransfer)
368
-    {
369
-        $data = DNData::inst();
370
-        $transferDir = $data->getDataTransferDir();
371
-        $filter = FileNameFilter::create();
372
-
373
-        return sprintf('%s/%s/%s/transfer-%s/',
374
-            $transferDir,
375
-            $filter->filter(strtolower($this->OriginalEnvironment()->Project()->Name)),
376
-            $filter->filter(strtolower($this->OriginalEnvironment()->Name)),
377
-            $dataTransfer->ID
378
-        );
379
-    }
380
-
381
-    /**
382
-     * Attach an sspak file path to this archive and associate the transfer.
383
-     * Does the job of creating a {@link File} record, and setting correct paths into the assets directory.
384
-     *
385
-     * @param string $sspakFilepath
386
-     * @param DNDataTransfer $dataTransfer
387
-     * @return bool
388
-     */
389
-    public function attachFile($sspakFilepath, DNDataTransfer $dataTransfer)
390
-    {
391
-        $sspakFilepath = ltrim(
392
-            str_replace(
393
-                array(ASSETS_PATH, realpath(ASSETS_PATH)),
394
-                '',
395
-                $sspakFilepath
396
-            ),
397
-            DIRECTORY_SEPARATOR
398
-        );
399
-
400
-        $folder = Folder::find_or_make(dirname($sspakFilepath));
401
-        $file = new File();
402
-        $file->Name = basename($sspakFilepath);
403
-        $file->Filename = $sspakFilepath;
404
-        $file->ParentID = $folder->ID;
405
-        $file->write();
406
-
407
-        // "Status" will be updated by the job execution
408
-        $dataTransfer->write();
409
-
410
-        // Get file hash to ensure consistency.
411
-        // Only do this when first associating the file since hashing large files is expensive.
412
-        // Note that with CapistranoDeploymentBackend the file won't be available yet, as it
413
-        // gets put in place immediately after this method gets called. In which case, it will
414
-        // be hashed in setArchiveFromFiles()
415
-        if (file_exists($file->FullPath)) {
416
-            $this->ArchiveFileHash = md5_file($file->FullPath);
417
-        }
418
-        $this->ArchiveFileID = $file->ID;
419
-        $this->DataTransfers()->add($dataTransfer);
420
-        $this->write();
421
-
422
-        return true;
423
-    }
424
-
425
-    /**
426
-     * Extract the current sspak contents into the given working directory.
427
-     * This also extracts the assets and database and puts them into
428
-     * <workingdir>/database.sql and <workingdir>/assets, respectively.
429
-     *
430
-     * @param string|null $workingDir The path to extract to
431
-     * @throws RuntimeException
432
-     * @return bool
433
-     */
434
-    public function extractArchive($workingDir = null)
435
-    {
436
-        if (!is_dir($workingDir)) {
437
-            mkdir($workingDir, 0700, true);
438
-        }
439
-
440
-        $cleanupFn = function () use ($workingDir) {
441
-            $process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
442
-            $process->run();
443
-        };
444
-
445
-        // Extract *.sspak to a temporary location
446
-        $sspakFilename = $this->ArchiveFile()->FullPath;
447
-        $process = new Process(sprintf(
448
-            'sspak extract %s %s',
449
-            escapeshellarg($sspakFilename),
450
-            escapeshellarg($workingDir)
451
-        ));
452
-        $process->setTimeout(3600);
453
-        $process->run();
454
-        if (!$process->isSuccessful()) {
455
-            $cleanupFn();
456
-            throw new RuntimeException(sprintf('Could not extract the sspak file: %s', $process->getErrorOutput()));
457
-        }
458
-
459
-        // Extract database.sql.gz to <workingdir>/database.sql
460
-        if (file_exists($workingDir . DIRECTORY_SEPARATOR . 'database.sql.gz')) {
461
-            $process = new Process('gunzip database.sql.gz', $workingDir);
462
-            $process->setTimeout(3600);
463
-            $process->run();
464
-            if (!$process->isSuccessful()) {
465
-                $cleanupFn();
466
-                throw new RuntimeException(sprintf('Could not extract the db archive: %s', $process->getErrorOutput()));
467
-            }
468
-        }
469
-
470
-        // Extract assets.tar.gz to <workingdir>/assets/
471
-        if (file_exists($workingDir . DIRECTORY_SEPARATOR . 'assets.tar.gz')) {
472
-            $process = new Process('tar xzf assets.tar.gz', $workingDir);
473
-            $process->setTimeout(3600);
474
-            $process->run();
475
-            if (!$process->isSuccessful()) {
476
-                $cleanupFn();
477
-                throw new RuntimeException(sprintf('Could not extract the assets archive: %s', $process->getErrorOutput()));
478
-            }
479
-        }
480
-
481
-        return true;
482
-    }
483
-
484
-    /**
485
-     * Validate that an sspak contains the correct content.
486
-     *
487
-     * For example, if the user uploaded an sspak containing just the db, but declared in the form
488
-     * that it contained db+assets, then the archive is not valid.
489
-     *
490
-     * @param string|null $mode "db", "assets", or "all". This is the content we're checking for. Default to the archive setting
491
-     * @return ValidationResult
492
-     */
493
-    public function validateArchiveContents($mode = null)
494
-    {
495
-        $mode = $mode ?: $this->Mode;
496
-        $result = new ValidationResult();
497
-
498
-        $file = $this->ArchiveFile()->FullPath;
499
-
500
-        if (!is_readable($file)) {
501
-            $result->error(sprintf('SSPak file "%s" cannot be read.', $file));
502
-            return $result;
503
-        }
504
-
505
-        $process = new Process(sprintf('tar -tf %s', escapeshellarg($file)));
506
-        $process->setTimeout(120);
507
-        $process->run();
508
-        if (!$process->isSuccessful()) {
509
-            throw new RuntimeException(sprintf('Could not list files in archive: %s', $process->getErrorOutput()));
510
-        }
511
-
512
-        $output = explode(PHP_EOL, $process->getOutput());
513
-        $files = array_filter($output);
514
-
515
-        if (in_array($mode, array('all', 'db')) && !in_array('database.sql.gz', $files)) {
516
-            $result->error('The snapshot is missing the database.');
517
-            return $result;
518
-        }
519
-
520
-        if (in_array($mode, array('all', 'assets')) && !in_array('assets.tar.gz', $files)) {
521
-            $result->error('The snapshot is missing assets.');
522
-            return $result;
523
-        }
524
-
525
-        return $result;
526
-    }
527
-
528
-    /**
529
-     * Given a path that already exists and contains an extracted sspak, including
530
-     * the assets, fix all of the file permissions so they're in a state ready to
531
-     * be pushed to remote servers.
532
-     *
533
-     * Normally, command line tar will use permissions found in the archive, but will
534
-     * substract the user's umask from them. This has a potential to create unreadable
535
-     * files, e.g. cygwin on Windows will pack files with mode 000, hence why this fix
536
-     * is necessary.
537
-     *
538
-     * @param string|null $workingDir The path of where the sspak has been extracted to
539
-     * @throws RuntimeException
540
-     * @return bool
541
-     */
542
-    public function fixArchivePermissions($workingDir)
543
-    {
544
-        $fixCmds = array(
545
-            // The directories need to have permissions changed one by one (hence the ; instead of +),
546
-            // otherwise we might end up having no +x access to a directory deeper down.
547
-            sprintf('find %s -type d -exec chmod 755 {} \;', escapeshellarg($workingDir)),
548
-            sprintf('find %s -type f -exec chmod 644 {} +', escapeshellarg($workingDir))
549
-        );
550
-
551
-        foreach ($fixCmds as $cmd) {
552
-            $process = new Process($cmd);
553
-            $process->setTimeout(3600);
554
-            $process->run();
555
-            if (!$process->isSuccessful()) {
556
-                throw new RuntimeException($process->getErrorOutput());
557
-            }
558
-        }
559
-
560
-        return true;
561
-    }
562
-
563
-    /**
564
-     * Given extracted sspak contents, create an sspak from it
565
-     * and overwrite the current ArchiveFile with it's contents.
566
-     *
567
-     * @param string|null $workingDir The path of where the sspak has been extracted to
568
-     * @return bool
569
-     */
570
-    public function setArchiveFromFiles($workingDir)
571
-    {
572
-        $command = sprintf('sspak saveexisting %s 2>&1', $this->ArchiveFile()->FullPath);
573
-        if ($this->Mode == 'db') {
574
-            $command .= sprintf(' --db=%s/database.sql', $workingDir);
575
-        } elseif ($this->Mode == 'assets') {
576
-            $command .= sprintf(' --assets=%s/assets', $workingDir);
577
-        } else {
578
-            $command .= sprintf(' --db=%s/database.sql --assets=%s/assets', $workingDir, $workingDir);
579
-        }
580
-
581
-        $process = new Process($command, $workingDir);
582
-        $process->setTimeout(3600);
583
-        $process->run();
584
-        if (!$process->isSuccessful()) {
585
-            throw new RuntimeException($process->getErrorOutput());
586
-        }
587
-
588
-        $this->ArchiveFileHash = md5_file($this->ArchiveFile()->FullPath);
589
-        $this->write();
590
-
591
-        return true;
592
-    }
53
+	private static $db = array(
54
+		'UploadToken' => 'Varchar(8)',
55
+		'ArchiveFileHash' => 'Varchar(32)',
56
+		"Mode" => "Enum('all, assets, db', '')",
57
+		"IsBackup" => "Boolean",
58
+		"IsManualUpload" => "Boolean",
59
+	);
60
+
61
+	private static $has_one = array(
62
+		'Author' => 'Member',
63
+		'OriginalEnvironment' => 'DNEnvironment',
64
+		'Environment' => 'DNEnvironment',
65
+		'ArchiveFile' => 'File'
66
+	);
67
+
68
+	private static $has_many = array(
69
+		'DataTransfers' => 'DNDataTransfer',
70
+	);
71
+
72
+	private static $singular_name = 'Data Archive';
73
+
74
+	private static $plural_name = 'Data Archives';
75
+
76
+	private static $summary_fields = array(
77
+		'Created' => 'Created',
78
+		'Author.Title' => 'Author',
79
+		'Environment.Project.Name' => 'Project',
80
+		'OriginalEnvironment.Name' => 'Origin',
81
+		'Environment.Name' => 'Environment',
82
+		'ArchiveFile.Name' => 'File',
83
+	);
84
+
85
+	private static $searchable_fields = array(
86
+		'Environment.Project.Name' => array(
87
+			'title' => 'Project',
88
+		),
89
+		'OriginalEnvironment.Name' => array(
90
+			'title' => 'Origin',
91
+		),
92
+		'Environment.Name' => array(
93
+			'title' => 'Environment',
94
+		),
95
+		'UploadToken' => array(
96
+			'title' => 'Upload Token',
97
+		),
98
+		'Mode' => array(
99
+			'title' => 'Mode',
100
+		),
101
+	);
102
+
103
+	private static $_cache_can_restore = array();
104
+
105
+	private static $_cache_can_download = array();
106
+
107
+	public static function get_mode_map()
108
+	{
109
+		return array(
110
+			'all' => 'Database and Assets',
111
+			'db' => 'Database only',
112
+			'assets' => 'Assets only',
113
+		);
114
+	}
115
+
116
+	/**
117
+	 * Returns a unique token to correlate an offline item (posted DVD)
118
+	 * with a specific archive placeholder.
119
+	 *
120
+	 * @return string
121
+	 */
122
+	public static function generate_upload_token($chars = 8)
123
+	{
124
+		$generator = new RandomGenerator();
125
+		return strtoupper(substr($generator->randomToken(), 0, $chars));
126
+	}
127
+
128
+	public function onBeforeWrite()
129
+	{
130
+		if (!$this->AuthorID) {
131
+			$this->AuthorID = Member::currentUserID();
132
+		}
133
+
134
+		parent::onBeforeWrite();
135
+	}
136
+
137
+	public function onAfterDelete()
138
+	{
139
+		$this->ArchiveFile()->delete();
140
+	}
141
+
142
+	public function getCMSFields()
143
+	{
144
+		$fields = parent::getCMSFields();
145
+		$fields->removeByName('OriginalEnvironmentID');
146
+		$fields->removeByName('EnvironmentID');
147
+		$fields->removeByName('ArchiveFile');
148
+		$fields->addFieldsToTab(
149
+			'Root.Main',
150
+			array(
151
+				new ReadonlyField('ProjectName', 'Project', $this->Environment()->Project()->Name),
152
+				new ReadonlyField('OriginalEnvironmentName', 'OriginalEnvironment', $this->OriginalEnvironment()->Name),
153
+				new ReadonlyField('EnvironmentName', 'Environment', $this->Environment()->Name),
154
+				$linkField = new ReadonlyField(
155
+					'DataArchive',
156
+					'Archive File',
157
+					sprintf(
158
+						'<a href="%s">%s</a>',
159
+						$this->ArchiveFile()->AbsoluteURL,
160
+						$this->ArchiveFile()->Filename
161
+					)
162
+				),
163
+				new GridField(
164
+					'DataTransfers',
165
+					'Transfers',
166
+					$this->DataTransfers()
167
+				),
168
+			)
169
+		);
170
+		$linkField->dontEscape = true;
171
+		$fields = $fields->makeReadonly();
172
+
173
+		return $fields;
174
+	}
175
+
176
+	public function getDefaultSearchContext()
177
+	{
178
+		$context = parent::getDefaultSearchContext();
179
+		$context->getFields()->dataFieldByName('Mode')->setHasEmptyDefault(true);
180
+
181
+		return $context;
182
+	}
183
+
184
+	/**
185
+	 * Calculates and returns a human-readable size of this archive file. If the file exists, it will determine
186
+	 * whether to display the output in bytes, kilobytes, megabytes, or gigabytes.
187
+	 *
188
+	 * @return string The human-readable size of this archive file
189
+	 */
190
+	public function FileSize()
191
+	{
192
+		if ($this->ArchiveFile()->exists()) {
193
+			return $this->ArchiveFile()->getSize();
194
+		} else {
195
+			return "N/A";
196
+		}
197
+	}
198
+
199
+	public function getModeNice()
200
+	{
201
+		if ($this->Mode == 'all') {
202
+			return 'database and assets';
203
+		} else {
204
+			return $this->Mode;
205
+		}
206
+	}
207
+
208
+	/**
209
+	 * Some archives don't have files attached to them yet,
210
+	 * because a file has been posted offline and is waiting to be uploaded
211
+	 * against this "archive placeholder".
212
+	 *
213
+	 * @return boolean
214
+	 */
215
+	public function isPending()
216
+	{
217
+		return !($this->ArchiveFileID);
218
+	}
219
+
220
+	/**
221
+	 * Inferred from both restore and backup permissions.
222
+	 *
223
+	 * @param Member|null $member The {@link Member} object to test against.
224
+	 */
225
+	public function canView($member = null)
226
+	{
227
+		return ($this->canRestore($member) || $this->canDownload($member));
228
+	}
229
+
230
+	/**
231
+	 * Whether a {@link Member} can restore this archive to an environment.
232
+	 * This only needs to be checked *once* per member and environment.
233
+	 *
234
+	 * @param Member|null $member The {@link Member} object to test against.
235
+	 * @return true if $member (or the currently logged in member if null) can upload this archive
236
+	 */
237
+	public function canRestore($member = null)
238
+	{
239
+		$memberID = $member ? $member->ID : Member::currentUserID();
240
+		if (!$memberID) {
241
+			return false;
242
+		}
243
+
244
+		$key = $memberID . '-' . $this->EnvironmentID;
245
+		if (!isset(self::$_cache_can_restore[$key])) {
246
+			self::$_cache_can_restore[$key] = $this->Environment()->canUploadArchive($member);
247
+		}
248
+
249
+		return self::$_cache_can_restore[$key];
250
+	}
251
+
252
+	/**
253
+	 * Whether a {@link Member} can download this archive to their PC.
254
+	 * This only needs to be checked *once* per member and environment.
255
+	 *
256
+	 * @param Member|null $member The {@link Member} object to test against.
257
+	 * @return true if $member (or the currently logged in member if null) can download this archive
258
+	 */
259
+	public function canDownload($member = null)
260
+	{
261
+		$memberID = $member ? $member->ID : Member::currentUserID();
262
+		if (!$memberID) {
263
+			return false;
264
+		}
265
+
266
+		$key = $memberID . '-' . $this->EnvironmentID;
267
+		if (!isset(self::$_cache_can_download[$key])) {
268
+			self::$_cache_can_download[$key] = $this->Environment()->canDownloadArchive($member);
269
+		}
270
+		return self::$_cache_can_download[$key];
271
+	}
272
+
273
+	/**
274
+	 * Whether a {@link Member} can delete this archive from staging area.
275
+	 *
276
+	 * @param Member|null $member The {@link Member} object to test against.
277
+	 * @return boolean if $member (or the currently logged in member if null) can delete this archive
278
+	 */
279
+	public function canDelete($member = null)
280
+	{
281
+		return $this->Environment()->canDeleteArchive($member);
282
+	}
283
+
284
+	/**
285
+	 * Check if this member can move archive into the environment.
286
+	 *
287
+	 * @param DNEnvironment $targetEnv Environment to check.
288
+	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
289
+	 *
290
+	 * @return boolean true if $member can upload archives linked to this environment, false if they can't.
291
+	 */
292
+	public function canMoveTo($targetEnv, $member = null)
293
+	{
294
+		if ($this->Environment()->Project()->ID != $targetEnv->Project()->ID) {
295
+			// We don't permit moving snapshots between projects at this stage.
296
+			return false;
297
+		}
298
+
299
+		if (!$member) {
300
+			$member = Member::currentUser();
301
+		}
302
+
303
+		// Must be logged in to check permissions
304
+		if (!$member) {
305
+			return false;
306
+		}
307
+
308
+		// Admin can always move.
309
+		if (Permission::checkMember($member, 'ADMIN')) {
310
+			return true;
311
+		}
312
+
313
+		// Checks if the user can actually access the archive.
314
+		if (!$this->canDownload($member)) {
315
+			return false;
316
+		}
317
+
318
+		// Hooks into ArchiveUploaders permission to prevent proliferation of permission checkboxes.
319
+		// Bypasses the quota check - we don't need to check for it as long as we move the snapshot within the project.
320
+		return $targetEnv->ArchiveUploaders()->byID($member->ID)
321
+			|| $member->inGroups($targetEnv->ArchiveUploaderGroups());
322
+	}
323
+
324
+	/**
325
+	 * Finds all environments within this project where the archive can be moved to.
326
+	 * Excludes current environment automatically.
327
+	 *
328
+	 * @return ArrayList List of valid environments.
329
+	 */
330
+	public function validTargetEnvironments()
331
+	{
332
+		$archive = $this;
333
+		$envs = $this->Environment()->Project()->DNEnvironmentList()
334
+			->filterByCallback(function ($item) use ($archive) {
335
+				return $archive->EnvironmentID != $item->ID && $archive->canMoveTo($item);
336
+			});
337
+
338
+		return $envs;
339
+	}
340
+
341
+	/**
342
+	 * Returns a unique filename, including project/environment/timestamp details.
343
+	 * @return string
344
+	 */
345
+	public function generateFilename(DNDataTransfer $dataTransfer)
346
+	{
347
+		$generator = new RandomGenerator();
348
+		$filter = FileNameFilter::create();
349
+
350
+		return sprintf(
351
+			'%s-%s-%s-%s-%s',
352
+			$filter->filter(strtolower($this->OriginalEnvironment()->Project()->Name)),
353
+			$filter->filter(strtolower($this->OriginalEnvironment()->Name)),
354
+			$dataTransfer->Mode,
355
+			date('Ymd'),
356
+			sha1($generator->generateEntropy())
357
+		);
358
+	}
359
+
360
+	/**
361
+	 * Returns a path unique to a specific transfer, including project/environment details.
362
+	 * Does not create the path on the filesystem. Can be used to store files related to this transfer.
363
+	 *
364
+	 * @param DNDataTransfer
365
+	 * @return string Absolute file path
366
+	 */
367
+	public function generateFilepath(DNDataTransfer $dataTransfer)
368
+	{
369
+		$data = DNData::inst();
370
+		$transferDir = $data->getDataTransferDir();
371
+		$filter = FileNameFilter::create();
372
+
373
+		return sprintf('%s/%s/%s/transfer-%s/',
374
+			$transferDir,
375
+			$filter->filter(strtolower($this->OriginalEnvironment()->Project()->Name)),
376
+			$filter->filter(strtolower($this->OriginalEnvironment()->Name)),
377
+			$dataTransfer->ID
378
+		);
379
+	}
380
+
381
+	/**
382
+	 * Attach an sspak file path to this archive and associate the transfer.
383
+	 * Does the job of creating a {@link File} record, and setting correct paths into the assets directory.
384
+	 *
385
+	 * @param string $sspakFilepath
386
+	 * @param DNDataTransfer $dataTransfer
387
+	 * @return bool
388
+	 */
389
+	public function attachFile($sspakFilepath, DNDataTransfer $dataTransfer)
390
+	{
391
+		$sspakFilepath = ltrim(
392
+			str_replace(
393
+				array(ASSETS_PATH, realpath(ASSETS_PATH)),
394
+				'',
395
+				$sspakFilepath
396
+			),
397
+			DIRECTORY_SEPARATOR
398
+		);
399
+
400
+		$folder = Folder::find_or_make(dirname($sspakFilepath));
401
+		$file = new File();
402
+		$file->Name = basename($sspakFilepath);
403
+		$file->Filename = $sspakFilepath;
404
+		$file->ParentID = $folder->ID;
405
+		$file->write();
406
+
407
+		// "Status" will be updated by the job execution
408
+		$dataTransfer->write();
409
+
410
+		// Get file hash to ensure consistency.
411
+		// Only do this when first associating the file since hashing large files is expensive.
412
+		// Note that with CapistranoDeploymentBackend the file won't be available yet, as it
413
+		// gets put in place immediately after this method gets called. In which case, it will
414
+		// be hashed in setArchiveFromFiles()
415
+		if (file_exists($file->FullPath)) {
416
+			$this->ArchiveFileHash = md5_file($file->FullPath);
417
+		}
418
+		$this->ArchiveFileID = $file->ID;
419
+		$this->DataTransfers()->add($dataTransfer);
420
+		$this->write();
421
+
422
+		return true;
423
+	}
424
+
425
+	/**
426
+	 * Extract the current sspak contents into the given working directory.
427
+	 * This also extracts the assets and database and puts them into
428
+	 * <workingdir>/database.sql and <workingdir>/assets, respectively.
429
+	 *
430
+	 * @param string|null $workingDir The path to extract to
431
+	 * @throws RuntimeException
432
+	 * @return bool
433
+	 */
434
+	public function extractArchive($workingDir = null)
435
+	{
436
+		if (!is_dir($workingDir)) {
437
+			mkdir($workingDir, 0700, true);
438
+		}
439
+
440
+		$cleanupFn = function () use ($workingDir) {
441
+			$process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
442
+			$process->run();
443
+		};
444
+
445
+		// Extract *.sspak to a temporary location
446
+		$sspakFilename = $this->ArchiveFile()->FullPath;
447
+		$process = new Process(sprintf(
448
+			'sspak extract %s %s',
449
+			escapeshellarg($sspakFilename),
450
+			escapeshellarg($workingDir)
451
+		));
452
+		$process->setTimeout(3600);
453
+		$process->run();
454
+		if (!$process->isSuccessful()) {
455
+			$cleanupFn();
456
+			throw new RuntimeException(sprintf('Could not extract the sspak file: %s', $process->getErrorOutput()));
457
+		}
458
+
459
+		// Extract database.sql.gz to <workingdir>/database.sql
460
+		if (file_exists($workingDir . DIRECTORY_SEPARATOR . 'database.sql.gz')) {
461
+			$process = new Process('gunzip database.sql.gz', $workingDir);
462
+			$process->setTimeout(3600);
463
+			$process->run();
464
+			if (!$process->isSuccessful()) {
465
+				$cleanupFn();
466
+				throw new RuntimeException(sprintf('Could not extract the db archive: %s', $process->getErrorOutput()));
467
+			}
468
+		}
469
+
470
+		// Extract assets.tar.gz to <workingdir>/assets/
471
+		if (file_exists($workingDir . DIRECTORY_SEPARATOR . 'assets.tar.gz')) {
472
+			$process = new Process('tar xzf assets.tar.gz', $workingDir);
473
+			$process->setTimeout(3600);
474
+			$process->run();
475
+			if (!$process->isSuccessful()) {
476
+				$cleanupFn();
477
+				throw new RuntimeException(sprintf('Could not extract the assets archive: %s', $process->getErrorOutput()));
478
+			}
479
+		}
480
+
481
+		return true;
482
+	}
483
+
484
+	/**
485
+	 * Validate that an sspak contains the correct content.
486
+	 *
487
+	 * For example, if the user uploaded an sspak containing just the db, but declared in the form
488
+	 * that it contained db+assets, then the archive is not valid.
489
+	 *
490
+	 * @param string|null $mode "db", "assets", or "all". This is the content we're checking for. Default to the archive setting
491
+	 * @return ValidationResult
492
+	 */
493
+	public function validateArchiveContents($mode = null)
494
+	{
495
+		$mode = $mode ?: $this->Mode;
496
+		$result = new ValidationResult();
497
+
498
+		$file = $this->ArchiveFile()->FullPath;
499
+
500
+		if (!is_readable($file)) {
501
+			$result->error(sprintf('SSPak file "%s" cannot be read.', $file));
502
+			return $result;
503
+		}
504
+
505
+		$process = new Process(sprintf('tar -tf %s', escapeshellarg($file)));
506
+		$process->setTimeout(120);
507
+		$process->run();
508
+		if (!$process->isSuccessful()) {
509
+			throw new RuntimeException(sprintf('Could not list files in archive: %s', $process->getErrorOutput()));
510
+		}
511
+
512
+		$output = explode(PHP_EOL, $process->getOutput());
513
+		$files = array_filter($output);
514
+
515
+		if (in_array($mode, array('all', 'db')) && !in_array('database.sql.gz', $files)) {
516
+			$result->error('The snapshot is missing the database.');
517
+			return $result;
518
+		}
519
+
520
+		if (in_array($mode, array('all', 'assets')) && !in_array('assets.tar.gz', $files)) {
521
+			$result->error('The snapshot is missing assets.');
522
+			return $result;
523
+		}
524
+
525
+		return $result;
526
+	}
527
+
528
+	/**
529
+	 * Given a path that already exists and contains an extracted sspak, including
530
+	 * the assets, fix all of the file permissions so they're in a state ready to
531
+	 * be pushed to remote servers.
532
+	 *
533
+	 * Normally, command line tar will use permissions found in the archive, but will
534
+	 * substract the user's umask from them. This has a potential to create unreadable
535
+	 * files, e.g. cygwin on Windows will pack files with mode 000, hence why this fix
536
+	 * is necessary.
537
+	 *
538
+	 * @param string|null $workingDir The path of where the sspak has been extracted to
539
+	 * @throws RuntimeException
540
+	 * @return bool
541
+	 */
542
+	public function fixArchivePermissions($workingDir)
543
+	{
544
+		$fixCmds = array(
545
+			// The directories need to have permissions changed one by one (hence the ; instead of +),
546
+			// otherwise we might end up having no +x access to a directory deeper down.
547
+			sprintf('find %s -type d -exec chmod 755 {} \;', escapeshellarg($workingDir)),
548
+			sprintf('find %s -type f -exec chmod 644 {} +', escapeshellarg($workingDir))
549
+		);
550
+
551
+		foreach ($fixCmds as $cmd) {
552
+			$process = new Process($cmd);
553
+			$process->setTimeout(3600);
554
+			$process->run();
555
+			if (!$process->isSuccessful()) {
556
+				throw new RuntimeException($process->getErrorOutput());
557
+			}
558
+		}
559
+
560
+		return true;
561
+	}
562
+
563
+	/**
564
+	 * Given extracted sspak contents, create an sspak from it
565
+	 * and overwrite the current ArchiveFile with it's contents.
566
+	 *
567
+	 * @param string|null $workingDir The path of where the sspak has been extracted to
568
+	 * @return bool
569
+	 */
570
+	public function setArchiveFromFiles($workingDir)
571
+	{
572
+		$command = sprintf('sspak saveexisting %s 2>&1', $this->ArchiveFile()->FullPath);
573
+		if ($this->Mode == 'db') {
574
+			$command .= sprintf(' --db=%s/database.sql', $workingDir);
575
+		} elseif ($this->Mode == 'assets') {
576
+			$command .= sprintf(' --assets=%s/assets', $workingDir);
577
+		} else {
578
+			$command .= sprintf(' --db=%s/database.sql --assets=%s/assets', $workingDir, $workingDir);
579
+		}
580
+
581
+		$process = new Process($command, $workingDir);
582
+		$process->setTimeout(3600);
583
+		$process->run();
584
+		if (!$process->isSuccessful()) {
585
+			throw new RuntimeException($process->getErrorOutput());
586
+		}
587
+
588
+		$this->ArchiveFileHash = md5_file($this->ArchiveFile()->FullPath);
589
+		$this->write();
590
+
591
+		return true;
592
+	}
593 593
 }
Please login to merge, or discard this patch.
Spacing   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 
128 128
     public function onBeforeWrite()
129 129
     {
130
-        if (!$this->AuthorID) {
130
+        if(!$this->AuthorID) {
131 131
             $this->AuthorID = Member::currentUserID();
132 132
         }
133 133
 
@@ -189,7 +189,7 @@  discard block
 block discarded – undo
189 189
      */
190 190
     public function FileSize()
191 191
     {
192
-        if ($this->ArchiveFile()->exists()) {
192
+        if($this->ArchiveFile()->exists()) {
193 193
             return $this->ArchiveFile()->getSize();
194 194
         } else {
195 195
             return "N/A";
@@ -198,7 +198,7 @@  discard block
 block discarded – undo
198 198
 
199 199
     public function getModeNice()
200 200
     {
201
-        if ($this->Mode == 'all') {
201
+        if($this->Mode == 'all') {
202 202
             return 'database and assets';
203 203
         } else {
204 204
             return $this->Mode;
@@ -237,12 +237,12 @@  discard block
 block discarded – undo
237 237
     public function canRestore($member = null)
238 238
     {
239 239
         $memberID = $member ? $member->ID : Member::currentUserID();
240
-        if (!$memberID) {
240
+        if(!$memberID) {
241 241
             return false;
242 242
         }
243 243
 
244 244
         $key = $memberID . '-' . $this->EnvironmentID;
245
-        if (!isset(self::$_cache_can_restore[$key])) {
245
+        if(!isset(self::$_cache_can_restore[$key])) {
246 246
             self::$_cache_can_restore[$key] = $this->Environment()->canUploadArchive($member);
247 247
         }
248 248
 
@@ -259,12 +259,12 @@  discard block
 block discarded – undo
259 259
     public function canDownload($member = null)
260 260
     {
261 261
         $memberID = $member ? $member->ID : Member::currentUserID();
262
-        if (!$memberID) {
262
+        if(!$memberID) {
263 263
             return false;
264 264
         }
265 265
 
266 266
         $key = $memberID . '-' . $this->EnvironmentID;
267
-        if (!isset(self::$_cache_can_download[$key])) {
267
+        if(!isset(self::$_cache_can_download[$key])) {
268 268
             self::$_cache_can_download[$key] = $this->Environment()->canDownloadArchive($member);
269 269
         }
270 270
         return self::$_cache_can_download[$key];
@@ -291,27 +291,27 @@  discard block
 block discarded – undo
291 291
      */
292 292
     public function canMoveTo($targetEnv, $member = null)
293 293
     {
294
-        if ($this->Environment()->Project()->ID != $targetEnv->Project()->ID) {
294
+        if($this->Environment()->Project()->ID != $targetEnv->Project()->ID) {
295 295
             // We don't permit moving snapshots between projects at this stage.
296 296
             return false;
297 297
         }
298 298
 
299
-        if (!$member) {
299
+        if(!$member) {
300 300
             $member = Member::currentUser();
301 301
         }
302 302
 
303 303
         // Must be logged in to check permissions
304
-        if (!$member) {
304
+        if(!$member) {
305 305
             return false;
306 306
         }
307 307
 
308 308
         // Admin can always move.
309
-        if (Permission::checkMember($member, 'ADMIN')) {
309
+        if(Permission::checkMember($member, 'ADMIN')) {
310 310
             return true;
311 311
         }
312 312
 
313 313
         // Checks if the user can actually access the archive.
314
-        if (!$this->canDownload($member)) {
314
+        if(!$this->canDownload($member)) {
315 315
             return false;
316 316
         }
317 317
 
@@ -331,7 +331,7 @@  discard block
 block discarded – undo
331 331
     {
332 332
         $archive = $this;
333 333
         $envs = $this->Environment()->Project()->DNEnvironmentList()
334
-            ->filterByCallback(function ($item) use ($archive) {
334
+            ->filterByCallback(function($item) use ($archive) {
335 335
                 return $archive->EnvironmentID != $item->ID && $archive->canMoveTo($item);
336 336
             });
337 337
 
@@ -412,7 +412,7 @@  discard block
 block discarded – undo
412 412
         // Note that with CapistranoDeploymentBackend the file won't be available yet, as it
413 413
         // gets put in place immediately after this method gets called. In which case, it will
414 414
         // be hashed in setArchiveFromFiles()
415
-        if (file_exists($file->FullPath)) {
415
+        if(file_exists($file->FullPath)) {
416 416
             $this->ArchiveFileHash = md5_file($file->FullPath);
417 417
         }
418 418
         $this->ArchiveFileID = $file->ID;
@@ -433,11 +433,11 @@  discard block
 block discarded – undo
433 433
      */
434 434
     public function extractArchive($workingDir = null)
435 435
     {
436
-        if (!is_dir($workingDir)) {
436
+        if(!is_dir($workingDir)) {
437 437
             mkdir($workingDir, 0700, true);
438 438
         }
439 439
 
440
-        $cleanupFn = function () use ($workingDir) {
440
+        $cleanupFn = function() use ($workingDir) {
441 441
             $process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
442 442
             $process->run();
443 443
         };
@@ -451,28 +451,28 @@  discard block
 block discarded – undo
451 451
         ));
452 452
         $process->setTimeout(3600);
453 453
         $process->run();
454
-        if (!$process->isSuccessful()) {
454
+        if(!$process->isSuccessful()) {
455 455
             $cleanupFn();
456 456
             throw new RuntimeException(sprintf('Could not extract the sspak file: %s', $process->getErrorOutput()));
457 457
         }
458 458
 
459 459
         // Extract database.sql.gz to <workingdir>/database.sql
460
-        if (file_exists($workingDir . DIRECTORY_SEPARATOR . 'database.sql.gz')) {
460
+        if(file_exists($workingDir . DIRECTORY_SEPARATOR . 'database.sql.gz')) {
461 461
             $process = new Process('gunzip database.sql.gz', $workingDir);
462 462
             $process->setTimeout(3600);
463 463
             $process->run();
464
-            if (!$process->isSuccessful()) {
464
+            if(!$process->isSuccessful()) {
465 465
                 $cleanupFn();
466 466
                 throw new RuntimeException(sprintf('Could not extract the db archive: %s', $process->getErrorOutput()));
467 467
             }
468 468
         }
469 469
 
470 470
         // Extract assets.tar.gz to <workingdir>/assets/
471
-        if (file_exists($workingDir . DIRECTORY_SEPARATOR . 'assets.tar.gz')) {
471
+        if(file_exists($workingDir . DIRECTORY_SEPARATOR . 'assets.tar.gz')) {
472 472
             $process = new Process('tar xzf assets.tar.gz', $workingDir);
473 473
             $process->setTimeout(3600);
474 474
             $process->run();
475
-            if (!$process->isSuccessful()) {
475
+            if(!$process->isSuccessful()) {
476 476
                 $cleanupFn();
477 477
                 throw new RuntimeException(sprintf('Could not extract the assets archive: %s', $process->getErrorOutput()));
478 478
             }
@@ -497,7 +497,7 @@  discard block
 block discarded – undo
497 497
 
498 498
         $file = $this->ArchiveFile()->FullPath;
499 499
 
500
-        if (!is_readable($file)) {
500
+        if(!is_readable($file)) {
501 501
             $result->error(sprintf('SSPak file "%s" cannot be read.', $file));
502 502
             return $result;
503 503
         }
@@ -505,19 +505,19 @@  discard block
 block discarded – undo
505 505
         $process = new Process(sprintf('tar -tf %s', escapeshellarg($file)));
506 506
         $process->setTimeout(120);
507 507
         $process->run();
508
-        if (!$process->isSuccessful()) {
508
+        if(!$process->isSuccessful()) {
509 509
             throw new RuntimeException(sprintf('Could not list files in archive: %s', $process->getErrorOutput()));
510 510
         }
511 511
 
512 512
         $output = explode(PHP_EOL, $process->getOutput());
513 513
         $files = array_filter($output);
514 514
 
515
-        if (in_array($mode, array('all', 'db')) && !in_array('database.sql.gz', $files)) {
515
+        if(in_array($mode, array('all', 'db')) && !in_array('database.sql.gz', $files)) {
516 516
             $result->error('The snapshot is missing the database.');
517 517
             return $result;
518 518
         }
519 519
 
520
-        if (in_array($mode, array('all', 'assets')) && !in_array('assets.tar.gz', $files)) {
520
+        if(in_array($mode, array('all', 'assets')) && !in_array('assets.tar.gz', $files)) {
521 521
             $result->error('The snapshot is missing assets.');
522 522
             return $result;
523 523
         }
@@ -548,11 +548,11 @@  discard block
 block discarded – undo
548 548
             sprintf('find %s -type f -exec chmod 644 {} +', escapeshellarg($workingDir))
549 549
         );
550 550
 
551
-        foreach ($fixCmds as $cmd) {
551
+        foreach($fixCmds as $cmd) {
552 552
             $process = new Process($cmd);
553 553
             $process->setTimeout(3600);
554 554
             $process->run();
555
-            if (!$process->isSuccessful()) {
555
+            if(!$process->isSuccessful()) {
556 556
                 throw new RuntimeException($process->getErrorOutput());
557 557
             }
558 558
         }
@@ -570,9 +570,9 @@  discard block
 block discarded – undo
570 570
     public function setArchiveFromFiles($workingDir)
571 571
     {
572 572
         $command = sprintf('sspak saveexisting %s 2>&1', $this->ArchiveFile()->FullPath);
573
-        if ($this->Mode == 'db') {
573
+        if($this->Mode == 'db') {
574 574
             $command .= sprintf(' --db=%s/database.sql', $workingDir);
575
-        } elseif ($this->Mode == 'assets') {
575
+        } elseif($this->Mode == 'assets') {
576 576
             $command .= sprintf(' --assets=%s/assets', $workingDir);
577 577
         } else {
578 578
             $command .= sprintf(' --db=%s/database.sql --assets=%s/assets', $workingDir, $workingDir);
@@ -581,7 +581,7 @@  discard block
 block discarded – undo
581 581
         $process = new Process($command, $workingDir);
582 582
         $process->setTimeout(3600);
583 583
         $process->run();
584
-        if (!$process->isSuccessful()) {
584
+        if(!$process->isSuccessful()) {
585 585
             throw new RuntimeException($process->getErrorOutput());
586 586
         }
587 587
 
Please login to merge, or discard this patch.
Braces   +23 added lines, -46 removed lines patch added patch discarded remove patch
@@ -47,8 +47,7 @@  discard block
 block discarded – undo
47 47
  * @method ManyManyList DataTransfers()
48 48
  *
49 49
  */
50
-class DNDataArchive extends DataObject
51
-{
50
+class DNDataArchive extends DataObject {
52 51
 
53 52
     private static $db = array(
54 53
         'UploadToken' => 'Varchar(8)',
@@ -104,8 +103,7 @@  discard block
 block discarded – undo
104 103
 
105 104
     private static $_cache_can_download = array();
106 105
 
107
-    public static function get_mode_map()
108
-    {
106
+    public static function get_mode_map() {
109 107
         return array(
110 108
             'all' => 'Database and Assets',
111 109
             'db' => 'Database only',
@@ -119,14 +117,12 @@  discard block
 block discarded – undo
119 117
      *
120 118
      * @return string
121 119
      */
122
-    public static function generate_upload_token($chars = 8)
123
-    {
120
+    public static function generate_upload_token($chars = 8) {
124 121
         $generator = new RandomGenerator();
125 122
         return strtoupper(substr($generator->randomToken(), 0, $chars));
126 123
     }
127 124
 
128
-    public function onBeforeWrite()
129
-    {
125
+    public function onBeforeWrite() {
130 126
         if (!$this->AuthorID) {
131 127
             $this->AuthorID = Member::currentUserID();
132 128
         }
@@ -134,13 +130,11 @@  discard block
 block discarded – undo
134 130
         parent::onBeforeWrite();
135 131
     }
136 132
 
137
-    public function onAfterDelete()
138
-    {
133
+    public function onAfterDelete() {
139 134
         $this->ArchiveFile()->delete();
140 135
     }
141 136
 
142
-    public function getCMSFields()
143
-    {
137
+    public function getCMSFields() {
144 138
         $fields = parent::getCMSFields();
145 139
         $fields->removeByName('OriginalEnvironmentID');
146 140
         $fields->removeByName('EnvironmentID');
@@ -173,8 +167,7 @@  discard block
 block discarded – undo
173 167
         return $fields;
174 168
     }
175 169
 
176
-    public function getDefaultSearchContext()
177
-    {
170
+    public function getDefaultSearchContext() {
178 171
         $context = parent::getDefaultSearchContext();
179 172
         $context->getFields()->dataFieldByName('Mode')->setHasEmptyDefault(true);
180 173
 
@@ -187,8 +180,7 @@  discard block
 block discarded – undo
187 180
      *
188 181
      * @return string The human-readable size of this archive file
189 182
      */
190
-    public function FileSize()
191
-    {
183
+    public function FileSize() {
192 184
         if ($this->ArchiveFile()->exists()) {
193 185
             return $this->ArchiveFile()->getSize();
194 186
         } else {
@@ -196,8 +188,7 @@  discard block
 block discarded – undo
196 188
         }
197 189
     }
198 190
 
199
-    public function getModeNice()
200
-    {
191
+    public function getModeNice() {
201 192
         if ($this->Mode == 'all') {
202 193
             return 'database and assets';
203 194
         } else {
@@ -212,8 +203,7 @@  discard block
 block discarded – undo
212 203
      *
213 204
      * @return boolean
214 205
      */
215
-    public function isPending()
216
-    {
206
+    public function isPending() {
217 207
         return !($this->ArchiveFileID);
218 208
     }
219 209
 
@@ -222,8 +212,7 @@  discard block
 block discarded – undo
222 212
      *
223 213
      * @param Member|null $member The {@link Member} object to test against.
224 214
      */
225
-    public function canView($member = null)
226
-    {
215
+    public function canView($member = null) {
227 216
         return ($this->canRestore($member) || $this->canDownload($member));
228 217
     }
229 218
 
@@ -234,8 +223,7 @@  discard block
 block discarded – undo
234 223
      * @param Member|null $member The {@link Member} object to test against.
235 224
      * @return true if $member (or the currently logged in member if null) can upload this archive
236 225
      */
237
-    public function canRestore($member = null)
238
-    {
226
+    public function canRestore($member = null) {
239 227
         $memberID = $member ? $member->ID : Member::currentUserID();
240 228
         if (!$memberID) {
241 229
             return false;
@@ -256,8 +244,7 @@  discard block
 block discarded – undo
256 244
      * @param Member|null $member The {@link Member} object to test against.
257 245
      * @return true if $member (or the currently logged in member if null) can download this archive
258 246
      */
259
-    public function canDownload($member = null)
260
-    {
247
+    public function canDownload($member = null) {
261 248
         $memberID = $member ? $member->ID : Member::currentUserID();
262 249
         if (!$memberID) {
263 250
             return false;
@@ -276,8 +263,7 @@  discard block
 block discarded – undo
276 263
      * @param Member|null $member The {@link Member} object to test against.
277 264
      * @return boolean if $member (or the currently logged in member if null) can delete this archive
278 265
      */
279
-    public function canDelete($member = null)
280
-    {
266
+    public function canDelete($member = null) {
281 267
         return $this->Environment()->canDeleteArchive($member);
282 268
     }
283 269
 
@@ -289,8 +275,7 @@  discard block
 block discarded – undo
289 275
      *
290 276
      * @return boolean true if $member can upload archives linked to this environment, false if they can't.
291 277
      */
292
-    public function canMoveTo($targetEnv, $member = null)
293
-    {
278
+    public function canMoveTo($targetEnv, $member = null) {
294 279
         if ($this->Environment()->Project()->ID != $targetEnv->Project()->ID) {
295 280
             // We don't permit moving snapshots between projects at this stage.
296 281
             return false;
@@ -327,8 +312,7 @@  discard block
 block discarded – undo
327 312
      *
328 313
      * @return ArrayList List of valid environments.
329 314
      */
330
-    public function validTargetEnvironments()
331
-    {
315
+    public function validTargetEnvironments() {
332 316
         $archive = $this;
333 317
         $envs = $this->Environment()->Project()->DNEnvironmentList()
334 318
             ->filterByCallback(function ($item) use ($archive) {
@@ -342,8 +326,7 @@  discard block
 block discarded – undo
342 326
      * Returns a unique filename, including project/environment/timestamp details.
343 327
      * @return string
344 328
      */
345
-    public function generateFilename(DNDataTransfer $dataTransfer)
346
-    {
329
+    public function generateFilename(DNDataTransfer $dataTransfer) {
347 330
         $generator = new RandomGenerator();
348 331
         $filter = FileNameFilter::create();
349 332
 
@@ -364,8 +347,7 @@  discard block
 block discarded – undo
364 347
      * @param DNDataTransfer
365 348
      * @return string Absolute file path
366 349
      */
367
-    public function generateFilepath(DNDataTransfer $dataTransfer)
368
-    {
350
+    public function generateFilepath(DNDataTransfer $dataTransfer) {
369 351
         $data = DNData::inst();
370 352
         $transferDir = $data->getDataTransferDir();
371 353
         $filter = FileNameFilter::create();
@@ -386,8 +368,7 @@  discard block
 block discarded – undo
386 368
      * @param DNDataTransfer $dataTransfer
387 369
      * @return bool
388 370
      */
389
-    public function attachFile($sspakFilepath, DNDataTransfer $dataTransfer)
390
-    {
371
+    public function attachFile($sspakFilepath, DNDataTransfer $dataTransfer) {
391 372
         $sspakFilepath = ltrim(
392 373
             str_replace(
393 374
                 array(ASSETS_PATH, realpath(ASSETS_PATH)),
@@ -431,8 +412,7 @@  discard block
 block discarded – undo
431 412
      * @throws RuntimeException
432 413
      * @return bool
433 414
      */
434
-    public function extractArchive($workingDir = null)
435
-    {
415
+    public function extractArchive($workingDir = null) {
436 416
         if (!is_dir($workingDir)) {
437 417
             mkdir($workingDir, 0700, true);
438 418
         }
@@ -490,8 +470,7 @@  discard block
 block discarded – undo
490 470
      * @param string|null $mode "db", "assets", or "all". This is the content we're checking for. Default to the archive setting
491 471
      * @return ValidationResult
492 472
      */
493
-    public function validateArchiveContents($mode = null)
494
-    {
473
+    public function validateArchiveContents($mode = null) {
495 474
         $mode = $mode ?: $this->Mode;
496 475
         $result = new ValidationResult();
497 476
 
@@ -539,8 +518,7 @@  discard block
 block discarded – undo
539 518
      * @throws RuntimeException
540 519
      * @return bool
541 520
      */
542
-    public function fixArchivePermissions($workingDir)
543
-    {
521
+    public function fixArchivePermissions($workingDir) {
544 522
         $fixCmds = array(
545 523
             // The directories need to have permissions changed one by one (hence the ; instead of +),
546 524
             // otherwise we might end up having no +x access to a directory deeper down.
@@ -567,8 +545,7 @@  discard block
 block discarded – undo
567 545
      * @param string|null $workingDir The path of where the sspak has been extracted to
568 546
      * @return bool
569 547
      */
570
-    public function setArchiveFromFiles($workingDir)
571
-    {
548
+    public function setArchiveFromFiles($workingDir) {
572 549
         $command = sprintf('sspak saveexisting %s 2>&1', $this->ArchiveFile()->FullPath);
573 550
         if ($this->Mode == 'db') {
574 551
             $command .= sprintf(' --db=%s/database.sql', $workingDir);
Please login to merge, or discard this patch.
code/model/DNEnvironment.php 3 patches
Indentation   +1366 added lines, -1366 removed lines patch added patch discarded remove patch
@@ -42,1376 +42,1376 @@
 block discarded – undo
42 42
 class DNEnvironment extends DataObject
43 43
 {
44 44
 
45
-    /**
46
-     * If this is set to a full pathfile, it will be used as template
47
-     * file when creating a new capistrano environment config file.
48
-     *
49
-     * If not set, the default 'environment.template' from the module
50
-     * root is used
51
-     *
52
-     * @config
53
-     * @var string
54
-     */
55
-    private static $template_file = '';
56
-
57
-    /**
58
-     * Set this to true to allow editing of the environment files via the web admin
59
-     *
60
-     * @var bool
61
-     */
62
-    private static $allow_web_editing = false;
63
-
64
-    /**
65
-     * @var array
66
-     */
67
-    private static $casting = array(
68
-        'DeployHistory' => 'Text'
69
-    );
70
-
71
-    /**
72
-     * Allowed backends. A map of Injector identifier to human-readable label.
73
-     *
74
-     * @config
75
-     * @var array
76
-     */
77
-    private static $allowed_backends = array();
78
-
79
-    /**
80
-     * @var array
81
-     */
82
-    public static $db = array(
83
-        "Filename" => "Varchar(255)",
84
-        "Name" => "Varchar(255)",
85
-        "URL" => "Varchar(255)",
86
-        "BackendIdentifier" => "Varchar(255)", // Injector identifier of the DeploymentBackend
87
-        "DryRunEnabled" => "Boolean", // True if the dry run button should be enabled on the frontend
88
-        "Usage" => "Enum('Production, UAT, Test, Unspecified', 'Unspecified')"
89
-    );
90
-
91
-    /**
92
-     * @var array
93
-     */
94
-    public static $has_one = array(
95
-        "Project" => "DNProject",
96
-        "CreateEnvironment" => "DNCreateEnvironment"
97
-    );
98
-
99
-    /**
100
-     * @var array
101
-     */
102
-    public static $has_many = array(
103
-        "Deployments" => "DNDeployment",
104
-        "DataArchives" => "DNDataArchive",
105
-        "Pipelines" => "Pipeline" // Only one Pipeline can be 'Running' at any one time. @see self::CurrentPipeline().
106
-    );
107
-
108
-    /**
109
-     * @var array
110
-     */
111
-    public static $many_many = array(
112
-        "Viewers"            => "Member", // Who can view this environment
113
-        "ViewerGroups"       => "Group",
114
-        "Deployers"          => "Member", // Who can deploy to this environment
115
-        "DeployerGroups" => "Group",
116
-        "CanRestoreMembers"  => "Member", // Who can restore archive files to this environment
117
-        "CanRestoreGroups"  => "Group",
118
-        "CanBackupMembers"   => "Member", // Who can backup archive files from this environment
119
-        "CanBackupGroups"   => "Group",
120
-        "ArchiveUploaders"   => "Member", // Who can upload archive files linked to this environment
121
-        "ArchiveUploaderGroups" => "Group",
122
-        "ArchiveDownloaders" => "Member", // Who can download archive files from this environment
123
-        "ArchiveDownloaderGroups" => "Group",
124
-        "ArchiveDeleters"    => "Member", // Who can delete archive files from this environment,
125
-        "ArchiveDeleterGroups" => "Group",
126
-        "PipelineApprovers"  => "Member", // Who can approve / reject pipelines from this environment
127
-        "PipelineApproverGroups" => "Group",
128
-        "PipelineCancellers"   => "Member", // Who can abort pipelines
129
-        "PipelineCancellerGroups" => "Group"
130
-    );
131
-
132
-    /**
133
-     * @var array
134
-     */
135
-    public static $summary_fields = array(
136
-        "Name" => "Environment Name",
137
-        "Usage" => "Usage",
138
-        "URL" => "URL",
139
-        "DeployersList" => "Can Deploy List",
140
-        "CanRestoreMembersList" => "Can Restore List",
141
-        "CanBackupMembersList" => "Can Backup List",
142
-        "ArchiveUploadersList" => "Can Upload List",
143
-        "ArchiveDownloadersList" => "Can Download List",
144
-        "ArchiveDeletersList"  => "Can Delete List",
145
-        "PipelineApproversList" => "Can Approve List",
146
-        "PipelineCancellersList" => "Can Cancel List"
147
-    );
148
-
149
-    private static $singular_name = 'Capistrano Environment';
150
-
151
-    private static $plural_name = 'Capistrano Environments';
152
-
153
-    /**
154
-     * @var array
155
-     */
156
-    public static $searchable_fields = array(
157
-        "Name",
158
-    );
159
-
160
-    /**
161
-     * @var string
162
-     */
163
-    private static $default_sort = 'Name';
164
-
165
-    /**
166
-     * Used by the sync task
167
-     *
168
-     * @param string $path
169
-     * @return \DNEnvironment
170
-     */
171
-    public static function create_from_path($path)
172
-    {
173
-        $e = DNEnvironment::create();
174
-        $e->Filename = $path;
175
-        $e->Name = basename($e->Filename, '.rb');
176
-
177
-        // add each administrator member as a deployer of the new environment
178
-        $adminGroup = Group::get()->filter('Code', 'administrators')->first();
179
-        $e->DeployerGroups()->add($adminGroup);
180
-        return $e;
181
-    }
182
-
183
-    /**
184
-     * Get the deployment backend used for this environment.
185
-     *
186
-     * Enforces compliance with the allowed_backends setting; if the DNEnvironment.BackendIdentifier value is
187
-     * illegal then that value is ignored.
188
-     *
189
-     * @return DeploymentBackend
190
-     */
191
-    public function Backend()
192
-    {
193
-        $backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET));
194
-        switch (sizeof($backends)) {
195
-        // Nothing allowed, use the default value "DeploymentBackend"
196
-            case 0:
197
-                $backend = "DeploymentBackend";
198
-                break;
199
-
200
-            // Only 1 thing allowed, use that
201
-            case 1:
202
-                $backend = $backends[0];
203
-                break;
204
-
205
-            // Multiple choices, use our choice if it's legal, otherwise default to the first item on the list
206
-            default:
207
-                $backend = $this->BackendIdentifier;
208
-                if (!in_array($backend, $backends)) {
209
-                    $backend = $backends[0];
210
-                }
211
-        }
212
-
213
-        return Injector::inst()->get($backend);
214
-    }
215
-
216
-    public function Menu()
217
-    {
218
-        $list = new ArrayList();
219
-
220
-        $controller = Controller::curr();
221
-        $actionType = $controller->getField('CurrentActionType');
222
-
223
-        $list->push(new ArrayData(array(
224
-            'Link' => sprintf('naut/project/%s/environment/%s', $this->Project()->Name, $this->Name),
225
-            'Title' => 'Deployments',
226
-            'IsCurrent' => $this->isCurrent(),
227
-            'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_DEPLOY
228
-        )));
229
-
230
-        $this->extend('updateMenu', $list);
231
-
232
-        return $list;
233
-    }
234
-
235
-    /**
236
-     * Return the current object from $this->Menu()
237
-     * Good for making titles and things
238
-     */
239
-    public function CurrentMenu()
240
-    {
241
-        return $this->Menu()->filter('IsSection', true)->First();
242
-    }
243
-
244
-    /**
245
-     * Return a name for this environment.
246
-     *
247
-     * @param string $separator The string used when concatenating project with env name
248
-     * @return string
249
-     */
250
-    public function getFullName($separator = ':')
251
-    {
252
-        return sprintf('%s%s%s', $this->Project()->Name, $separator, $this->Name);
253
-    }
254
-
255
-    public function getBareURL()
256
-    {
257
-        $url = parse_url($this->URL);
258
-        if (isset($url['host'])) {
259
-            return strtolower($url['host']);
260
-        }
261
-    }
262
-
263
-    /**
264
-     * @return boolean true if there is a pipeline for the current environment.
265
-     */
266
-    public function HasPipelineSupport()
267
-    {
268
-        $config = $this->GenericPipelineConfig();
269
-        return $config instanceof ArrayData && isset($config->Steps);
270
-    }
271
-
272
-    /**
273
-     * Returns a {@link Pipeline} object that is linked to this environment, but isn't saved into the database. This
274
-     * shouldn't be saved into the database unless you plan on starting an actual pipeline.
275
-     *
276
-     * @return Pipeline
277
-     */
278
-    public function GenericPipeline()
279
-    {
280
-        $pipeline = Pipeline::create();
281
-        $pipeline->EnvironmentID = $this->ID;
282
-        return $pipeline;
283
-    }
284
-
285
-    /**
286
-     * Returns the parsed config, based on a {@link Pipeline} being created for this {@link DNEnvironment}.
287
-     *
288
-     * @return ArrayData
289
-     */
290
-    public function GenericPipelineConfig()
291
-    {
292
-        $config = $this->loadPipelineConfig();
293
-        if ($config) {
294
-            return self::array_to_viewabledata($config);
295
-        }
296
-    }
297
-
298
-    /**
299
-     * Extract pipeline configuration data from the source yml file
300
-     *
301
-     * @return array
302
-     */
303
-    public function loadPipelineConfig()
304
-    {
305
-        require_once 'thirdparty/spyc/spyc.php';
306
-
307
-        $path = $this->getPipelineFilename();
308
-        if (file_exists($path)) {
309
-            return Spyc::YAMLLoad($path);
310
-        }
311
-    }
312
-
313
-    /**
314
-     * Returns the {@link DNEnvironment} object relating to the pipeline config for this environment. The environment
315
-     * YAML file (e.g. project1-uat.yml; see docs/en/pipelines.md) contains two variable called `DependsOnProject` and
316
-     * `DependsOnEnvironment` - these are used together to find the {@link DNEnvironment} that this environment should
317
-     * rely on.
318
-     */
319
-    public function DependsOnEnvironment()
320
-    {
321
-        if ($this->HasPipelineSupport()) {
322
-            $pipeline = $this->GenericPipeline();
323
-            return $pipeline->getDependentEnvironment();
324
-        }
325
-
326
-        return null;
327
-    }
328
-
329
-    /**
330
-     * @return bool true if there is a currently running Pipeline, and false if there isn't
331
-     */
332
-    public function HasCurrentPipeline()
333
-    {
334
-        return $this->CurrentPipeline() && $this->CurrentPipeline()->isInDB();
335
-    }
336
-
337
-    /**
338
-     * This can be used to determine if there is a currently running pipeline (there can only be one running per
339
-     * {@link DNEnvironment} at once), as well as getting the current pipeline to be shown in templates.
340
-     *
341
-     * @return DataObject|null The currently running pipeline, or null if there isn't any.
342
-     */
343
-    public function CurrentPipeline()
344
-    {
345
-        return $this->Pipelines()->filter('Status', array('Running', 'Rollback'))->first();
346
-    }
347
-
348
-    /**
349
-     * @return bool true if the current user can cancel a running pipeline
350
-     */
351
-    public function CanCancelPipeline()
352
-    {
353
-        // do we have a current pipeline
354
-        if ($this->HasCurrentPipeline()) {
355
-            return $this->CurrentPipeline()->canAbort();
356
-        }
357
-        return false;
358
-    }
359
-
360
-    /**
361
-     * Environments are only viewable by people that can view the environment.
362
-     *
363
-     * @param Member|null $member
364
-     * @return boolean
365
-     */
366
-    public function canView($member = null)
367
-    {
368
-        if (!$member) {
369
-            $member = Member::currentUser();
370
-        }
371
-        if (!$member) {
372
-            return false;
373
-        }
374
-        // Must be logged in to check permissions
375
-
376
-        if (Permission::checkMember($member, 'ADMIN')) {
377
-            return true;
378
-        }
379
-
380
-        // if no Viewers or ViewerGroups defined, fallback to DNProject::canView permissions
381
-        if ($this->Viewers()->exists() || $this->ViewerGroups()->exists()) {
382
-            return $this->Viewers()->byID($member->ID)
383
-                || $member->inGroups($this->ViewerGroups());
384
-        }
385
-
386
-        return $this->Project()->canView($member);
387
-    }
388
-
389
-    /**
390
-     * Allow deploy only to some people.
391
-     *
392
-     * @param Member|null $member
393
-     * @return boolean
394
-     */
395
-    public function canDeploy($member = null)
396
-    {
397
-        if (!$member) {
398
-            $member = Member::currentUser();
399
-        }
400
-        if (!$member) {
401
-            return false;
402
-        }
403
-        // Must be logged in to check permissions
404
-
405
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
406
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_DEPLOYMENT, $member)) {
407
-                return true;
408
-            }
409
-        } else {
410
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_DEPLOYMENT, $member)) {
411
-                return true;
412
-            }
413
-        }
414
-
415
-        return $this->Deployers()->byID($member->ID)
416
-            || $member->inGroups($this->DeployerGroups());
417
-    }
418
-
419
-    /**
420
-     * Allows only selected {@link Member} objects to restore {@link DNDataArchive} objects into this
421
-     * {@link DNEnvironment}.
422
-     *
423
-     * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
424
-     * @return boolean true if $member can restore, and false if they can't.
425
-     */
426
-    public function canRestore($member = null)
427
-    {
428
-        if (!$member) {
429
-            $member = Member::currentUser();
430
-        }
431
-        if (!$member) {
432
-            return false;
433
-        }
434
-        // Must be logged in to check permissions
435
-
436
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
437
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
438
-                return true;
439
-            }
440
-        } else {
441
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
442
-                return true;
443
-            }
444
-        }
445
-
446
-        return $this->CanRestoreMembers()->byID($member->ID)
447
-            || $member->inGroups($this->CanRestoreGroups());
448
-    }
449
-
450
-    /**
451
-     * Allows only selected {@link Member} objects to backup this {@link DNEnvironment} to a {@link DNDataArchive}
452
-     * file.
453
-     *
454
-     * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
455
-     * @return boolean true if $member can backup, and false if they can't.
456
-     */
457
-    public function canBackup($member = null)
458
-    {
459
-        $project = $this->Project();
460
-        if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
461
-            return false;
462
-        }
463
-
464
-        if (!$member) {
465
-            $member = Member::currentUser();
466
-        }
467
-        // Must be logged in to check permissions
468
-        if (!$member) {
469
-            return false;
470
-        }
471
-
472
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
473
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
474
-                return true;
475
-            }
476
-        } else {
477
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
478
-                return true;
479
-            }
480
-        }
481
-
482
-        return $this->CanBackupMembers()->byID($member->ID)
483
-            || $member->inGroups($this->CanBackupGroups());
484
-    }
485
-
486
-    /**
487
-     * Allows only selected {@link Member} objects to upload {@link DNDataArchive} objects linked to this
488
-     * {@link DNEnvironment}.
489
-     *
490
-     * Note: This is not uploading them to the actual environment itself (e.g. uploading to the live site) - it is the
491
-     * process of uploading a *.sspak file into Deploynaut for later 'restoring' to an environment. See
492
-     * {@link self::canRestore()}.
493
-     *
494
-     * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
495
-     * @return boolean true if $member can upload archives linked to this environment, false if they can't.
496
-     */
497
-    public function canUploadArchive($member = null)
498
-    {
499
-        $project = $this->Project();
500
-        if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
501
-            return false;
502
-        }
503
-
504
-        if (!$member) {
505
-            $member = Member::currentUser();
506
-        }
507
-        if (!$member) {
508
-            return false;
509
-        }
510
-        // Must be logged in to check permissions
511
-
512
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
513
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
514
-                return true;
515
-            }
516
-        } else {
517
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
518
-                return true;
519
-            }
520
-        }
521
-
522
-        return $this->ArchiveUploaders()->byID($member->ID)
523
-            || $member->inGroups($this->ArchiveUploaderGroups());
524
-    }
525
-
526
-    /**
527
-     * Allows only selected {@link Member} objects to download {@link DNDataArchive} objects from this
528
-     * {@link DNEnvironment}.
529
-     *
530
-     * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
531
-     * @return boolean true if $member can download archives from this environment, false if they can't.
532
-     */
533
-    public function canDownloadArchive($member = null)
534
-    {
535
-        if (!$member) {
536
-            $member = Member::currentUser();
537
-        }
538
-        if (!$member) {
539
-            return false;
540
-        }
541
-        // Must be logged in to check permissions
542
-
543
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
544
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
545
-                return true;
546
-            }
547
-        } else {
548
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
549
-                return true;
550
-            }
551
-        }
552
-
553
-        return $this->ArchiveDownloaders()->byID($member->ID)
554
-            || $member->inGroups($this->ArchiveDownloaderGroups());
555
-    }
556
-
557
-    /**
558
-     * Determine if the specified user can abort any pipelines
559
-     *
560
-     * @param Member|null $member
561
-     * @return boolean
562
-     */
563
-    public function canAbort($member = null)
564
-    {
565
-        if (!$member) {
566
-            $member = Member::currentUser();
567
-        }
568
-        if (!$member) {
569
-            return false;
570
-        }
571
-
572
-        if (Permission::checkMember($member, 'ADMIN')) {
573
-            return true;
574
-        }
575
-
576
-        return $this->PipelineCancellers()->byID($member->ID)
577
-            || $member->inGroups($this->PipelineCancellerGroups());
578
-    }
579
-
580
-    /**
581
-     * Determine if the specified user can approve any pipelines
582
-     *
583
-     * @param Member|null $member
584
-     * @return boolean
585
-     */
586
-    public function canApprove($member = null)
587
-    {
588
-        if (!$member) {
589
-            $member = Member::currentUser();
590
-        }
591
-        if (!$member) {
592
-            return false;
593
-        }
594
-
595
-        if (Permission::checkMember($member, 'ADMIN')) {
596
-            return true;
597
-        }
598
-        return $this->PipelineApprovers()->byID($member->ID)
599
-            || $member->inGroups($this->PipelineApproverGroups());
600
-    }
601
-
602
-    /**
603
-     * Allows only selected {@link Member} objects to delete {@link DNDataArchive} objects from this
604
-     * {@link DNEnvironment}.
605
-     *
606
-     * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
607
-     * @return boolean true if $member can delete archives from this environment, false if they can't.
608
-     */
609
-    public function canDeleteArchive($member = null)
610
-    {
611
-        if (!$member) {
612
-            $member = Member::currentUser();
613
-        }
614
-        if (!$member) {
615
-            return false;
616
-        }
617
-        // Must be logged in to check permissions
618
-
619
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
620
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
621
-                return true;
622
-            }
623
-        } else {
624
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
625
-                return true;
626
-            }
627
-        }
628
-
629
-        return $this->ArchiveDeleters()->byID($member->ID)
630
-            || $member->inGroups($this->ArchiveDeleterGroups());
631
-    }
632
-    /**
633
-     * Get a string of groups/people that are allowed to deploy to this environment.
634
-     * Used in DNRoot_project.ss to list {@link Member}s who have permission to perform this action.
635
-     *
636
-     * @return string
637
-     */
638
-    public function getDeployersList()
639
-    {
640
-        return implode(
641
-            ", ",
642
-            array_merge(
643
-                $this->DeployerGroups()->column("Title"),
644
-                $this->Deployers()->column("FirstName")
645
-            )
646
-        );
647
-    }
648
-
649
-    /**
650
-     * Get a string of groups/people that are allowed to restore {@link DNDataArchive} objects into this environment.
651
-     *
652
-     * @return string
653
-     */
654
-    public function getCanRestoreMembersList()
655
-    {
656
-        return implode(
657
-            ", ",
658
-            array_merge(
659
-                $this->CanRestoreGroups()->column("Title"),
660
-                $this->CanRestoreMembers()->column("FirstName")
661
-            )
662
-        );
663
-    }
664
-
665
-    /**
666
-     * Get a string of groups/people that are allowed to backup {@link DNDataArchive} objects from this environment.
667
-     *
668
-     * @return string
669
-     */
670
-    public function getCanBackupMembersList()
671
-    {
672
-        return implode(
673
-            ", ",
674
-            array_merge(
675
-                $this->CanBackupGroups()->column("Title"),
676
-                $this->CanBackupMembers()->column("FirstName")
677
-            )
678
-        );
679
-    }
680
-
681
-    /**
682
-     * Get a string of groups/people that are allowed to upload {@link DNDataArchive}
683
-     *  objects linked to this environment.
684
-     *
685
-     * @return string
686
-     */
687
-    public function getArchiveUploadersList()
688
-    {
689
-        return implode(
690
-            ", ",
691
-            array_merge(
692
-                $this->ArchiveUploaderGroups()->column("Title"),
693
-                $this->ArchiveUploaders()->column("FirstName")
694
-            )
695
-        );
696
-    }
697
-
698
-    /**
699
-     * Get a string of groups/people that are allowed to download {@link DNDataArchive} objects from this environment.
700
-     *
701
-     * @return string
702
-     */
703
-    public function getArchiveDownloadersList()
704
-    {
705
-        return implode(
706
-            ", ",
707
-            array_merge(
708
-                $this->ArchiveDownloaderGroups()->column("Title"),
709
-                $this->ArchiveDownloaders()->column("FirstName")
710
-            )
711
-        );
712
-    }
713
-
714
-    /**
715
-     * Get a string of groups/people that are allowed to delete {@link DNDataArchive} objects from this environment.
716
-     *
717
-     * @return string
718
-     */
719
-    public function getArchiveDeletersList()
720
-    {
721
-        return implode(
722
-            ", ",
723
-            array_merge(
724
-                $this->ArchiveDeleterGroups()->column("Title"),
725
-                $this->ArchiveDeleters()->column("FirstName")
726
-            )
727
-        );
728
-    }
729
-
730
-    /**
731
-     * Get a string of groups/people that are allowed to approve pipelines
732
-     *
733
-     * @return string
734
-     */
735
-    public function getPipelineApproversList()
736
-    {
737
-        return implode(
738
-            ", ",
739
-            array_merge(
740
-                $this->PipelineApproverGroups()->column("Title"),
741
-                $this->PipelineApprovers()->column("FirstName")
742
-            )
743
-        );
744
-    }
745
-
746
-    /**
747
-     * Get a string of groups/people that are allowed to cancel pipelines
748
-     *
749
-     * @return string
750
-     */
751
-    public function getPipelineCancellersList()
752
-    {
753
-        return implode(
754
-            ", ",
755
-            array_merge(
756
-                $this->PipelineCancellerGroups()->column("Title"),
757
-                $this->PipelineCancellers()->column("FirstName")
758
-            )
759
-        );
760
-    }
761
-
762
-    /**
763
-     * @return DNData
764
-     */
765
-    public function DNData()
766
-    {
767
-        return DNData::inst();
768
-    }
769
-
770
-    /**
771
-     * Get the current deployed build for this environment
772
-     *
773
-     * Dear people of the future: If you are looking to optimize this, simply create a CurrentBuildSHA(), which can be
774
-     * a lot faster. I presume you came here because of the Project display template, which only needs a SHA.
775
-     *
776
-     * @return false|DNDeployment
777
-     */
778
-    public function CurrentBuild()
779
-    {
780
-        // The DeployHistory function is far too slow to use for this
781
-
782
-        /** @var DNDeployment $deploy */
783
-        $deploy = DNDeployment::get()->filter(array(
784
-            'EnvironmentID' => $this->ID,
785
-            'Status' => 'Finished'
786
-        ))->sort('LastEdited DESC')->first();
787
-
788
-        if (!$deploy || (!$deploy->SHA)) {
789
-            return false;
790
-        }
791
-
792
-        $repo = $this->Project()->getRepository();
793
-        if (!$repo) {
794
-            return $deploy;
795
-        }
796
-
797
-        try {
798
-            $commit = $repo->getCommit($deploy->SHA);
799
-            if ($commit) {
800
-                $deploy->Message = Convert::raw2xml($commit->getMessage());
801
-                $deploy->Committer = Convert::raw2xml($commit->getCommitterName());
802
-                $deploy->CommitDate = $commit->getCommitterDate()->Format('d/m/Y g:ia');
803
-                $deploy->Author = Convert::raw2xml($commit->getAuthorName());
804
-                $deploy->AuthorDate = $commit->getAuthorDate()->Format('d/m/Y g:ia');
805
-            }
806
-            // We can't find this SHA, so we ignore adding a commit message to the deployment
807
-        } catch (Exception $ex) {
808
-        }
809
-
810
-        return $deploy;
811
-    }
812
-
813
-    /**
814
-     * A history of all builds deployed to this environment
815
-     *
816
-     * @return ArrayList
817
-     */
818
-    public function DeployHistory()
819
-    {
820
-        return $this->Deployments()
821
-            ->where('SHA IS NOT NULL')
822
-            ->sort('LastEdited DESC');
823
-    }
824
-
825
-    /**
826
-     * @param string $sha
827
-     * @return array
828
-     */
829
-    protected function getCommitData($sha)
830
-    {
831
-        try {
832
-            $repo = $this->Project()->getRepository();
833
-            if ($repo !== false) {
834
-                $commit = new \Gitonomy\Git\Commit($repo, $sha);
835
-                return [
836
-                    'AuthorName' => (string)Convert::raw2xml($commit->getAuthorName()),
837
-                    'AuthorEmail' => (string)Convert::raw2xml($commit->getAuthorEmail()),
838
-                    'Message' => (string)Convert::raw2xml($commit->getMessage()),
839
-                    'ShortHash' => Convert::raw2xml($commit->getFixedShortHash(8)),
840
-                    'Hash' => Convert::raw2xml($commit->getHash())
841
-                ];
842
-            }
843
-        } catch (\Gitonomy\Git\Exception\ReferenceNotFoundException $exc) {
844
-            SS_Log::log($exc, SS_Log::WARN);
845
-        }
846
-        return array(
847
-            'AuthorName' => '(unknown)',
848
-            'AuthorEmail' => '(unknown)',
849
-            'Message' => '(unknown)',
850
-            'ShortHash' => $sha,
851
-            'Hash' => '(unknown)',
852
-        );
853
-    }
854
-
855
-    /**
856
-     * @return string
857
-     */
858
-    public function Link()
859
-    {
860
-        return $this->Project()->Link() . "/environment/" . $this->Name;
861
-    }
862
-
863
-    /**
864
-     * Is this environment currently at the root level of the controller that handles it?
865
-     * @return bool
866
-     */
867
-    public function isCurrent()
868
-    {
869
-        return $this->isSection() && Controller::curr()->getAction() == 'environment';
870
-    }
871
-
872
-    /**
873
-     * Is this environment currently in a controller that is handling it or performing a sub-task?
874
-     * @return bool
875
-     */
876
-    public function isSection()
877
-    {
878
-        $controller = Controller::curr();
879
-        $environment = $controller->getField('CurrentEnvironment');
880
-        return $environment && $environment->ID == $this->ID;
881
-    }
882
-
883
-
884
-    /**
885
-     * Build a set of multi-select fields for assigning permissions to a pair of group and member many_many relations
886
-     *
887
-     * @param string $groupField Group field name
888
-     * @param string $memberField Member field name
889
-     * @param array $groups List of groups
890
-     * @param array $members List of members
891
-     * @return FieldGroup
892
-     */
893
-    protected function buildPermissionField($groupField, $memberField, $groups, $members)
894
-    {
895
-        return FieldGroup::create(
896
-            ListboxField::create($groupField, false, $groups)
897
-                ->setMultiple(true)
898
-                ->setAttribute('data-placeholder', 'Groups')
899
-                ->setAttribute('placeholder', 'Groups')
900
-                ->setAttribute('style', 'width: 400px;'),
901
-
902
-            ListboxField::create($memberField, false, $members)
903
-                ->setMultiple(true)
904
-                ->setAttribute('data-placeholder', 'Members')
905
-                ->setAttribute('placeholder', 'Members')
906
-                ->setAttribute('style', 'width: 400px;')
907
-        );
908
-    }
909
-
910
-    /**
911
-     * @return FieldList
912
-     */
913
-    public function getCMSFields()
914
-    {
915
-        $fields = new FieldList(new TabSet('Root'));
916
-
917
-        $project = $this->Project();
918
-        if ($project && $project->exists()) {
919
-            $viewerGroups = $project->Viewers();
920
-            $groups = $viewerGroups->sort('Title')->map()->toArray();
921
-            $members = array();
922
-            foreach ($viewerGroups as $group) {
923
-                foreach ($group->Members()->map() as $k => $v) {
924
-                    $members[$k] = $v;
925
-                }
926
-            }
927
-            asort($members);
928
-        } else {
929
-            $groups = array();
930
-            $members = array();
931
-        }
932
-
933
-        // Main tab
934
-        $fields->addFieldsToTab('Root.Main', array(
935
-            // The Main.ProjectID
936
-            TextField::create('ProjectName', 'Project')
937
-                ->setValue(($project = $this->Project()) ? $project->Name : null)
938
-                ->performReadonlyTransformation(),
939
-
940
-            // The Main.Name
941
-            TextField::create('Name', 'Environment name')
942
-                ->setDescription('A descriptive name for this environment, e.g. staging, uat, production'),
943
-
944
-
945
-            $this->obj('Usage')->scaffoldFormField('Environment usage'),
946
-
947
-            // The Main.URL field
948
-            TextField::create('URL', 'Server URL')
949
-                ->setDescription('This url will be used to provide the front-end with a link to this environment'),
950
-
951
-            // The Main.Filename
952
-            TextField::create('Filename')
953
-                ->setDescription('The capistrano environment file name')
954
-                ->performReadonlyTransformation(),
955
-        ));
956
-
957
-        // Backend identifier - pick from a named list of configurations specified in YML config
958
-        $backends = $this->config()->get('allowed_backends', Config::FIRST_SET);
959
-        // If there's only 1 backend, then user selection isn't needed
960
-        if (sizeof($backends) > 1) {
961
-            $fields->addFieldToTab('Root.Main', DropdownField::create('BackendIdentifier', 'Deployment backend')
962
-                ->setSource($backends)
963
-                ->setDescription('What kind of deployment system should be used to deploy to this environment'));
964
-        }
965
-
966
-        $fields->addFieldsToTab('Root.UserPermissions', array(
967
-            // The viewers of the environment
968
-            $this
969
-                ->buildPermissionField('ViewerGroups', 'Viewers', $groups, $members)
970
-                ->setTitle('Who can view this environment?')
971
-                ->setDescription('Groups or Users who can view this environment'),
972
-
973
-            // The Main.Deployers
974
-            $this
975
-                ->buildPermissionField('DeployerGroups', 'Deployers', $groups, $members)
976
-                ->setTitle('Who can deploy?')
977
-                ->setDescription('Groups or Users who can deploy to this environment'),
978
-
979
-            // A box to select all snapshot options.
980
-            $this
981
-                ->buildPermissionField('TickAllSnapshotGroups', 'TickAllSnapshot', $groups, $members)
982
-                ->setTitle("<em>All snapshot permissions</em>")
983
-                ->addExtraClass('tickall')
984
-                ->setDescription('UI shortcut to select all snapshot-related options - not written to the database.'),
985
-
986
-            // The Main.CanRestoreMembers
987
-            $this
988
-                ->buildPermissionField('CanRestoreGroups', 'CanRestoreMembers', $groups, $members)
989
-                ->setTitle('Who can restore?')
990
-                ->setDescription('Groups or Users who can restore archives from Deploynaut into this environment'),
991
-
992
-            // The Main.CanBackupMembers
993
-            $this
994
-                ->buildPermissionField('CanBackupGroups', 'CanBackupMembers', $groups, $members)
995
-                ->setTitle('Who can backup?')
996
-                ->setDescription('Groups or Users who can backup archives from this environment into Deploynaut'),
997
-
998
-            // The Main.ArchiveDeleters
999
-            $this
1000
-                ->buildPermissionField('ArchiveDeleterGroups', 'ArchiveDeleters', $groups, $members)
1001
-                ->setTitle('Who can delete?')
1002
-                ->setDescription("Groups or Users who can delete archives from this environment's staging area."),
1003
-
1004
-            // The Main.ArchiveUploaders
1005
-            $this
1006
-                ->buildPermissionField('ArchiveUploaderGroups', 'ArchiveUploaders', $groups, $members)
1007
-                ->setTitle('Who can upload?')
1008
-                ->setDescription(
1009
-                    'Users who can upload archives linked to this environment into Deploynaut.<br />' .
1010
-                    'Linking them to an environment allows limiting download permissions (see below).'
1011
-                ),
1012
-
1013
-            // The Main.ArchiveDownloaders
1014
-            $this
1015
-                ->buildPermissionField('ArchiveDownloaderGroups', 'ArchiveDownloaders', $groups, $members)
1016
-                ->setTitle('Who can download?')
1017
-                ->setDescription(<<<PHP
45
+	/**
46
+	 * If this is set to a full pathfile, it will be used as template
47
+	 * file when creating a new capistrano environment config file.
48
+	 *
49
+	 * If not set, the default 'environment.template' from the module
50
+	 * root is used
51
+	 *
52
+	 * @config
53
+	 * @var string
54
+	 */
55
+	private static $template_file = '';
56
+
57
+	/**
58
+	 * Set this to true to allow editing of the environment files via the web admin
59
+	 *
60
+	 * @var bool
61
+	 */
62
+	private static $allow_web_editing = false;
63
+
64
+	/**
65
+	 * @var array
66
+	 */
67
+	private static $casting = array(
68
+		'DeployHistory' => 'Text'
69
+	);
70
+
71
+	/**
72
+	 * Allowed backends. A map of Injector identifier to human-readable label.
73
+	 *
74
+	 * @config
75
+	 * @var array
76
+	 */
77
+	private static $allowed_backends = array();
78
+
79
+	/**
80
+	 * @var array
81
+	 */
82
+	public static $db = array(
83
+		"Filename" => "Varchar(255)",
84
+		"Name" => "Varchar(255)",
85
+		"URL" => "Varchar(255)",
86
+		"BackendIdentifier" => "Varchar(255)", // Injector identifier of the DeploymentBackend
87
+		"DryRunEnabled" => "Boolean", // True if the dry run button should be enabled on the frontend
88
+		"Usage" => "Enum('Production, UAT, Test, Unspecified', 'Unspecified')"
89
+	);
90
+
91
+	/**
92
+	 * @var array
93
+	 */
94
+	public static $has_one = array(
95
+		"Project" => "DNProject",
96
+		"CreateEnvironment" => "DNCreateEnvironment"
97
+	);
98
+
99
+	/**
100
+	 * @var array
101
+	 */
102
+	public static $has_many = array(
103
+		"Deployments" => "DNDeployment",
104
+		"DataArchives" => "DNDataArchive",
105
+		"Pipelines" => "Pipeline" // Only one Pipeline can be 'Running' at any one time. @see self::CurrentPipeline().
106
+	);
107
+
108
+	/**
109
+	 * @var array
110
+	 */
111
+	public static $many_many = array(
112
+		"Viewers"            => "Member", // Who can view this environment
113
+		"ViewerGroups"       => "Group",
114
+		"Deployers"          => "Member", // Who can deploy to this environment
115
+		"DeployerGroups" => "Group",
116
+		"CanRestoreMembers"  => "Member", // Who can restore archive files to this environment
117
+		"CanRestoreGroups"  => "Group",
118
+		"CanBackupMembers"   => "Member", // Who can backup archive files from this environment
119
+		"CanBackupGroups"   => "Group",
120
+		"ArchiveUploaders"   => "Member", // Who can upload archive files linked to this environment
121
+		"ArchiveUploaderGroups" => "Group",
122
+		"ArchiveDownloaders" => "Member", // Who can download archive files from this environment
123
+		"ArchiveDownloaderGroups" => "Group",
124
+		"ArchiveDeleters"    => "Member", // Who can delete archive files from this environment,
125
+		"ArchiveDeleterGroups" => "Group",
126
+		"PipelineApprovers"  => "Member", // Who can approve / reject pipelines from this environment
127
+		"PipelineApproverGroups" => "Group",
128
+		"PipelineCancellers"   => "Member", // Who can abort pipelines
129
+		"PipelineCancellerGroups" => "Group"
130
+	);
131
+
132
+	/**
133
+	 * @var array
134
+	 */
135
+	public static $summary_fields = array(
136
+		"Name" => "Environment Name",
137
+		"Usage" => "Usage",
138
+		"URL" => "URL",
139
+		"DeployersList" => "Can Deploy List",
140
+		"CanRestoreMembersList" => "Can Restore List",
141
+		"CanBackupMembersList" => "Can Backup List",
142
+		"ArchiveUploadersList" => "Can Upload List",
143
+		"ArchiveDownloadersList" => "Can Download List",
144
+		"ArchiveDeletersList"  => "Can Delete List",
145
+		"PipelineApproversList" => "Can Approve List",
146
+		"PipelineCancellersList" => "Can Cancel List"
147
+	);
148
+
149
+	private static $singular_name = 'Capistrano Environment';
150
+
151
+	private static $plural_name = 'Capistrano Environments';
152
+
153
+	/**
154
+	 * @var array
155
+	 */
156
+	public static $searchable_fields = array(
157
+		"Name",
158
+	);
159
+
160
+	/**
161
+	 * @var string
162
+	 */
163
+	private static $default_sort = 'Name';
164
+
165
+	/**
166
+	 * Used by the sync task
167
+	 *
168
+	 * @param string $path
169
+	 * @return \DNEnvironment
170
+	 */
171
+	public static function create_from_path($path)
172
+	{
173
+		$e = DNEnvironment::create();
174
+		$e->Filename = $path;
175
+		$e->Name = basename($e->Filename, '.rb');
176
+
177
+		// add each administrator member as a deployer of the new environment
178
+		$adminGroup = Group::get()->filter('Code', 'administrators')->first();
179
+		$e->DeployerGroups()->add($adminGroup);
180
+		return $e;
181
+	}
182
+
183
+	/**
184
+	 * Get the deployment backend used for this environment.
185
+	 *
186
+	 * Enforces compliance with the allowed_backends setting; if the DNEnvironment.BackendIdentifier value is
187
+	 * illegal then that value is ignored.
188
+	 *
189
+	 * @return DeploymentBackend
190
+	 */
191
+	public function Backend()
192
+	{
193
+		$backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET));
194
+		switch (sizeof($backends)) {
195
+		// Nothing allowed, use the default value "DeploymentBackend"
196
+			case 0:
197
+				$backend = "DeploymentBackend";
198
+				break;
199
+
200
+			// Only 1 thing allowed, use that
201
+			case 1:
202
+				$backend = $backends[0];
203
+				break;
204
+
205
+			// Multiple choices, use our choice if it's legal, otherwise default to the first item on the list
206
+			default:
207
+				$backend = $this->BackendIdentifier;
208
+				if (!in_array($backend, $backends)) {
209
+					$backend = $backends[0];
210
+				}
211
+		}
212
+
213
+		return Injector::inst()->get($backend);
214
+	}
215
+
216
+	public function Menu()
217
+	{
218
+		$list = new ArrayList();
219
+
220
+		$controller = Controller::curr();
221
+		$actionType = $controller->getField('CurrentActionType');
222
+
223
+		$list->push(new ArrayData(array(
224
+			'Link' => sprintf('naut/project/%s/environment/%s', $this->Project()->Name, $this->Name),
225
+			'Title' => 'Deployments',
226
+			'IsCurrent' => $this->isCurrent(),
227
+			'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_DEPLOY
228
+		)));
229
+
230
+		$this->extend('updateMenu', $list);
231
+
232
+		return $list;
233
+	}
234
+
235
+	/**
236
+	 * Return the current object from $this->Menu()
237
+	 * Good for making titles and things
238
+	 */
239
+	public function CurrentMenu()
240
+	{
241
+		return $this->Menu()->filter('IsSection', true)->First();
242
+	}
243
+
244
+	/**
245
+	 * Return a name for this environment.
246
+	 *
247
+	 * @param string $separator The string used when concatenating project with env name
248
+	 * @return string
249
+	 */
250
+	public function getFullName($separator = ':')
251
+	{
252
+		return sprintf('%s%s%s', $this->Project()->Name, $separator, $this->Name);
253
+	}
254
+
255
+	public function getBareURL()
256
+	{
257
+		$url = parse_url($this->URL);
258
+		if (isset($url['host'])) {
259
+			return strtolower($url['host']);
260
+		}
261
+	}
262
+
263
+	/**
264
+	 * @return boolean true if there is a pipeline for the current environment.
265
+	 */
266
+	public function HasPipelineSupport()
267
+	{
268
+		$config = $this->GenericPipelineConfig();
269
+		return $config instanceof ArrayData && isset($config->Steps);
270
+	}
271
+
272
+	/**
273
+	 * Returns a {@link Pipeline} object that is linked to this environment, but isn't saved into the database. This
274
+	 * shouldn't be saved into the database unless you plan on starting an actual pipeline.
275
+	 *
276
+	 * @return Pipeline
277
+	 */
278
+	public function GenericPipeline()
279
+	{
280
+		$pipeline = Pipeline::create();
281
+		$pipeline->EnvironmentID = $this->ID;
282
+		return $pipeline;
283
+	}
284
+
285
+	/**
286
+	 * Returns the parsed config, based on a {@link Pipeline} being created for this {@link DNEnvironment}.
287
+	 *
288
+	 * @return ArrayData
289
+	 */
290
+	public function GenericPipelineConfig()
291
+	{
292
+		$config = $this->loadPipelineConfig();
293
+		if ($config) {
294
+			return self::array_to_viewabledata($config);
295
+		}
296
+	}
297
+
298
+	/**
299
+	 * Extract pipeline configuration data from the source yml file
300
+	 *
301
+	 * @return array
302
+	 */
303
+	public function loadPipelineConfig()
304
+	{
305
+		require_once 'thirdparty/spyc/spyc.php';
306
+
307
+		$path = $this->getPipelineFilename();
308
+		if (file_exists($path)) {
309
+			return Spyc::YAMLLoad($path);
310
+		}
311
+	}
312
+
313
+	/**
314
+	 * Returns the {@link DNEnvironment} object relating to the pipeline config for this environment. The environment
315
+	 * YAML file (e.g. project1-uat.yml; see docs/en/pipelines.md) contains two variable called `DependsOnProject` and
316
+	 * `DependsOnEnvironment` - these are used together to find the {@link DNEnvironment} that this environment should
317
+	 * rely on.
318
+	 */
319
+	public function DependsOnEnvironment()
320
+	{
321
+		if ($this->HasPipelineSupport()) {
322
+			$pipeline = $this->GenericPipeline();
323
+			return $pipeline->getDependentEnvironment();
324
+		}
325
+
326
+		return null;
327
+	}
328
+
329
+	/**
330
+	 * @return bool true if there is a currently running Pipeline, and false if there isn't
331
+	 */
332
+	public function HasCurrentPipeline()
333
+	{
334
+		return $this->CurrentPipeline() && $this->CurrentPipeline()->isInDB();
335
+	}
336
+
337
+	/**
338
+	 * This can be used to determine if there is a currently running pipeline (there can only be one running per
339
+	 * {@link DNEnvironment} at once), as well as getting the current pipeline to be shown in templates.
340
+	 *
341
+	 * @return DataObject|null The currently running pipeline, or null if there isn't any.
342
+	 */
343
+	public function CurrentPipeline()
344
+	{
345
+		return $this->Pipelines()->filter('Status', array('Running', 'Rollback'))->first();
346
+	}
347
+
348
+	/**
349
+	 * @return bool true if the current user can cancel a running pipeline
350
+	 */
351
+	public function CanCancelPipeline()
352
+	{
353
+		// do we have a current pipeline
354
+		if ($this->HasCurrentPipeline()) {
355
+			return $this->CurrentPipeline()->canAbort();
356
+		}
357
+		return false;
358
+	}
359
+
360
+	/**
361
+	 * Environments are only viewable by people that can view the environment.
362
+	 *
363
+	 * @param Member|null $member
364
+	 * @return boolean
365
+	 */
366
+	public function canView($member = null)
367
+	{
368
+		if (!$member) {
369
+			$member = Member::currentUser();
370
+		}
371
+		if (!$member) {
372
+			return false;
373
+		}
374
+		// Must be logged in to check permissions
375
+
376
+		if (Permission::checkMember($member, 'ADMIN')) {
377
+			return true;
378
+		}
379
+
380
+		// if no Viewers or ViewerGroups defined, fallback to DNProject::canView permissions
381
+		if ($this->Viewers()->exists() || $this->ViewerGroups()->exists()) {
382
+			return $this->Viewers()->byID($member->ID)
383
+				|| $member->inGroups($this->ViewerGroups());
384
+		}
385
+
386
+		return $this->Project()->canView($member);
387
+	}
388
+
389
+	/**
390
+	 * Allow deploy only to some people.
391
+	 *
392
+	 * @param Member|null $member
393
+	 * @return boolean
394
+	 */
395
+	public function canDeploy($member = null)
396
+	{
397
+		if (!$member) {
398
+			$member = Member::currentUser();
399
+		}
400
+		if (!$member) {
401
+			return false;
402
+		}
403
+		// Must be logged in to check permissions
404
+
405
+		if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
406
+			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_DEPLOYMENT, $member)) {
407
+				return true;
408
+			}
409
+		} else {
410
+			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_DEPLOYMENT, $member)) {
411
+				return true;
412
+			}
413
+		}
414
+
415
+		return $this->Deployers()->byID($member->ID)
416
+			|| $member->inGroups($this->DeployerGroups());
417
+	}
418
+
419
+	/**
420
+	 * Allows only selected {@link Member} objects to restore {@link DNDataArchive} objects into this
421
+	 * {@link DNEnvironment}.
422
+	 *
423
+	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
424
+	 * @return boolean true if $member can restore, and false if they can't.
425
+	 */
426
+	public function canRestore($member = null)
427
+	{
428
+		if (!$member) {
429
+			$member = Member::currentUser();
430
+		}
431
+		if (!$member) {
432
+			return false;
433
+		}
434
+		// Must be logged in to check permissions
435
+
436
+		if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
437
+			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
438
+				return true;
439
+			}
440
+		} else {
441
+			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
442
+				return true;
443
+			}
444
+		}
445
+
446
+		return $this->CanRestoreMembers()->byID($member->ID)
447
+			|| $member->inGroups($this->CanRestoreGroups());
448
+	}
449
+
450
+	/**
451
+	 * Allows only selected {@link Member} objects to backup this {@link DNEnvironment} to a {@link DNDataArchive}
452
+	 * file.
453
+	 *
454
+	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
455
+	 * @return boolean true if $member can backup, and false if they can't.
456
+	 */
457
+	public function canBackup($member = null)
458
+	{
459
+		$project = $this->Project();
460
+		if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
461
+			return false;
462
+		}
463
+
464
+		if (!$member) {
465
+			$member = Member::currentUser();
466
+		}
467
+		// Must be logged in to check permissions
468
+		if (!$member) {
469
+			return false;
470
+		}
471
+
472
+		if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
473
+			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
474
+				return true;
475
+			}
476
+		} else {
477
+			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
478
+				return true;
479
+			}
480
+		}
481
+
482
+		return $this->CanBackupMembers()->byID($member->ID)
483
+			|| $member->inGroups($this->CanBackupGroups());
484
+	}
485
+
486
+	/**
487
+	 * Allows only selected {@link Member} objects to upload {@link DNDataArchive} objects linked to this
488
+	 * {@link DNEnvironment}.
489
+	 *
490
+	 * Note: This is not uploading them to the actual environment itself (e.g. uploading to the live site) - it is the
491
+	 * process of uploading a *.sspak file into Deploynaut for later 'restoring' to an environment. See
492
+	 * {@link self::canRestore()}.
493
+	 *
494
+	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
495
+	 * @return boolean true if $member can upload archives linked to this environment, false if they can't.
496
+	 */
497
+	public function canUploadArchive($member = null)
498
+	{
499
+		$project = $this->Project();
500
+		if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
501
+			return false;
502
+		}
503
+
504
+		if (!$member) {
505
+			$member = Member::currentUser();
506
+		}
507
+		if (!$member) {
508
+			return false;
509
+		}
510
+		// Must be logged in to check permissions
511
+
512
+		if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
513
+			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
514
+				return true;
515
+			}
516
+		} else {
517
+			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
518
+				return true;
519
+			}
520
+		}
521
+
522
+		return $this->ArchiveUploaders()->byID($member->ID)
523
+			|| $member->inGroups($this->ArchiveUploaderGroups());
524
+	}
525
+
526
+	/**
527
+	 * Allows only selected {@link Member} objects to download {@link DNDataArchive} objects from this
528
+	 * {@link DNEnvironment}.
529
+	 *
530
+	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
531
+	 * @return boolean true if $member can download archives from this environment, false if they can't.
532
+	 */
533
+	public function canDownloadArchive($member = null)
534
+	{
535
+		if (!$member) {
536
+			$member = Member::currentUser();
537
+		}
538
+		if (!$member) {
539
+			return false;
540
+		}
541
+		// Must be logged in to check permissions
542
+
543
+		if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
544
+			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
545
+				return true;
546
+			}
547
+		} else {
548
+			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
549
+				return true;
550
+			}
551
+		}
552
+
553
+		return $this->ArchiveDownloaders()->byID($member->ID)
554
+			|| $member->inGroups($this->ArchiveDownloaderGroups());
555
+	}
556
+
557
+	/**
558
+	 * Determine if the specified user can abort any pipelines
559
+	 *
560
+	 * @param Member|null $member
561
+	 * @return boolean
562
+	 */
563
+	public function canAbort($member = null)
564
+	{
565
+		if (!$member) {
566
+			$member = Member::currentUser();
567
+		}
568
+		if (!$member) {
569
+			return false;
570
+		}
571
+
572
+		if (Permission::checkMember($member, 'ADMIN')) {
573
+			return true;
574
+		}
575
+
576
+		return $this->PipelineCancellers()->byID($member->ID)
577
+			|| $member->inGroups($this->PipelineCancellerGroups());
578
+	}
579
+
580
+	/**
581
+	 * Determine if the specified user can approve any pipelines
582
+	 *
583
+	 * @param Member|null $member
584
+	 * @return boolean
585
+	 */
586
+	public function canApprove($member = null)
587
+	{
588
+		if (!$member) {
589
+			$member = Member::currentUser();
590
+		}
591
+		if (!$member) {
592
+			return false;
593
+		}
594
+
595
+		if (Permission::checkMember($member, 'ADMIN')) {
596
+			return true;
597
+		}
598
+		return $this->PipelineApprovers()->byID($member->ID)
599
+			|| $member->inGroups($this->PipelineApproverGroups());
600
+	}
601
+
602
+	/**
603
+	 * Allows only selected {@link Member} objects to delete {@link DNDataArchive} objects from this
604
+	 * {@link DNEnvironment}.
605
+	 *
606
+	 * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
607
+	 * @return boolean true if $member can delete archives from this environment, false if they can't.
608
+	 */
609
+	public function canDeleteArchive($member = null)
610
+	{
611
+		if (!$member) {
612
+			$member = Member::currentUser();
613
+		}
614
+		if (!$member) {
615
+			return false;
616
+		}
617
+		// Must be logged in to check permissions
618
+
619
+		if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
620
+			if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
621
+				return true;
622
+			}
623
+		} else {
624
+			if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
625
+				return true;
626
+			}
627
+		}
628
+
629
+		return $this->ArchiveDeleters()->byID($member->ID)
630
+			|| $member->inGroups($this->ArchiveDeleterGroups());
631
+	}
632
+	/**
633
+	 * Get a string of groups/people that are allowed to deploy to this environment.
634
+	 * Used in DNRoot_project.ss to list {@link Member}s who have permission to perform this action.
635
+	 *
636
+	 * @return string
637
+	 */
638
+	public function getDeployersList()
639
+	{
640
+		return implode(
641
+			", ",
642
+			array_merge(
643
+				$this->DeployerGroups()->column("Title"),
644
+				$this->Deployers()->column("FirstName")
645
+			)
646
+		);
647
+	}
648
+
649
+	/**
650
+	 * Get a string of groups/people that are allowed to restore {@link DNDataArchive} objects into this environment.
651
+	 *
652
+	 * @return string
653
+	 */
654
+	public function getCanRestoreMembersList()
655
+	{
656
+		return implode(
657
+			", ",
658
+			array_merge(
659
+				$this->CanRestoreGroups()->column("Title"),
660
+				$this->CanRestoreMembers()->column("FirstName")
661
+			)
662
+		);
663
+	}
664
+
665
+	/**
666
+	 * Get a string of groups/people that are allowed to backup {@link DNDataArchive} objects from this environment.
667
+	 *
668
+	 * @return string
669
+	 */
670
+	public function getCanBackupMembersList()
671
+	{
672
+		return implode(
673
+			", ",
674
+			array_merge(
675
+				$this->CanBackupGroups()->column("Title"),
676
+				$this->CanBackupMembers()->column("FirstName")
677
+			)
678
+		);
679
+	}
680
+
681
+	/**
682
+	 * Get a string of groups/people that are allowed to upload {@link DNDataArchive}
683
+	 *  objects linked to this environment.
684
+	 *
685
+	 * @return string
686
+	 */
687
+	public function getArchiveUploadersList()
688
+	{
689
+		return implode(
690
+			", ",
691
+			array_merge(
692
+				$this->ArchiveUploaderGroups()->column("Title"),
693
+				$this->ArchiveUploaders()->column("FirstName")
694
+			)
695
+		);
696
+	}
697
+
698
+	/**
699
+	 * Get a string of groups/people that are allowed to download {@link DNDataArchive} objects from this environment.
700
+	 *
701
+	 * @return string
702
+	 */
703
+	public function getArchiveDownloadersList()
704
+	{
705
+		return implode(
706
+			", ",
707
+			array_merge(
708
+				$this->ArchiveDownloaderGroups()->column("Title"),
709
+				$this->ArchiveDownloaders()->column("FirstName")
710
+			)
711
+		);
712
+	}
713
+
714
+	/**
715
+	 * Get a string of groups/people that are allowed to delete {@link DNDataArchive} objects from this environment.
716
+	 *
717
+	 * @return string
718
+	 */
719
+	public function getArchiveDeletersList()
720
+	{
721
+		return implode(
722
+			", ",
723
+			array_merge(
724
+				$this->ArchiveDeleterGroups()->column("Title"),
725
+				$this->ArchiveDeleters()->column("FirstName")
726
+			)
727
+		);
728
+	}
729
+
730
+	/**
731
+	 * Get a string of groups/people that are allowed to approve pipelines
732
+	 *
733
+	 * @return string
734
+	 */
735
+	public function getPipelineApproversList()
736
+	{
737
+		return implode(
738
+			", ",
739
+			array_merge(
740
+				$this->PipelineApproverGroups()->column("Title"),
741
+				$this->PipelineApprovers()->column("FirstName")
742
+			)
743
+		);
744
+	}
745
+
746
+	/**
747
+	 * Get a string of groups/people that are allowed to cancel pipelines
748
+	 *
749
+	 * @return string
750
+	 */
751
+	public function getPipelineCancellersList()
752
+	{
753
+		return implode(
754
+			", ",
755
+			array_merge(
756
+				$this->PipelineCancellerGroups()->column("Title"),
757
+				$this->PipelineCancellers()->column("FirstName")
758
+			)
759
+		);
760
+	}
761
+
762
+	/**
763
+	 * @return DNData
764
+	 */
765
+	public function DNData()
766
+	{
767
+		return DNData::inst();
768
+	}
769
+
770
+	/**
771
+	 * Get the current deployed build for this environment
772
+	 *
773
+	 * Dear people of the future: If you are looking to optimize this, simply create a CurrentBuildSHA(), which can be
774
+	 * a lot faster. I presume you came here because of the Project display template, which only needs a SHA.
775
+	 *
776
+	 * @return false|DNDeployment
777
+	 */
778
+	public function CurrentBuild()
779
+	{
780
+		// The DeployHistory function is far too slow to use for this
781
+
782
+		/** @var DNDeployment $deploy */
783
+		$deploy = DNDeployment::get()->filter(array(
784
+			'EnvironmentID' => $this->ID,
785
+			'Status' => 'Finished'
786
+		))->sort('LastEdited DESC')->first();
787
+
788
+		if (!$deploy || (!$deploy->SHA)) {
789
+			return false;
790
+		}
791
+
792
+		$repo = $this->Project()->getRepository();
793
+		if (!$repo) {
794
+			return $deploy;
795
+		}
796
+
797
+		try {
798
+			$commit = $repo->getCommit($deploy->SHA);
799
+			if ($commit) {
800
+				$deploy->Message = Convert::raw2xml($commit->getMessage());
801
+				$deploy->Committer = Convert::raw2xml($commit->getCommitterName());
802
+				$deploy->CommitDate = $commit->getCommitterDate()->Format('d/m/Y g:ia');
803
+				$deploy->Author = Convert::raw2xml($commit->getAuthorName());
804
+				$deploy->AuthorDate = $commit->getAuthorDate()->Format('d/m/Y g:ia');
805
+			}
806
+			// We can't find this SHA, so we ignore adding a commit message to the deployment
807
+		} catch (Exception $ex) {
808
+		}
809
+
810
+		return $deploy;
811
+	}
812
+
813
+	/**
814
+	 * A history of all builds deployed to this environment
815
+	 *
816
+	 * @return ArrayList
817
+	 */
818
+	public function DeployHistory()
819
+	{
820
+		return $this->Deployments()
821
+			->where('SHA IS NOT NULL')
822
+			->sort('LastEdited DESC');
823
+	}
824
+
825
+	/**
826
+	 * @param string $sha
827
+	 * @return array
828
+	 */
829
+	protected function getCommitData($sha)
830
+	{
831
+		try {
832
+			$repo = $this->Project()->getRepository();
833
+			if ($repo !== false) {
834
+				$commit = new \Gitonomy\Git\Commit($repo, $sha);
835
+				return [
836
+					'AuthorName' => (string)Convert::raw2xml($commit->getAuthorName()),
837
+					'AuthorEmail' => (string)Convert::raw2xml($commit->getAuthorEmail()),
838
+					'Message' => (string)Convert::raw2xml($commit->getMessage()),
839
+					'ShortHash' => Convert::raw2xml($commit->getFixedShortHash(8)),
840
+					'Hash' => Convert::raw2xml($commit->getHash())
841
+				];
842
+			}
843
+		} catch (\Gitonomy\Git\Exception\ReferenceNotFoundException $exc) {
844
+			SS_Log::log($exc, SS_Log::WARN);
845
+		}
846
+		return array(
847
+			'AuthorName' => '(unknown)',
848
+			'AuthorEmail' => '(unknown)',
849
+			'Message' => '(unknown)',
850
+			'ShortHash' => $sha,
851
+			'Hash' => '(unknown)',
852
+		);
853
+	}
854
+
855
+	/**
856
+	 * @return string
857
+	 */
858
+	public function Link()
859
+	{
860
+		return $this->Project()->Link() . "/environment/" . $this->Name;
861
+	}
862
+
863
+	/**
864
+	 * Is this environment currently at the root level of the controller that handles it?
865
+	 * @return bool
866
+	 */
867
+	public function isCurrent()
868
+	{
869
+		return $this->isSection() && Controller::curr()->getAction() == 'environment';
870
+	}
871
+
872
+	/**
873
+	 * Is this environment currently in a controller that is handling it or performing a sub-task?
874
+	 * @return bool
875
+	 */
876
+	public function isSection()
877
+	{
878
+		$controller = Controller::curr();
879
+		$environment = $controller->getField('CurrentEnvironment');
880
+		return $environment && $environment->ID == $this->ID;
881
+	}
882
+
883
+
884
+	/**
885
+	 * Build a set of multi-select fields for assigning permissions to a pair of group and member many_many relations
886
+	 *
887
+	 * @param string $groupField Group field name
888
+	 * @param string $memberField Member field name
889
+	 * @param array $groups List of groups
890
+	 * @param array $members List of members
891
+	 * @return FieldGroup
892
+	 */
893
+	protected function buildPermissionField($groupField, $memberField, $groups, $members)
894
+	{
895
+		return FieldGroup::create(
896
+			ListboxField::create($groupField, false, $groups)
897
+				->setMultiple(true)
898
+				->setAttribute('data-placeholder', 'Groups')
899
+				->setAttribute('placeholder', 'Groups')
900
+				->setAttribute('style', 'width: 400px;'),
901
+
902
+			ListboxField::create($memberField, false, $members)
903
+				->setMultiple(true)
904
+				->setAttribute('data-placeholder', 'Members')
905
+				->setAttribute('placeholder', 'Members')
906
+				->setAttribute('style', 'width: 400px;')
907
+		);
908
+	}
909
+
910
+	/**
911
+	 * @return FieldList
912
+	 */
913
+	public function getCMSFields()
914
+	{
915
+		$fields = new FieldList(new TabSet('Root'));
916
+
917
+		$project = $this->Project();
918
+		if ($project && $project->exists()) {
919
+			$viewerGroups = $project->Viewers();
920
+			$groups = $viewerGroups->sort('Title')->map()->toArray();
921
+			$members = array();
922
+			foreach ($viewerGroups as $group) {
923
+				foreach ($group->Members()->map() as $k => $v) {
924
+					$members[$k] = $v;
925
+				}
926
+			}
927
+			asort($members);
928
+		} else {
929
+			$groups = array();
930
+			$members = array();
931
+		}
932
+
933
+		// Main tab
934
+		$fields->addFieldsToTab('Root.Main', array(
935
+			// The Main.ProjectID
936
+			TextField::create('ProjectName', 'Project')
937
+				->setValue(($project = $this->Project()) ? $project->Name : null)
938
+				->performReadonlyTransformation(),
939
+
940
+			// The Main.Name
941
+			TextField::create('Name', 'Environment name')
942
+				->setDescription('A descriptive name for this environment, e.g. staging, uat, production'),
943
+
944
+
945
+			$this->obj('Usage')->scaffoldFormField('Environment usage'),
946
+
947
+			// The Main.URL field
948
+			TextField::create('URL', 'Server URL')
949
+				->setDescription('This url will be used to provide the front-end with a link to this environment'),
950
+
951
+			// The Main.Filename
952
+			TextField::create('Filename')
953
+				->setDescription('The capistrano environment file name')
954
+				->performReadonlyTransformation(),
955
+		));
956
+
957
+		// Backend identifier - pick from a named list of configurations specified in YML config
958
+		$backends = $this->config()->get('allowed_backends', Config::FIRST_SET);
959
+		// If there's only 1 backend, then user selection isn't needed
960
+		if (sizeof($backends) > 1) {
961
+			$fields->addFieldToTab('Root.Main', DropdownField::create('BackendIdentifier', 'Deployment backend')
962
+				->setSource($backends)
963
+				->setDescription('What kind of deployment system should be used to deploy to this environment'));
964
+		}
965
+
966
+		$fields->addFieldsToTab('Root.UserPermissions', array(
967
+			// The viewers of the environment
968
+			$this
969
+				->buildPermissionField('ViewerGroups', 'Viewers', $groups, $members)
970
+				->setTitle('Who can view this environment?')
971
+				->setDescription('Groups or Users who can view this environment'),
972
+
973
+			// The Main.Deployers
974
+			$this
975
+				->buildPermissionField('DeployerGroups', 'Deployers', $groups, $members)
976
+				->setTitle('Who can deploy?')
977
+				->setDescription('Groups or Users who can deploy to this environment'),
978
+
979
+			// A box to select all snapshot options.
980
+			$this
981
+				->buildPermissionField('TickAllSnapshotGroups', 'TickAllSnapshot', $groups, $members)
982
+				->setTitle("<em>All snapshot permissions</em>")
983
+				->addExtraClass('tickall')
984
+				->setDescription('UI shortcut to select all snapshot-related options - not written to the database.'),
985
+
986
+			// The Main.CanRestoreMembers
987
+			$this
988
+				->buildPermissionField('CanRestoreGroups', 'CanRestoreMembers', $groups, $members)
989
+				->setTitle('Who can restore?')
990
+				->setDescription('Groups or Users who can restore archives from Deploynaut into this environment'),
991
+
992
+			// The Main.CanBackupMembers
993
+			$this
994
+				->buildPermissionField('CanBackupGroups', 'CanBackupMembers', $groups, $members)
995
+				->setTitle('Who can backup?')
996
+				->setDescription('Groups or Users who can backup archives from this environment into Deploynaut'),
997
+
998
+			// The Main.ArchiveDeleters
999
+			$this
1000
+				->buildPermissionField('ArchiveDeleterGroups', 'ArchiveDeleters', $groups, $members)
1001
+				->setTitle('Who can delete?')
1002
+				->setDescription("Groups or Users who can delete archives from this environment's staging area."),
1003
+
1004
+			// The Main.ArchiveUploaders
1005
+			$this
1006
+				->buildPermissionField('ArchiveUploaderGroups', 'ArchiveUploaders', $groups, $members)
1007
+				->setTitle('Who can upload?')
1008
+				->setDescription(
1009
+					'Users who can upload archives linked to this environment into Deploynaut.<br />' .
1010
+					'Linking them to an environment allows limiting download permissions (see below).'
1011
+				),
1012
+
1013
+			// The Main.ArchiveDownloaders
1014
+			$this
1015
+				->buildPermissionField('ArchiveDownloaderGroups', 'ArchiveDownloaders', $groups, $members)
1016
+				->setTitle('Who can download?')
1017
+				->setDescription(<<<PHP
1018 1018
 Users who can download archives from this environment to their computer.<br />
1019 1019
 Since this implies access to the snapshot, it is also a prerequisite for restores
1020 1020
 to other environments, alongside the "Who can restore" permission.<br>
1021 1021
 Should include all users with upload permissions, otherwise they can't download
1022 1022
 their own uploads.
1023 1023
 PHP
1024
-                ),
1025
-
1026
-            // The Main.PipelineApprovers
1027
-            $this
1028
-                ->buildPermissionField('PipelineApproverGroups', 'PipelineApprovers', $groups, $members)
1029
-                ->setTitle('Who can approve pipelines?')
1030
-                ->setDescription('Users who can approve waiting deployment pipelines.'),
1031
-
1032
-            // The Main.PipelineCancellers
1033
-            $this
1034
-                ->buildPermissionField('PipelineCancellerGroups', 'PipelineCancellers', $groups, $members)
1035
-                ->setTitle('Who can cancel pipelines?')
1036
-                ->setDescription('Users who can cancel in-progess deployment pipelines.')
1037
-        ));
1038
-
1039
-        // The Main.DeployConfig
1040
-        if ($this->Project()->exists()) {
1041
-            $this->setDeployConfigurationFields($fields);
1042
-        }
1043
-
1044
-        // The DataArchives
1045
-        $dataArchiveConfig = GridFieldConfig_RecordViewer::create();
1046
-        $dataArchiveConfig->removeComponentsByType('GridFieldAddNewButton');
1047
-        if (class_exists('GridFieldBulkManager')) {
1048
-            $dataArchiveConfig->addComponent(new GridFieldBulkManager());
1049
-        }
1050
-        $dataArchive = GridField::create('DataArchives', 'Data Archives', $this->DataArchives(), $dataArchiveConfig);
1051
-        $fields->addFieldToTab('Root.DataArchive', $dataArchive);
1052
-
1053
-        // Pipeline templates
1054
-        $this->setPipelineConfigurationFields($fields);
1055
-
1056
-        // Pipelines
1057
-        if ($this->Pipelines()->Count()) {
1058
-            $pipelinesConfig = GridFieldConfig_RecordEditor::create();
1059
-            $pipelinesConfig->removeComponentsByType('GridFieldAddNewButton');
1060
-            if (class_exists('GridFieldBulkManager')) {
1061
-                $pipelinesConfig->addComponent(new GridFieldBulkManager());
1062
-            }
1063
-            $pipelines = GridField::create('Pipelines', 'Pipelines', $this->Pipelines(), $pipelinesConfig);
1064
-            $fields->addFieldToTab('Root.Pipelines', $pipelines);
1065
-        }
1066
-
1067
-        // Deployments
1068
-        $deploymentsConfig = GridFieldConfig_RecordEditor::create();
1069
-        $deploymentsConfig->removeComponentsByType('GridFieldAddNewButton');
1070
-        if (class_exists('GridFieldBulkManager')) {
1071
-            $deploymentsConfig->addComponent(new GridFieldBulkManager());
1072
-        }
1073
-        $deployments = GridField::create('Deployments', 'Deployments', $this->Deployments(), $deploymentsConfig);
1074
-        $fields->addFieldToTab('Root.Deployments', $deployments);
1075
-
1076
-        Requirements::javascript('deploynaut/javascript/environment.js');
1077
-
1078
-        // Add actions
1079
-        $action = new FormAction('check', 'Check Connection');
1080
-        $action->setUseButtonTag(true);
1081
-        $dataURL = Director::absoluteBaseURL() . 'naut/api/' . $this->Project()->Name . '/' . $this->Name . '/ping';
1082
-        $action->setAttribute('data-url', $dataURL);
1083
-        $fields->insertBefore($action, 'Name');
1084
-
1085
-        // Allow extensions
1086
-        $this->extend('updateCMSFields', $fields);
1087
-        return $fields;
1088
-    }
1089
-
1090
-    /**
1091
-     * @param FieldList $fields
1092
-     */
1093
-    protected function setDeployConfigurationFields(&$fields)
1094
-    {
1095
-        if (!$this->config()->get('allow_web_editing')) {
1096
-            return;
1097
-        }
1098
-
1099
-        if ($this->envFileExists()) {
1100
-            $deployConfig = new TextareaField('DeployConfig', 'Deploy config', $this->getEnvironmentConfig());
1101
-            $deployConfig->setRows(40);
1102
-            $fields->insertAfter($deployConfig, 'Filename');
1103
-            return;
1104
-        }
1105
-
1106
-        $warning = 'Warning: This environment doesn\'t have deployment configuration.';
1107
-        $noDeployConfig = new LabelField('noDeployConfig', $warning);
1108
-        $noDeployConfig->addExtraClass('message warning');
1109
-        $fields->insertAfter($noDeployConfig, 'Filename');
1110
-        $createConfigField = new CheckboxField('CreateEnvConfig', 'Create Config');
1111
-        $createConfigField->setDescription('Would you like to create the capistrano deploy configuration?');
1112
-        $fields->insertAfter($createConfigField, 'noDeployConfig');
1113
-    }
1114
-
1115
-    /**
1116
-     * @param FieldList $fields
1117
-     */
1118
-    protected function setPipelineConfigurationFields($fields)
1119
-    {
1120
-        if (!$this->config()->get('allow_web_editing')) {
1121
-            return;
1122
-        }
1123
-        $config = $this->pipelineFileExists()
1124
-            ? file_get_contents($this->getPipelineFilename())
1125
-            : '';
1126
-        $deployConfig = new TextareaField('PipelineConfig', 'Pipeline config', $config);
1127
-        $deployConfig->setRows(40);
1128
-        if (!$this->pipelineFileExists()) {
1129
-            $deployConfig->setDescription(
1130
-                "No pipeline is configured for this environment. Saving content here will generate a new template."
1131
-            );
1132
-        }
1133
-        $fields->addFieldsToTab('Root.PipelineSettings', array(
1134
-            FieldGroup::create(
1135
-                CheckboxField::create('DryRunEnabled', 'Enable dry-run?')
1136
-            )
1137
-                ->setTitle('Pipeline Options')
1138
-                ->setDescription(
1139
-                    "Allows admins to run simulated pipelines without triggering deployments or notifications."
1140
-                ),
1141
-            $deployConfig
1142
-        ));
1143
-    }
1144
-
1145
-    /**
1146
-     */
1147
-    public function onBeforeWrite()
1148
-    {
1149
-        parent::onBeforeWrite();
1150
-        if ($this->Name && $this->Name . '.rb' != $this->Filename) {
1151
-            $this->Filename = $this->Name . '.rb';
1152
-        }
1153
-        $this->checkEnvironmentPath();
1154
-        $this->writeConfigFile();
1155
-        $this->writePipelineFile();
1156
-    }
1157
-
1158
-    public function onAfterWrite()
1159
-    {
1160
-        parent::onAfterWrite();
1161
-
1162
-        if ($this->Usage == 'Production' || $this->Usage == 'UAT') {
1163
-            $conflicting = DNEnvironment::get()
1164
-                ->filter('ProjectID', $this->ProjectID)
1165
-                ->filter('Usage', $this->Usage)
1166
-                ->exclude('ID', $this->ID);
1167
-
1168
-            foreach ($conflicting as $otherEnvironment) {
1169
-                $otherEnvironment->Usage = 'Unspecified';
1170
-                $otherEnvironment->write();
1171
-            }
1172
-        }
1173
-    }
1174
-
1175
-
1176
-    /**
1177
-     * Ensure that environment paths are setup on the local filesystem
1178
-     */
1179
-    protected function checkEnvironmentPath()
1180
-    {
1181
-        // Create folder if it doesn't exist
1182
-        $configDir = dirname($this->getConfigFilename());
1183
-        if (!file_exists($configDir) && $configDir) {
1184
-            mkdir($configDir, 0777, true);
1185
-        }
1186
-    }
1187
-
1188
-    /**
1189
-     * Write the deployment config file to filesystem
1190
-     */
1191
-    protected function writeConfigFile()
1192
-    {
1193
-        if (!$this->config()->get('allow_web_editing')) {
1194
-            return;
1195
-        }
1196
-
1197
-        // Create a basic new environment config from a template
1198
-        if (!$this->envFileExists()
1199
-            && $this->Filename
1200
-            && $this->CreateEnvConfig
1201
-        ) {
1202
-            $templateFile = $this->config()->template_file ?: BASE_PATH . '/deploynaut/environment.template';
1203
-            file_put_contents($this->getConfigFilename(), file_get_contents($templateFile));
1204
-        } elseif ($this->envFileExists() && $this->DeployConfig) {
1205
-            file_put_contents($this->getConfigFilename(), $this->DeployConfig);
1206
-        }
1207
-    }
1208
-
1209
-    /**
1210
-     * Write the pipeline config file to filesystem
1211
-     */
1212
-    protected function writePipelineFile()
1213
-    {
1214
-        if (!$this->config()->get('allow_web_editing')) {
1215
-            return;
1216
-        }
1217
-        $path = $this->getPipelineFilename();
1218
-        if ($this->PipelineConfig) {
1219
-            // Update file
1220
-            file_put_contents($path, $this->PipelineConfig);
1221
-        } elseif ($this->isChanged('PipelineConfig') && file_exists($path)) {
1222
-            // Remove file if deleted
1223
-            unlink($path);
1224
-        }
1225
-    }
1226
-
1227
-    /**
1228
-     * Delete any related config files
1229
-     */
1230
-    public function onAfterDelete()
1231
-    {
1232
-        parent::onAfterDelete();
1233
-        // Create a basic new environment config from a template
1234
-        if ($this->config()->get('allow_web_editing') && $this->envFileExists()) {
1235
-            unlink($this->getConfigFilename());
1236
-        }
1237
-
1238
-        $create = $this->CreateEnvironment();
1239
-        if ($create && $create->exists()) {
1240
-            $create->delete();
1241
-        }
1242
-    }
1243
-
1244
-    /**
1245
-     * @return string
1246
-     */
1247
-    protected function getEnvironmentConfig()
1248
-    {
1249
-        if (!$this->envFileExists()) {
1250
-            return '';
1251
-        }
1252
-        return file_get_contents($this->getConfigFilename());
1253
-    }
1254
-
1255
-    /**
1256
-     * @return boolean
1257
-     */
1258
-    protected function envFileExists()
1259
-    {
1260
-        if (!$this->getConfigFilename()) {
1261
-            return false;
1262
-        }
1263
-        return file_exists($this->getConfigFilename());
1264
-    }
1265
-
1266
-    /**
1267
-     * Returns the path to the ruby config file
1268
-     *
1269
-     * @return string
1270
-     */
1271
-    public function getConfigFilename()
1272
-    {
1273
-        if (!$this->Project()->exists()) {
1274
-            return '';
1275
-        }
1276
-        if (!$this->Filename) {
1277
-            return '';
1278
-        }
1279
-        return $this->DNData()->getEnvironmentDir() . '/' . $this->Project()->Name . '/' . $this->Filename;
1280
-    }
1281
-
1282
-    /**
1283
-     * Returns the path to the {@link Pipeline} configuration for this environment.
1284
-     * Uses the same path and filename as the capistrano config, but with .yml extension.
1285
-     *
1286
-     * @return string
1287
-     */
1288
-    public function getPipelineFilename()
1289
-    {
1290
-        $name = $this->getConfigFilename();
1291
-        if (!$name) {
1292
-            return null;
1293
-        }
1294
-        $path = pathinfo($name);
1295
-        if ($path) {
1296
-            return $path['dirname'] . '/' . $path['filename'] . '.yml';
1297
-        }
1298
-    }
1299
-
1300
-    /**
1301
-     * Does this environment have a pipeline config file
1302
-     *
1303
-     * @return boolean
1304
-     */
1305
-    protected function pipelineFileExists()
1306
-    {
1307
-        $filename = $this->getPipelineFilename();
1308
-        if (empty($filename)) {
1309
-            return false;
1310
-        }
1311
-        return file_exists($filename);
1312
-    }
1313
-
1314
-    /**
1315
-     * Helper function to convert a multi-dimensional array (associative or indexed) to an {@link ArrayList} or
1316
-     * {@link ArrayData} object structure, so that values can be used in templates.
1317
-     *
1318
-     * @param array $array The (single- or multi-dimensional) array to convert
1319
-     * @return object Either an {@link ArrayList} or {@link ArrayData} object, or the original item ($array) if $array
1320
-     * isn't an array.
1321
-     */
1322
-    public static function array_to_viewabledata($array)
1323
-    {
1324
-        // Don't transform non-arrays
1325
-        if (!is_array($array)) {
1326
-            return $array;
1327
-        }
1328
-
1329
-        // Figure out whether this is indexed or associative
1330
-        $keys = array_keys($array);
1331
-        $assoc = ($keys != array_keys($keys));
1332
-        if ($assoc) {
1333
-            // Treat as viewable data
1334
-            $data = new ArrayData(array());
1335
-            foreach ($array as $key => $value) {
1336
-                $data->setField($key, self::array_to_viewabledata($value));
1337
-            }
1338
-            return $data;
1339
-        } else {
1340
-            // Treat this as basic non-associative list
1341
-            $list = new ArrayList();
1342
-            foreach ($array as $value) {
1343
-                $list->push(self::array_to_viewabledata($value));
1344
-            }
1345
-            return $list;
1346
-        }
1347
-    }
1348
-
1349
-
1350
-
1351
-    /**
1352
-     * Helper function to retrieve filtered commits from an environment
1353
-     * this environment depends on
1354
-     *
1355
-     * @return DataList
1356
-     */
1357
-    public function getDependentFilteredCommits()
1358
-    {
1359
-        // check if this environment depends on another environemnt
1360
-        $dependsOnEnv = $this->DependsOnEnvironment();
1361
-        if (empty($dependsOnEnv)) {
1362
-            return null;
1363
-        }
1364
-
1365
-        // Check if there is a filter
1366
-        $config = $this->GenericPipelineConfig();
1367
-        $filter = isset($config->PipelineConfig->FilteredCommits)
1368
-            ? $config->PipelineConfig->FilteredCommits
1369
-            : null;
1370
-        if (empty($filter)) {
1371
-            return null;
1372
-        }
1373
-
1374
-        // Create and execute filter
1375
-        if (!class_exists($filter)) {
1376
-            throw new Exception(sprintf("Class %s does not exist", $filter));
1377
-        }
1378
-        $commitClass = $filter::create();
1379
-        // setup the environment to check for commits
1380
-        $commitClass->env = $dependsOnEnv;
1381
-        return $commitClass->getCommits();
1382
-    }
1383
-
1384
-    /**
1385
-     * Enable the maintenance page
1386
-     *
1387
-     * @param DeploynautLogFile $log
1388
-     */
1389
-    public function enableMaintenace($log)
1390
-    {
1391
-        $this->Backend()
1392
-            ->enableMaintenance($this, $log, $this->Project());
1393
-    }
1394
-
1395
-    /**
1396
-     * Disable maintenance page
1397
-     *
1398
-     * @param DeploynautLogFile $log
1399
-     */
1400
-    public function disableMaintenance($log)
1401
-    {
1402
-        $this->Backend()
1403
-            ->disableMaintenance($this, $log, $this->Project());
1404
-    }
1405
-
1406
-    protected function validate()
1407
-    {
1408
-        $result = parent::validate();
1409
-        $backend = $this->Backend();
1410
-
1411
-        if (strcasecmp('test', $this->Name) === 0 && get_class($backend) == 'CapistranoDeploymentBackend') {
1412
-            $result->error('"test" is not a valid environment name when using Capistrano backend.');
1413
-        }
1414
-
1415
-        return $result;
1416
-    }
1024
+				),
1025
+
1026
+			// The Main.PipelineApprovers
1027
+			$this
1028
+				->buildPermissionField('PipelineApproverGroups', 'PipelineApprovers', $groups, $members)
1029
+				->setTitle('Who can approve pipelines?')
1030
+				->setDescription('Users who can approve waiting deployment pipelines.'),
1031
+
1032
+			// The Main.PipelineCancellers
1033
+			$this
1034
+				->buildPermissionField('PipelineCancellerGroups', 'PipelineCancellers', $groups, $members)
1035
+				->setTitle('Who can cancel pipelines?')
1036
+				->setDescription('Users who can cancel in-progess deployment pipelines.')
1037
+		));
1038
+
1039
+		// The Main.DeployConfig
1040
+		if ($this->Project()->exists()) {
1041
+			$this->setDeployConfigurationFields($fields);
1042
+		}
1043
+
1044
+		// The DataArchives
1045
+		$dataArchiveConfig = GridFieldConfig_RecordViewer::create();
1046
+		$dataArchiveConfig->removeComponentsByType('GridFieldAddNewButton');
1047
+		if (class_exists('GridFieldBulkManager')) {
1048
+			$dataArchiveConfig->addComponent(new GridFieldBulkManager());
1049
+		}
1050
+		$dataArchive = GridField::create('DataArchives', 'Data Archives', $this->DataArchives(), $dataArchiveConfig);
1051
+		$fields->addFieldToTab('Root.DataArchive', $dataArchive);
1052
+
1053
+		// Pipeline templates
1054
+		$this->setPipelineConfigurationFields($fields);
1055
+
1056
+		// Pipelines
1057
+		if ($this->Pipelines()->Count()) {
1058
+			$pipelinesConfig = GridFieldConfig_RecordEditor::create();
1059
+			$pipelinesConfig->removeComponentsByType('GridFieldAddNewButton');
1060
+			if (class_exists('GridFieldBulkManager')) {
1061
+				$pipelinesConfig->addComponent(new GridFieldBulkManager());
1062
+			}
1063
+			$pipelines = GridField::create('Pipelines', 'Pipelines', $this->Pipelines(), $pipelinesConfig);
1064
+			$fields->addFieldToTab('Root.Pipelines', $pipelines);
1065
+		}
1066
+
1067
+		// Deployments
1068
+		$deploymentsConfig = GridFieldConfig_RecordEditor::create();
1069
+		$deploymentsConfig->removeComponentsByType('GridFieldAddNewButton');
1070
+		if (class_exists('GridFieldBulkManager')) {
1071
+			$deploymentsConfig->addComponent(new GridFieldBulkManager());
1072
+		}
1073
+		$deployments = GridField::create('Deployments', 'Deployments', $this->Deployments(), $deploymentsConfig);
1074
+		$fields->addFieldToTab('Root.Deployments', $deployments);
1075
+
1076
+		Requirements::javascript('deploynaut/javascript/environment.js');
1077
+
1078
+		// Add actions
1079
+		$action = new FormAction('check', 'Check Connection');
1080
+		$action->setUseButtonTag(true);
1081
+		$dataURL = Director::absoluteBaseURL() . 'naut/api/' . $this->Project()->Name . '/' . $this->Name . '/ping';
1082
+		$action->setAttribute('data-url', $dataURL);
1083
+		$fields->insertBefore($action, 'Name');
1084
+
1085
+		// Allow extensions
1086
+		$this->extend('updateCMSFields', $fields);
1087
+		return $fields;
1088
+	}
1089
+
1090
+	/**
1091
+	 * @param FieldList $fields
1092
+	 */
1093
+	protected function setDeployConfigurationFields(&$fields)
1094
+	{
1095
+		if (!$this->config()->get('allow_web_editing')) {
1096
+			return;
1097
+		}
1098
+
1099
+		if ($this->envFileExists()) {
1100
+			$deployConfig = new TextareaField('DeployConfig', 'Deploy config', $this->getEnvironmentConfig());
1101
+			$deployConfig->setRows(40);
1102
+			$fields->insertAfter($deployConfig, 'Filename');
1103
+			return;
1104
+		}
1105
+
1106
+		$warning = 'Warning: This environment doesn\'t have deployment configuration.';
1107
+		$noDeployConfig = new LabelField('noDeployConfig', $warning);
1108
+		$noDeployConfig->addExtraClass('message warning');
1109
+		$fields->insertAfter($noDeployConfig, 'Filename');
1110
+		$createConfigField = new CheckboxField('CreateEnvConfig', 'Create Config');
1111
+		$createConfigField->setDescription('Would you like to create the capistrano deploy configuration?');
1112
+		$fields->insertAfter($createConfigField, 'noDeployConfig');
1113
+	}
1114
+
1115
+	/**
1116
+	 * @param FieldList $fields
1117
+	 */
1118
+	protected function setPipelineConfigurationFields($fields)
1119
+	{
1120
+		if (!$this->config()->get('allow_web_editing')) {
1121
+			return;
1122
+		}
1123
+		$config = $this->pipelineFileExists()
1124
+			? file_get_contents($this->getPipelineFilename())
1125
+			: '';
1126
+		$deployConfig = new TextareaField('PipelineConfig', 'Pipeline config', $config);
1127
+		$deployConfig->setRows(40);
1128
+		if (!$this->pipelineFileExists()) {
1129
+			$deployConfig->setDescription(
1130
+				"No pipeline is configured for this environment. Saving content here will generate a new template."
1131
+			);
1132
+		}
1133
+		$fields->addFieldsToTab('Root.PipelineSettings', array(
1134
+			FieldGroup::create(
1135
+				CheckboxField::create('DryRunEnabled', 'Enable dry-run?')
1136
+			)
1137
+				->setTitle('Pipeline Options')
1138
+				->setDescription(
1139
+					"Allows admins to run simulated pipelines without triggering deployments or notifications."
1140
+				),
1141
+			$deployConfig
1142
+		));
1143
+	}
1144
+
1145
+	/**
1146
+	 */
1147
+	public function onBeforeWrite()
1148
+	{
1149
+		parent::onBeforeWrite();
1150
+		if ($this->Name && $this->Name . '.rb' != $this->Filename) {
1151
+			$this->Filename = $this->Name . '.rb';
1152
+		}
1153
+		$this->checkEnvironmentPath();
1154
+		$this->writeConfigFile();
1155
+		$this->writePipelineFile();
1156
+	}
1157
+
1158
+	public function onAfterWrite()
1159
+	{
1160
+		parent::onAfterWrite();
1161
+
1162
+		if ($this->Usage == 'Production' || $this->Usage == 'UAT') {
1163
+			$conflicting = DNEnvironment::get()
1164
+				->filter('ProjectID', $this->ProjectID)
1165
+				->filter('Usage', $this->Usage)
1166
+				->exclude('ID', $this->ID);
1167
+
1168
+			foreach ($conflicting as $otherEnvironment) {
1169
+				$otherEnvironment->Usage = 'Unspecified';
1170
+				$otherEnvironment->write();
1171
+			}
1172
+		}
1173
+	}
1174
+
1175
+
1176
+	/**
1177
+	 * Ensure that environment paths are setup on the local filesystem
1178
+	 */
1179
+	protected function checkEnvironmentPath()
1180
+	{
1181
+		// Create folder if it doesn't exist
1182
+		$configDir = dirname($this->getConfigFilename());
1183
+		if (!file_exists($configDir) && $configDir) {
1184
+			mkdir($configDir, 0777, true);
1185
+		}
1186
+	}
1187
+
1188
+	/**
1189
+	 * Write the deployment config file to filesystem
1190
+	 */
1191
+	protected function writeConfigFile()
1192
+	{
1193
+		if (!$this->config()->get('allow_web_editing')) {
1194
+			return;
1195
+		}
1196
+
1197
+		// Create a basic new environment config from a template
1198
+		if (!$this->envFileExists()
1199
+			&& $this->Filename
1200
+			&& $this->CreateEnvConfig
1201
+		) {
1202
+			$templateFile = $this->config()->template_file ?: BASE_PATH . '/deploynaut/environment.template';
1203
+			file_put_contents($this->getConfigFilename(), file_get_contents($templateFile));
1204
+		} elseif ($this->envFileExists() && $this->DeployConfig) {
1205
+			file_put_contents($this->getConfigFilename(), $this->DeployConfig);
1206
+		}
1207
+	}
1208
+
1209
+	/**
1210
+	 * Write the pipeline config file to filesystem
1211
+	 */
1212
+	protected function writePipelineFile()
1213
+	{
1214
+		if (!$this->config()->get('allow_web_editing')) {
1215
+			return;
1216
+		}
1217
+		$path = $this->getPipelineFilename();
1218
+		if ($this->PipelineConfig) {
1219
+			// Update file
1220
+			file_put_contents($path, $this->PipelineConfig);
1221
+		} elseif ($this->isChanged('PipelineConfig') && file_exists($path)) {
1222
+			// Remove file if deleted
1223
+			unlink($path);
1224
+		}
1225
+	}
1226
+
1227
+	/**
1228
+	 * Delete any related config files
1229
+	 */
1230
+	public function onAfterDelete()
1231
+	{
1232
+		parent::onAfterDelete();
1233
+		// Create a basic new environment config from a template
1234
+		if ($this->config()->get('allow_web_editing') && $this->envFileExists()) {
1235
+			unlink($this->getConfigFilename());
1236
+		}
1237
+
1238
+		$create = $this->CreateEnvironment();
1239
+		if ($create && $create->exists()) {
1240
+			$create->delete();
1241
+		}
1242
+	}
1243
+
1244
+	/**
1245
+	 * @return string
1246
+	 */
1247
+	protected function getEnvironmentConfig()
1248
+	{
1249
+		if (!$this->envFileExists()) {
1250
+			return '';
1251
+		}
1252
+		return file_get_contents($this->getConfigFilename());
1253
+	}
1254
+
1255
+	/**
1256
+	 * @return boolean
1257
+	 */
1258
+	protected function envFileExists()
1259
+	{
1260
+		if (!$this->getConfigFilename()) {
1261
+			return false;
1262
+		}
1263
+		return file_exists($this->getConfigFilename());
1264
+	}
1265
+
1266
+	/**
1267
+	 * Returns the path to the ruby config file
1268
+	 *
1269
+	 * @return string
1270
+	 */
1271
+	public function getConfigFilename()
1272
+	{
1273
+		if (!$this->Project()->exists()) {
1274
+			return '';
1275
+		}
1276
+		if (!$this->Filename) {
1277
+			return '';
1278
+		}
1279
+		return $this->DNData()->getEnvironmentDir() . '/' . $this->Project()->Name . '/' . $this->Filename;
1280
+	}
1281
+
1282
+	/**
1283
+	 * Returns the path to the {@link Pipeline} configuration for this environment.
1284
+	 * Uses the same path and filename as the capistrano config, but with .yml extension.
1285
+	 *
1286
+	 * @return string
1287
+	 */
1288
+	public function getPipelineFilename()
1289
+	{
1290
+		$name = $this->getConfigFilename();
1291
+		if (!$name) {
1292
+			return null;
1293
+		}
1294
+		$path = pathinfo($name);
1295
+		if ($path) {
1296
+			return $path['dirname'] . '/' . $path['filename'] . '.yml';
1297
+		}
1298
+	}
1299
+
1300
+	/**
1301
+	 * Does this environment have a pipeline config file
1302
+	 *
1303
+	 * @return boolean
1304
+	 */
1305
+	protected function pipelineFileExists()
1306
+	{
1307
+		$filename = $this->getPipelineFilename();
1308
+		if (empty($filename)) {
1309
+			return false;
1310
+		}
1311
+		return file_exists($filename);
1312
+	}
1313
+
1314
+	/**
1315
+	 * Helper function to convert a multi-dimensional array (associative or indexed) to an {@link ArrayList} or
1316
+	 * {@link ArrayData} object structure, so that values can be used in templates.
1317
+	 *
1318
+	 * @param array $array The (single- or multi-dimensional) array to convert
1319
+	 * @return object Either an {@link ArrayList} or {@link ArrayData} object, or the original item ($array) if $array
1320
+	 * isn't an array.
1321
+	 */
1322
+	public static function array_to_viewabledata($array)
1323
+	{
1324
+		// Don't transform non-arrays
1325
+		if (!is_array($array)) {
1326
+			return $array;
1327
+		}
1328
+
1329
+		// Figure out whether this is indexed or associative
1330
+		$keys = array_keys($array);
1331
+		$assoc = ($keys != array_keys($keys));
1332
+		if ($assoc) {
1333
+			// Treat as viewable data
1334
+			$data = new ArrayData(array());
1335
+			foreach ($array as $key => $value) {
1336
+				$data->setField($key, self::array_to_viewabledata($value));
1337
+			}
1338
+			return $data;
1339
+		} else {
1340
+			// Treat this as basic non-associative list
1341
+			$list = new ArrayList();
1342
+			foreach ($array as $value) {
1343
+				$list->push(self::array_to_viewabledata($value));
1344
+			}
1345
+			return $list;
1346
+		}
1347
+	}
1348
+
1349
+
1350
+
1351
+	/**
1352
+	 * Helper function to retrieve filtered commits from an environment
1353
+	 * this environment depends on
1354
+	 *
1355
+	 * @return DataList
1356
+	 */
1357
+	public function getDependentFilteredCommits()
1358
+	{
1359
+		// check if this environment depends on another environemnt
1360
+		$dependsOnEnv = $this->DependsOnEnvironment();
1361
+		if (empty($dependsOnEnv)) {
1362
+			return null;
1363
+		}
1364
+
1365
+		// Check if there is a filter
1366
+		$config = $this->GenericPipelineConfig();
1367
+		$filter = isset($config->PipelineConfig->FilteredCommits)
1368
+			? $config->PipelineConfig->FilteredCommits
1369
+			: null;
1370
+		if (empty($filter)) {
1371
+			return null;
1372
+		}
1373
+
1374
+		// Create and execute filter
1375
+		if (!class_exists($filter)) {
1376
+			throw new Exception(sprintf("Class %s does not exist", $filter));
1377
+		}
1378
+		$commitClass = $filter::create();
1379
+		// setup the environment to check for commits
1380
+		$commitClass->env = $dependsOnEnv;
1381
+		return $commitClass->getCommits();
1382
+	}
1383
+
1384
+	/**
1385
+	 * Enable the maintenance page
1386
+	 *
1387
+	 * @param DeploynautLogFile $log
1388
+	 */
1389
+	public function enableMaintenace($log)
1390
+	{
1391
+		$this->Backend()
1392
+			->enableMaintenance($this, $log, $this->Project());
1393
+	}
1394
+
1395
+	/**
1396
+	 * Disable maintenance page
1397
+	 *
1398
+	 * @param DeploynautLogFile $log
1399
+	 */
1400
+	public function disableMaintenance($log)
1401
+	{
1402
+		$this->Backend()
1403
+			->disableMaintenance($this, $log, $this->Project());
1404
+	}
1405
+
1406
+	protected function validate()
1407
+	{
1408
+		$result = parent::validate();
1409
+		$backend = $this->Backend();
1410
+
1411
+		if (strcasecmp('test', $this->Name) === 0 && get_class($backend) == 'CapistranoDeploymentBackend') {
1412
+			$result->error('"test" is not a valid environment name when using Capistrano backend.');
1413
+		}
1414
+
1415
+		return $result;
1416
+	}
1417 1417
 }
Please login to merge, or discard this patch.
Spacing   +95 added lines, -95 removed lines patch added patch discarded remove patch
@@ -191,7 +191,7 @@  discard block
 block discarded – undo
191 191
     public function Backend()
192 192
     {
193 193
         $backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET));
194
-        switch (sizeof($backends)) {
194
+        switch(sizeof($backends)) {
195 195
         // Nothing allowed, use the default value "DeploymentBackend"
196 196
             case 0:
197 197
                 $backend = "DeploymentBackend";
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
             // Multiple choices, use our choice if it's legal, otherwise default to the first item on the list
206 206
             default:
207 207
                 $backend = $this->BackendIdentifier;
208
-                if (!in_array($backend, $backends)) {
208
+                if(!in_array($backend, $backends)) {
209 209
                     $backend = $backends[0];
210 210
                 }
211 211
         }
@@ -255,7 +255,7 @@  discard block
 block discarded – undo
255 255
     public function getBareURL()
256 256
     {
257 257
         $url = parse_url($this->URL);
258
-        if (isset($url['host'])) {
258
+        if(isset($url['host'])) {
259 259
             return strtolower($url['host']);
260 260
         }
261 261
     }
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
     public function GenericPipelineConfig()
291 291
     {
292 292
         $config = $this->loadPipelineConfig();
293
-        if ($config) {
293
+        if($config) {
294 294
             return self::array_to_viewabledata($config);
295 295
         }
296 296
     }
@@ -305,7 +305,7 @@  discard block
 block discarded – undo
305 305
         require_once 'thirdparty/spyc/spyc.php';
306 306
 
307 307
         $path = $this->getPipelineFilename();
308
-        if (file_exists($path)) {
308
+        if(file_exists($path)) {
309 309
             return Spyc::YAMLLoad($path);
310 310
         }
311 311
     }
@@ -318,7 +318,7 @@  discard block
 block discarded – undo
318 318
      */
319 319
     public function DependsOnEnvironment()
320 320
     {
321
-        if ($this->HasPipelineSupport()) {
321
+        if($this->HasPipelineSupport()) {
322 322
             $pipeline = $this->GenericPipeline();
323 323
             return $pipeline->getDependentEnvironment();
324 324
         }
@@ -351,7 +351,7 @@  discard block
 block discarded – undo
351 351
     public function CanCancelPipeline()
352 352
     {
353 353
         // do we have a current pipeline
354
-        if ($this->HasCurrentPipeline()) {
354
+        if($this->HasCurrentPipeline()) {
355 355
             return $this->CurrentPipeline()->canAbort();
356 356
         }
357 357
         return false;
@@ -365,20 +365,20 @@  discard block
 block discarded – undo
365 365
      */
366 366
     public function canView($member = null)
367 367
     {
368
-        if (!$member) {
368
+        if(!$member) {
369 369
             $member = Member::currentUser();
370 370
         }
371
-        if (!$member) {
371
+        if(!$member) {
372 372
             return false;
373 373
         }
374 374
         // Must be logged in to check permissions
375 375
 
376
-        if (Permission::checkMember($member, 'ADMIN')) {
376
+        if(Permission::checkMember($member, 'ADMIN')) {
377 377
             return true;
378 378
         }
379 379
 
380 380
         // if no Viewers or ViewerGroups defined, fallback to DNProject::canView permissions
381
-        if ($this->Viewers()->exists() || $this->ViewerGroups()->exists()) {
381
+        if($this->Viewers()->exists() || $this->ViewerGroups()->exists()) {
382 382
             return $this->Viewers()->byID($member->ID)
383 383
                 || $member->inGroups($this->ViewerGroups());
384 384
         }
@@ -394,20 +394,20 @@  discard block
 block discarded – undo
394 394
      */
395 395
     public function canDeploy($member = null)
396 396
     {
397
-        if (!$member) {
397
+        if(!$member) {
398 398
             $member = Member::currentUser();
399 399
         }
400
-        if (!$member) {
400
+        if(!$member) {
401 401
             return false;
402 402
         }
403 403
         // Must be logged in to check permissions
404 404
 
405
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
406
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_DEPLOYMENT, $member)) {
405
+        if($this->Usage === 'Production' || $this->Usage === 'Unspecified') {
406
+            if($this->Project()->allowed(DNRoot::ALLOW_PROD_DEPLOYMENT, $member)) {
407 407
                 return true;
408 408
             }
409 409
         } else {
410
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_DEPLOYMENT, $member)) {
410
+            if($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_DEPLOYMENT, $member)) {
411 411
                 return true;
412 412
             }
413 413
         }
@@ -425,20 +425,20 @@  discard block
 block discarded – undo
425 425
      */
426 426
     public function canRestore($member = null)
427 427
     {
428
-        if (!$member) {
428
+        if(!$member) {
429 429
             $member = Member::currentUser();
430 430
         }
431
-        if (!$member) {
431
+        if(!$member) {
432 432
             return false;
433 433
         }
434 434
         // Must be logged in to check permissions
435 435
 
436
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
437
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
436
+        if($this->Usage === 'Production' || $this->Usage === 'Unspecified') {
437
+            if($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
438 438
                 return true;
439 439
             }
440 440
         } else {
441
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
441
+            if($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
442 442
                 return true;
443 443
             }
444 444
         }
@@ -457,24 +457,24 @@  discard block
 block discarded – undo
457 457
     public function canBackup($member = null)
458 458
     {
459 459
         $project = $this->Project();
460
-        if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
460
+        if($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
461 461
             return false;
462 462
         }
463 463
 
464
-        if (!$member) {
464
+        if(!$member) {
465 465
             $member = Member::currentUser();
466 466
         }
467 467
         // Must be logged in to check permissions
468
-        if (!$member) {
468
+        if(!$member) {
469 469
             return false;
470 470
         }
471 471
 
472
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
473
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
472
+        if($this->Usage === 'Production' || $this->Usage === 'Unspecified') {
473
+            if($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
474 474
                 return true;
475 475
             }
476 476
         } else {
477
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
477
+            if($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
478 478
                 return true;
479 479
             }
480 480
         }
@@ -497,24 +497,24 @@  discard block
 block discarded – undo
497 497
     public function canUploadArchive($member = null)
498 498
     {
499 499
         $project = $this->Project();
500
-        if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
500
+        if($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
501 501
             return false;
502 502
         }
503 503
 
504
-        if (!$member) {
504
+        if(!$member) {
505 505
             $member = Member::currentUser();
506 506
         }
507
-        if (!$member) {
507
+        if(!$member) {
508 508
             return false;
509 509
         }
510 510
         // Must be logged in to check permissions
511 511
 
512
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
513
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
512
+        if($this->Usage === 'Production' || $this->Usage === 'Unspecified') {
513
+            if($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
514 514
                 return true;
515 515
             }
516 516
         } else {
517
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
517
+            if($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
518 518
                 return true;
519 519
             }
520 520
         }
@@ -532,20 +532,20 @@  discard block
 block discarded – undo
532 532
      */
533 533
     public function canDownloadArchive($member = null)
534 534
     {
535
-        if (!$member) {
535
+        if(!$member) {
536 536
             $member = Member::currentUser();
537 537
         }
538
-        if (!$member) {
538
+        if(!$member) {
539 539
             return false;
540 540
         }
541 541
         // Must be logged in to check permissions
542 542
 
543
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
544
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
543
+        if($this->Usage === 'Production' || $this->Usage === 'Unspecified') {
544
+            if($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
545 545
                 return true;
546 546
             }
547 547
         } else {
548
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
548
+            if($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
549 549
                 return true;
550 550
             }
551 551
         }
@@ -562,14 +562,14 @@  discard block
 block discarded – undo
562 562
      */
563 563
     public function canAbort($member = null)
564 564
     {
565
-        if (!$member) {
565
+        if(!$member) {
566 566
             $member = Member::currentUser();
567 567
         }
568
-        if (!$member) {
568
+        if(!$member) {
569 569
             return false;
570 570
         }
571 571
 
572
-        if (Permission::checkMember($member, 'ADMIN')) {
572
+        if(Permission::checkMember($member, 'ADMIN')) {
573 573
             return true;
574 574
         }
575 575
 
@@ -585,14 +585,14 @@  discard block
 block discarded – undo
585 585
      */
586 586
     public function canApprove($member = null)
587 587
     {
588
-        if (!$member) {
588
+        if(!$member) {
589 589
             $member = Member::currentUser();
590 590
         }
591
-        if (!$member) {
591
+        if(!$member) {
592 592
             return false;
593 593
         }
594 594
 
595
-        if (Permission::checkMember($member, 'ADMIN')) {
595
+        if(Permission::checkMember($member, 'ADMIN')) {
596 596
             return true;
597 597
         }
598 598
         return $this->PipelineApprovers()->byID($member->ID)
@@ -608,20 +608,20 @@  discard block
 block discarded – undo
608 608
      */
609 609
     public function canDeleteArchive($member = null)
610 610
     {
611
-        if (!$member) {
611
+        if(!$member) {
612 612
             $member = Member::currentUser();
613 613
         }
614
-        if (!$member) {
614
+        if(!$member) {
615 615
             return false;
616 616
         }
617 617
         // Must be logged in to check permissions
618 618
 
619
-        if ($this->Usage==='Production' || $this->Usage==='Unspecified') {
620
-            if ($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
619
+        if($this->Usage === 'Production' || $this->Usage === 'Unspecified') {
620
+            if($this->Project()->allowed(DNRoot::ALLOW_PROD_SNAPSHOT, $member)) {
621 621
                 return true;
622 622
             }
623 623
         } else {
624
-            if ($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
624
+            if($this->Project()->allowed(DNRoot::ALLOW_NON_PROD_SNAPSHOT, $member)) {
625 625
                 return true;
626 626
             }
627 627
         }
@@ -785,18 +785,18 @@  discard block
 block discarded – undo
785 785
             'Status' => 'Finished'
786 786
         ))->sort('LastEdited DESC')->first();
787 787
 
788
-        if (!$deploy || (!$deploy->SHA)) {
788
+        if(!$deploy || (!$deploy->SHA)) {
789 789
             return false;
790 790
         }
791 791
 
792 792
         $repo = $this->Project()->getRepository();
793
-        if (!$repo) {
793
+        if(!$repo) {
794 794
             return $deploy;
795 795
         }
796 796
 
797 797
         try {
798 798
             $commit = $repo->getCommit($deploy->SHA);
799
-            if ($commit) {
799
+            if($commit) {
800 800
                 $deploy->Message = Convert::raw2xml($commit->getMessage());
801 801
                 $deploy->Committer = Convert::raw2xml($commit->getCommitterName());
802 802
                 $deploy->CommitDate = $commit->getCommitterDate()->Format('d/m/Y g:ia');
@@ -804,7 +804,7 @@  discard block
 block discarded – undo
804 804
                 $deploy->AuthorDate = $commit->getAuthorDate()->Format('d/m/Y g:ia');
805 805
             }
806 806
             // We can't find this SHA, so we ignore adding a commit message to the deployment
807
-        } catch (Exception $ex) {
807
+        } catch(Exception $ex) {
808 808
         }
809 809
 
810 810
         return $deploy;
@@ -830,7 +830,7 @@  discard block
 block discarded – undo
830 830
     {
831 831
         try {
832 832
             $repo = $this->Project()->getRepository();
833
-            if ($repo !== false) {
833
+            if($repo !== false) {
834 834
                 $commit = new \Gitonomy\Git\Commit($repo, $sha);
835 835
                 return [
836 836
                     'AuthorName' => (string)Convert::raw2xml($commit->getAuthorName()),
@@ -840,7 +840,7 @@  discard block
 block discarded – undo
840 840
                     'Hash' => Convert::raw2xml($commit->getHash())
841 841
                 ];
842 842
             }
843
-        } catch (\Gitonomy\Git\Exception\ReferenceNotFoundException $exc) {
843
+        } catch(\Gitonomy\Git\Exception\ReferenceNotFoundException $exc) {
844 844
             SS_Log::log($exc, SS_Log::WARN);
845 845
         }
846 846
         return array(
@@ -915,12 +915,12 @@  discard block
 block discarded – undo
915 915
         $fields = new FieldList(new TabSet('Root'));
916 916
 
917 917
         $project = $this->Project();
918
-        if ($project && $project->exists()) {
918
+        if($project && $project->exists()) {
919 919
             $viewerGroups = $project->Viewers();
920 920
             $groups = $viewerGroups->sort('Title')->map()->toArray();
921 921
             $members = array();
922
-            foreach ($viewerGroups as $group) {
923
-                foreach ($group->Members()->map() as $k => $v) {
922
+            foreach($viewerGroups as $group) {
923
+                foreach($group->Members()->map() as $k => $v) {
924 924
                     $members[$k] = $v;
925 925
                 }
926 926
             }
@@ -957,7 +957,7 @@  discard block
 block discarded – undo
957 957
         // Backend identifier - pick from a named list of configurations specified in YML config
958 958
         $backends = $this->config()->get('allowed_backends', Config::FIRST_SET);
959 959
         // If there's only 1 backend, then user selection isn't needed
960
-        if (sizeof($backends) > 1) {
960
+        if(sizeof($backends) > 1) {
961 961
             $fields->addFieldToTab('Root.Main', DropdownField::create('BackendIdentifier', 'Deployment backend')
962 962
                 ->setSource($backends)
963 963
                 ->setDescription('What kind of deployment system should be used to deploy to this environment'));
@@ -1037,14 +1037,14 @@  discard block
 block discarded – undo
1037 1037
         ));
1038 1038
 
1039 1039
         // The Main.DeployConfig
1040
-        if ($this->Project()->exists()) {
1040
+        if($this->Project()->exists()) {
1041 1041
             $this->setDeployConfigurationFields($fields);
1042 1042
         }
1043 1043
 
1044 1044
         // The DataArchives
1045 1045
         $dataArchiveConfig = GridFieldConfig_RecordViewer::create();
1046 1046
         $dataArchiveConfig->removeComponentsByType('GridFieldAddNewButton');
1047
-        if (class_exists('GridFieldBulkManager')) {
1047
+        if(class_exists('GridFieldBulkManager')) {
1048 1048
             $dataArchiveConfig->addComponent(new GridFieldBulkManager());
1049 1049
         }
1050 1050
         $dataArchive = GridField::create('DataArchives', 'Data Archives', $this->DataArchives(), $dataArchiveConfig);
@@ -1054,10 +1054,10 @@  discard block
 block discarded – undo
1054 1054
         $this->setPipelineConfigurationFields($fields);
1055 1055
 
1056 1056
         // Pipelines
1057
-        if ($this->Pipelines()->Count()) {
1057
+        if($this->Pipelines()->Count()) {
1058 1058
             $pipelinesConfig = GridFieldConfig_RecordEditor::create();
1059 1059
             $pipelinesConfig->removeComponentsByType('GridFieldAddNewButton');
1060
-            if (class_exists('GridFieldBulkManager')) {
1060
+            if(class_exists('GridFieldBulkManager')) {
1061 1061
                 $pipelinesConfig->addComponent(new GridFieldBulkManager());
1062 1062
             }
1063 1063
             $pipelines = GridField::create('Pipelines', 'Pipelines', $this->Pipelines(), $pipelinesConfig);
@@ -1067,7 +1067,7 @@  discard block
 block discarded – undo
1067 1067
         // Deployments
1068 1068
         $deploymentsConfig = GridFieldConfig_RecordEditor::create();
1069 1069
         $deploymentsConfig->removeComponentsByType('GridFieldAddNewButton');
1070
-        if (class_exists('GridFieldBulkManager')) {
1070
+        if(class_exists('GridFieldBulkManager')) {
1071 1071
             $deploymentsConfig->addComponent(new GridFieldBulkManager());
1072 1072
         }
1073 1073
         $deployments = GridField::create('Deployments', 'Deployments', $this->Deployments(), $deploymentsConfig);
@@ -1092,11 +1092,11 @@  discard block
 block discarded – undo
1092 1092
      */
1093 1093
     protected function setDeployConfigurationFields(&$fields)
1094 1094
     {
1095
-        if (!$this->config()->get('allow_web_editing')) {
1095
+        if(!$this->config()->get('allow_web_editing')) {
1096 1096
             return;
1097 1097
         }
1098 1098
 
1099
-        if ($this->envFileExists()) {
1099
+        if($this->envFileExists()) {
1100 1100
             $deployConfig = new TextareaField('DeployConfig', 'Deploy config', $this->getEnvironmentConfig());
1101 1101
             $deployConfig->setRows(40);
1102 1102
             $fields->insertAfter($deployConfig, 'Filename');
@@ -1117,7 +1117,7 @@  discard block
 block discarded – undo
1117 1117
      */
1118 1118
     protected function setPipelineConfigurationFields($fields)
1119 1119
     {
1120
-        if (!$this->config()->get('allow_web_editing')) {
1120
+        if(!$this->config()->get('allow_web_editing')) {
1121 1121
             return;
1122 1122
         }
1123 1123
         $config = $this->pipelineFileExists()
@@ -1125,7 +1125,7 @@  discard block
 block discarded – undo
1125 1125
             : '';
1126 1126
         $deployConfig = new TextareaField('PipelineConfig', 'Pipeline config', $config);
1127 1127
         $deployConfig->setRows(40);
1128
-        if (!$this->pipelineFileExists()) {
1128
+        if(!$this->pipelineFileExists()) {
1129 1129
             $deployConfig->setDescription(
1130 1130
                 "No pipeline is configured for this environment. Saving content here will generate a new template."
1131 1131
             );
@@ -1147,7 +1147,7 @@  discard block
 block discarded – undo
1147 1147
     public function onBeforeWrite()
1148 1148
     {
1149 1149
         parent::onBeforeWrite();
1150
-        if ($this->Name && $this->Name . '.rb' != $this->Filename) {
1150
+        if($this->Name && $this->Name . '.rb' != $this->Filename) {
1151 1151
             $this->Filename = $this->Name . '.rb';
1152 1152
         }
1153 1153
         $this->checkEnvironmentPath();
@@ -1159,13 +1159,13 @@  discard block
 block discarded – undo
1159 1159
     {
1160 1160
         parent::onAfterWrite();
1161 1161
 
1162
-        if ($this->Usage == 'Production' || $this->Usage == 'UAT') {
1162
+        if($this->Usage == 'Production' || $this->Usage == 'UAT') {
1163 1163
             $conflicting = DNEnvironment::get()
1164 1164
                 ->filter('ProjectID', $this->ProjectID)
1165 1165
                 ->filter('Usage', $this->Usage)
1166 1166
                 ->exclude('ID', $this->ID);
1167 1167
 
1168
-            foreach ($conflicting as $otherEnvironment) {
1168
+            foreach($conflicting as $otherEnvironment) {
1169 1169
                 $otherEnvironment->Usage = 'Unspecified';
1170 1170
                 $otherEnvironment->write();
1171 1171
             }
@@ -1180,7 +1180,7 @@  discard block
 block discarded – undo
1180 1180
     {
1181 1181
         // Create folder if it doesn't exist
1182 1182
         $configDir = dirname($this->getConfigFilename());
1183
-        if (!file_exists($configDir) && $configDir) {
1183
+        if(!file_exists($configDir) && $configDir) {
1184 1184
             mkdir($configDir, 0777, true);
1185 1185
         }
1186 1186
     }
@@ -1190,18 +1190,18 @@  discard block
 block discarded – undo
1190 1190
      */
1191 1191
     protected function writeConfigFile()
1192 1192
     {
1193
-        if (!$this->config()->get('allow_web_editing')) {
1193
+        if(!$this->config()->get('allow_web_editing')) {
1194 1194
             return;
1195 1195
         }
1196 1196
 
1197 1197
         // Create a basic new environment config from a template
1198
-        if (!$this->envFileExists()
1198
+        if(!$this->envFileExists()
1199 1199
             && $this->Filename
1200 1200
             && $this->CreateEnvConfig
1201 1201
         ) {
1202 1202
             $templateFile = $this->config()->template_file ?: BASE_PATH . '/deploynaut/environment.template';
1203 1203
             file_put_contents($this->getConfigFilename(), file_get_contents($templateFile));
1204
-        } elseif ($this->envFileExists() && $this->DeployConfig) {
1204
+        } elseif($this->envFileExists() && $this->DeployConfig) {
1205 1205
             file_put_contents($this->getConfigFilename(), $this->DeployConfig);
1206 1206
         }
1207 1207
     }
@@ -1211,14 +1211,14 @@  discard block
 block discarded – undo
1211 1211
      */
1212 1212
     protected function writePipelineFile()
1213 1213
     {
1214
-        if (!$this->config()->get('allow_web_editing')) {
1214
+        if(!$this->config()->get('allow_web_editing')) {
1215 1215
             return;
1216 1216
         }
1217 1217
         $path = $this->getPipelineFilename();
1218
-        if ($this->PipelineConfig) {
1218
+        if($this->PipelineConfig) {
1219 1219
             // Update file
1220 1220
             file_put_contents($path, $this->PipelineConfig);
1221
-        } elseif ($this->isChanged('PipelineConfig') && file_exists($path)) {
1221
+        } elseif($this->isChanged('PipelineConfig') && file_exists($path)) {
1222 1222
             // Remove file if deleted
1223 1223
             unlink($path);
1224 1224
         }
@@ -1231,12 +1231,12 @@  discard block
 block discarded – undo
1231 1231
     {
1232 1232
         parent::onAfterDelete();
1233 1233
         // Create a basic new environment config from a template
1234
-        if ($this->config()->get('allow_web_editing') && $this->envFileExists()) {
1234
+        if($this->config()->get('allow_web_editing') && $this->envFileExists()) {
1235 1235
             unlink($this->getConfigFilename());
1236 1236
         }
1237 1237
 
1238 1238
         $create = $this->CreateEnvironment();
1239
-        if ($create && $create->exists()) {
1239
+        if($create && $create->exists()) {
1240 1240
             $create->delete();
1241 1241
         }
1242 1242
     }
@@ -1246,7 +1246,7 @@  discard block
 block discarded – undo
1246 1246
      */
1247 1247
     protected function getEnvironmentConfig()
1248 1248
     {
1249
-        if (!$this->envFileExists()) {
1249
+        if(!$this->envFileExists()) {
1250 1250
             return '';
1251 1251
         }
1252 1252
         return file_get_contents($this->getConfigFilename());
@@ -1257,7 +1257,7 @@  discard block
 block discarded – undo
1257 1257
      */
1258 1258
     protected function envFileExists()
1259 1259
     {
1260
-        if (!$this->getConfigFilename()) {
1260
+        if(!$this->getConfigFilename()) {
1261 1261
             return false;
1262 1262
         }
1263 1263
         return file_exists($this->getConfigFilename());
@@ -1270,10 +1270,10 @@  discard block
 block discarded – undo
1270 1270
      */
1271 1271
     public function getConfigFilename()
1272 1272
     {
1273
-        if (!$this->Project()->exists()) {
1273
+        if(!$this->Project()->exists()) {
1274 1274
             return '';
1275 1275
         }
1276
-        if (!$this->Filename) {
1276
+        if(!$this->Filename) {
1277 1277
             return '';
1278 1278
         }
1279 1279
         return $this->DNData()->getEnvironmentDir() . '/' . $this->Project()->Name . '/' . $this->Filename;
@@ -1288,11 +1288,11 @@  discard block
 block discarded – undo
1288 1288
     public function getPipelineFilename()
1289 1289
     {
1290 1290
         $name = $this->getConfigFilename();
1291
-        if (!$name) {
1291
+        if(!$name) {
1292 1292
             return null;
1293 1293
         }
1294 1294
         $path = pathinfo($name);
1295
-        if ($path) {
1295
+        if($path) {
1296 1296
             return $path['dirname'] . '/' . $path['filename'] . '.yml';
1297 1297
         }
1298 1298
     }
@@ -1305,7 +1305,7 @@  discard block
 block discarded – undo
1305 1305
     protected function pipelineFileExists()
1306 1306
     {
1307 1307
         $filename = $this->getPipelineFilename();
1308
-        if (empty($filename)) {
1308
+        if(empty($filename)) {
1309 1309
             return false;
1310 1310
         }
1311 1311
         return file_exists($filename);
@@ -1322,24 +1322,24 @@  discard block
 block discarded – undo
1322 1322
     public static function array_to_viewabledata($array)
1323 1323
     {
1324 1324
         // Don't transform non-arrays
1325
-        if (!is_array($array)) {
1325
+        if(!is_array($array)) {
1326 1326
             return $array;
1327 1327
         }
1328 1328
 
1329 1329
         // Figure out whether this is indexed or associative
1330 1330
         $keys = array_keys($array);
1331 1331
         $assoc = ($keys != array_keys($keys));
1332
-        if ($assoc) {
1332
+        if($assoc) {
1333 1333
             // Treat as viewable data
1334 1334
             $data = new ArrayData(array());
1335
-            foreach ($array as $key => $value) {
1335
+            foreach($array as $key => $value) {
1336 1336
                 $data->setField($key, self::array_to_viewabledata($value));
1337 1337
             }
1338 1338
             return $data;
1339 1339
         } else {
1340 1340
             // Treat this as basic non-associative list
1341 1341
             $list = new ArrayList();
1342
-            foreach ($array as $value) {
1342
+            foreach($array as $value) {
1343 1343
                 $list->push(self::array_to_viewabledata($value));
1344 1344
             }
1345 1345
             return $list;
@@ -1358,7 +1358,7 @@  discard block
 block discarded – undo
1358 1358
     {
1359 1359
         // check if this environment depends on another environemnt
1360 1360
         $dependsOnEnv = $this->DependsOnEnvironment();
1361
-        if (empty($dependsOnEnv)) {
1361
+        if(empty($dependsOnEnv)) {
1362 1362
             return null;
1363 1363
         }
1364 1364
 
@@ -1367,12 +1367,12 @@  discard block
 block discarded – undo
1367 1367
         $filter = isset($config->PipelineConfig->FilteredCommits)
1368 1368
             ? $config->PipelineConfig->FilteredCommits
1369 1369
             : null;
1370
-        if (empty($filter)) {
1370
+        if(empty($filter)) {
1371 1371
             return null;
1372 1372
         }
1373 1373
 
1374 1374
         // Create and execute filter
1375
-        if (!class_exists($filter)) {
1375
+        if(!class_exists($filter)) {
1376 1376
             throw new Exception(sprintf("Class %s does not exist", $filter));
1377 1377
         }
1378 1378
         $commitClass = $filter::create();
@@ -1408,7 +1408,7 @@  discard block
 block discarded – undo
1408 1408
         $result = parent::validate();
1409 1409
         $backend = $this->Backend();
1410 1410
 
1411
-        if (strcasecmp('test', $this->Name) === 0 && get_class($backend) == 'CapistranoDeploymentBackend') {
1411
+        if(strcasecmp('test', $this->Name) === 0 && get_class($backend) == 'CapistranoDeploymentBackend') {
1412 1412
             $result->error('"test" is not a valid environment name when using Capistrano backend.');
1413 1413
         }
1414 1414
 
Please login to merge, or discard this patch.
Braces   +59 added lines, -118 removed lines patch added patch discarded remove patch
@@ -39,8 +39,7 @@  discard block
 block discarded – undo
39 39
  * @method ManyManyList PipelineCancellers()
40 40
  * @method ManyManyList PipelineCancellerGroups()
41 41
  */
42
-class DNEnvironment extends DataObject
43
-{
42
+class DNEnvironment extends DataObject {
44 43
 
45 44
     /**
46 45
      * If this is set to a full pathfile, it will be used as template
@@ -168,8 +167,7 @@  discard block
 block discarded – undo
168 167
      * @param string $path
169 168
      * @return \DNEnvironment
170 169
      */
171
-    public static function create_from_path($path)
172
-    {
170
+    public static function create_from_path($path) {
173 171
         $e = DNEnvironment::create();
174 172
         $e->Filename = $path;
175 173
         $e->Name = basename($e->Filename, '.rb');
@@ -188,8 +186,7 @@  discard block
 block discarded – undo
188 186
      *
189 187
      * @return DeploymentBackend
190 188
      */
191
-    public function Backend()
192
-    {
189
+    public function Backend() {
193 190
         $backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET));
194 191
         switch (sizeof($backends)) {
195 192
         // Nothing allowed, use the default value "DeploymentBackend"
@@ -213,8 +210,7 @@  discard block
 block discarded – undo
213 210
         return Injector::inst()->get($backend);
214 211
     }
215 212
 
216
-    public function Menu()
217
-    {
213
+    public function Menu() {
218 214
         $list = new ArrayList();
219 215
 
220 216
         $controller = Controller::curr();
@@ -236,8 +232,7 @@  discard block
 block discarded – undo
236 232
      * Return the current object from $this->Menu()
237 233
      * Good for making titles and things
238 234
      */
239
-    public function CurrentMenu()
240
-    {
235
+    public function CurrentMenu() {
241 236
         return $this->Menu()->filter('IsSection', true)->First();
242 237
     }
243 238
 
@@ -247,13 +242,11 @@  discard block
 block discarded – undo
247 242
      * @param string $separator The string used when concatenating project with env name
248 243
      * @return string
249 244
      */
250
-    public function getFullName($separator = ':')
251
-    {
245
+    public function getFullName($separator = ':') {
252 246
         return sprintf('%s%s%s', $this->Project()->Name, $separator, $this->Name);
253 247
     }
254 248
 
255
-    public function getBareURL()
256
-    {
249
+    public function getBareURL() {
257 250
         $url = parse_url($this->URL);
258 251
         if (isset($url['host'])) {
259 252
             return strtolower($url['host']);
@@ -263,8 +256,7 @@  discard block
 block discarded – undo
263 256
     /**
264 257
      * @return boolean true if there is a pipeline for the current environment.
265 258
      */
266
-    public function HasPipelineSupport()
267
-    {
259
+    public function HasPipelineSupport() {
268 260
         $config = $this->GenericPipelineConfig();
269 261
         return $config instanceof ArrayData && isset($config->Steps);
270 262
     }
@@ -275,8 +267,7 @@  discard block
 block discarded – undo
275 267
      *
276 268
      * @return Pipeline
277 269
      */
278
-    public function GenericPipeline()
279
-    {
270
+    public function GenericPipeline() {
280 271
         $pipeline = Pipeline::create();
281 272
         $pipeline->EnvironmentID = $this->ID;
282 273
         return $pipeline;
@@ -287,8 +278,7 @@  discard block
 block discarded – undo
287 278
      *
288 279
      * @return ArrayData
289 280
      */
290
-    public function GenericPipelineConfig()
291
-    {
281
+    public function GenericPipelineConfig() {
292 282
         $config = $this->loadPipelineConfig();
293 283
         if ($config) {
294 284
             return self::array_to_viewabledata($config);
@@ -300,8 +290,7 @@  discard block
 block discarded – undo
300 290
      *
301 291
      * @return array
302 292
      */
303
-    public function loadPipelineConfig()
304
-    {
293
+    public function loadPipelineConfig() {
305 294
         require_once 'thirdparty/spyc/spyc.php';
306 295
 
307 296
         $path = $this->getPipelineFilename();
@@ -316,8 +305,7 @@  discard block
 block discarded – undo
316 305
      * `DependsOnEnvironment` - these are used together to find the {@link DNEnvironment} that this environment should
317 306
      * rely on.
318 307
      */
319
-    public function DependsOnEnvironment()
320
-    {
308
+    public function DependsOnEnvironment() {
321 309
         if ($this->HasPipelineSupport()) {
322 310
             $pipeline = $this->GenericPipeline();
323 311
             return $pipeline->getDependentEnvironment();
@@ -329,8 +317,7 @@  discard block
 block discarded – undo
329 317
     /**
330 318
      * @return bool true if there is a currently running Pipeline, and false if there isn't
331 319
      */
332
-    public function HasCurrentPipeline()
333
-    {
320
+    public function HasCurrentPipeline() {
334 321
         return $this->CurrentPipeline() && $this->CurrentPipeline()->isInDB();
335 322
     }
336 323
 
@@ -340,16 +327,14 @@  discard block
 block discarded – undo
340 327
      *
341 328
      * @return DataObject|null The currently running pipeline, or null if there isn't any.
342 329
      */
343
-    public function CurrentPipeline()
344
-    {
330
+    public function CurrentPipeline() {
345 331
         return $this->Pipelines()->filter('Status', array('Running', 'Rollback'))->first();
346 332
     }
347 333
 
348 334
     /**
349 335
      * @return bool true if the current user can cancel a running pipeline
350 336
      */
351
-    public function CanCancelPipeline()
352
-    {
337
+    public function CanCancelPipeline() {
353 338
         // do we have a current pipeline
354 339
         if ($this->HasCurrentPipeline()) {
355 340
             return $this->CurrentPipeline()->canAbort();
@@ -363,8 +348,7 @@  discard block
 block discarded – undo
363 348
      * @param Member|null $member
364 349
      * @return boolean
365 350
      */
366
-    public function canView($member = null)
367
-    {
351
+    public function canView($member = null) {
368 352
         if (!$member) {
369 353
             $member = Member::currentUser();
370 354
         }
@@ -392,8 +376,7 @@  discard block
 block discarded – undo
392 376
      * @param Member|null $member
393 377
      * @return boolean
394 378
      */
395
-    public function canDeploy($member = null)
396
-    {
379
+    public function canDeploy($member = null) {
397 380
         if (!$member) {
398 381
             $member = Member::currentUser();
399 382
         }
@@ -423,8 +406,7 @@  discard block
 block discarded – undo
423 406
      * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
424 407
      * @return boolean true if $member can restore, and false if they can't.
425 408
      */
426
-    public function canRestore($member = null)
427
-    {
409
+    public function canRestore($member = null) {
428 410
         if (!$member) {
429 411
             $member = Member::currentUser();
430 412
         }
@@ -454,8 +436,7 @@  discard block
 block discarded – undo
454 436
      * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
455 437
      * @return boolean true if $member can backup, and false if they can't.
456 438
      */
457
-    public function canBackup($member = null)
458
-    {
439
+    public function canBackup($member = null) {
459 440
         $project = $this->Project();
460 441
         if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
461 442
             return false;
@@ -494,8 +475,7 @@  discard block
 block discarded – undo
494 475
      * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
495 476
      * @return boolean true if $member can upload archives linked to this environment, false if they can't.
496 477
      */
497
-    public function canUploadArchive($member = null)
498
-    {
478
+    public function canUploadArchive($member = null) {
499 479
         $project = $this->Project();
500 480
         if ($project->HasDiskQuota() && $project->HasExceededDiskQuota()) {
501 481
             return false;
@@ -530,8 +510,7 @@  discard block
 block discarded – undo
530 510
      * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
531 511
      * @return boolean true if $member can download archives from this environment, false if they can't.
532 512
      */
533
-    public function canDownloadArchive($member = null)
534
-    {
513
+    public function canDownloadArchive($member = null) {
535 514
         if (!$member) {
536 515
             $member = Member::currentUser();
537 516
         }
@@ -560,8 +539,7 @@  discard block
 block discarded – undo
560 539
      * @param Member|null $member
561 540
      * @return boolean
562 541
      */
563
-    public function canAbort($member = null)
564
-    {
542
+    public function canAbort($member = null) {
565 543
         if (!$member) {
566 544
             $member = Member::currentUser();
567 545
         }
@@ -583,8 +561,7 @@  discard block
 block discarded – undo
583 561
      * @param Member|null $member
584 562
      * @return boolean
585 563
      */
586
-    public function canApprove($member = null)
587
-    {
564
+    public function canApprove($member = null) {
588 565
         if (!$member) {
589 566
             $member = Member::currentUser();
590 567
         }
@@ -606,8 +583,7 @@  discard block
 block discarded – undo
606 583
      * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
607 584
      * @return boolean true if $member can delete archives from this environment, false if they can't.
608 585
      */
609
-    public function canDeleteArchive($member = null)
610
-    {
586
+    public function canDeleteArchive($member = null) {
611 587
         if (!$member) {
612 588
             $member = Member::currentUser();
613 589
         }
@@ -635,8 +611,7 @@  discard block
 block discarded – undo
635 611
      *
636 612
      * @return string
637 613
      */
638
-    public function getDeployersList()
639
-    {
614
+    public function getDeployersList() {
640 615
         return implode(
641 616
             ", ",
642 617
             array_merge(
@@ -651,8 +626,7 @@  discard block
 block discarded – undo
651 626
      *
652 627
      * @return string
653 628
      */
654
-    public function getCanRestoreMembersList()
655
-    {
629
+    public function getCanRestoreMembersList() {
656 630
         return implode(
657 631
             ", ",
658 632
             array_merge(
@@ -667,8 +641,7 @@  discard block
 block discarded – undo
667 641
      *
668 642
      * @return string
669 643
      */
670
-    public function getCanBackupMembersList()
671
-    {
644
+    public function getCanBackupMembersList() {
672 645
         return implode(
673 646
             ", ",
674 647
             array_merge(
@@ -684,8 +657,7 @@  discard block
 block discarded – undo
684 657
      *
685 658
      * @return string
686 659
      */
687
-    public function getArchiveUploadersList()
688
-    {
660
+    public function getArchiveUploadersList() {
689 661
         return implode(
690 662
             ", ",
691 663
             array_merge(
@@ -700,8 +672,7 @@  discard block
 block discarded – undo
700 672
      *
701 673
      * @return string
702 674
      */
703
-    public function getArchiveDownloadersList()
704
-    {
675
+    public function getArchiveDownloadersList() {
705 676
         return implode(
706 677
             ", ",
707 678
             array_merge(
@@ -716,8 +687,7 @@  discard block
 block discarded – undo
716 687
      *
717 688
      * @return string
718 689
      */
719
-    public function getArchiveDeletersList()
720
-    {
690
+    public function getArchiveDeletersList() {
721 691
         return implode(
722 692
             ", ",
723 693
             array_merge(
@@ -732,8 +702,7 @@  discard block
 block discarded – undo
732 702
      *
733 703
      * @return string
734 704
      */
735
-    public function getPipelineApproversList()
736
-    {
705
+    public function getPipelineApproversList() {
737 706
         return implode(
738 707
             ", ",
739 708
             array_merge(
@@ -748,8 +717,7 @@  discard block
 block discarded – undo
748 717
      *
749 718
      * @return string
750 719
      */
751
-    public function getPipelineCancellersList()
752
-    {
720
+    public function getPipelineCancellersList() {
753 721
         return implode(
754 722
             ", ",
755 723
             array_merge(
@@ -762,8 +730,7 @@  discard block
 block discarded – undo
762 730
     /**
763 731
      * @return DNData
764 732
      */
765
-    public function DNData()
766
-    {
733
+    public function DNData() {
767 734
         return DNData::inst();
768 735
     }
769 736
 
@@ -775,8 +742,7 @@  discard block
 block discarded – undo
775 742
      *
776 743
      * @return false|DNDeployment
777 744
      */
778
-    public function CurrentBuild()
779
-    {
745
+    public function CurrentBuild() {
780 746
         // The DeployHistory function is far too slow to use for this
781 747
 
782 748
         /** @var DNDeployment $deploy */
@@ -815,8 +781,7 @@  discard block
 block discarded – undo
815 781
      *
816 782
      * @return ArrayList
817 783
      */
818
-    public function DeployHistory()
819
-    {
784
+    public function DeployHistory() {
820 785
         return $this->Deployments()
821 786
             ->where('SHA IS NOT NULL')
822 787
             ->sort('LastEdited DESC');
@@ -826,8 +791,7 @@  discard block
 block discarded – undo
826 791
      * @param string $sha
827 792
      * @return array
828 793
      */
829
-    protected function getCommitData($sha)
830
-    {
794
+    protected function getCommitData($sha) {
831 795
         try {
832 796
             $repo = $this->Project()->getRepository();
833 797
             if ($repo !== false) {
@@ -855,8 +819,7 @@  discard block
 block discarded – undo
855 819
     /**
856 820
      * @return string
857 821
      */
858
-    public function Link()
859
-    {
822
+    public function Link() {
860 823
         return $this->Project()->Link() . "/environment/" . $this->Name;
861 824
     }
862 825
 
@@ -864,8 +827,7 @@  discard block
 block discarded – undo
864 827
      * Is this environment currently at the root level of the controller that handles it?
865 828
      * @return bool
866 829
      */
867
-    public function isCurrent()
868
-    {
830
+    public function isCurrent() {
869 831
         return $this->isSection() && Controller::curr()->getAction() == 'environment';
870 832
     }
871 833
 
@@ -873,8 +835,7 @@  discard block
 block discarded – undo
873 835
      * Is this environment currently in a controller that is handling it or performing a sub-task?
874 836
      * @return bool
875 837
      */
876
-    public function isSection()
877
-    {
838
+    public function isSection() {
878 839
         $controller = Controller::curr();
879 840
         $environment = $controller->getField('CurrentEnvironment');
880 841
         return $environment && $environment->ID == $this->ID;
@@ -890,8 +851,7 @@  discard block
 block discarded – undo
890 851
      * @param array $members List of members
891 852
      * @return FieldGroup
892 853
      */
893
-    protected function buildPermissionField($groupField, $memberField, $groups, $members)
894
-    {
854
+    protected function buildPermissionField($groupField, $memberField, $groups, $members) {
895 855
         return FieldGroup::create(
896 856
             ListboxField::create($groupField, false, $groups)
897 857
                 ->setMultiple(true)
@@ -910,8 +870,7 @@  discard block
 block discarded – undo
910 870
     /**
911 871
      * @return FieldList
912 872
      */
913
-    public function getCMSFields()
914
-    {
873
+    public function getCMSFields() {
915 874
         $fields = new FieldList(new TabSet('Root'));
916 875
 
917 876
         $project = $this->Project();
@@ -1090,8 +1049,7 @@  discard block
 block discarded – undo
1090 1049
     /**
1091 1050
      * @param FieldList $fields
1092 1051
      */
1093
-    protected function setDeployConfigurationFields(&$fields)
1094
-    {
1052
+    protected function setDeployConfigurationFields(&$fields) {
1095 1053
         if (!$this->config()->get('allow_web_editing')) {
1096 1054
             return;
1097 1055
         }
@@ -1115,8 +1073,7 @@  discard block
 block discarded – undo
1115 1073
     /**
1116 1074
      * @param FieldList $fields
1117 1075
      */
1118
-    protected function setPipelineConfigurationFields($fields)
1119
-    {
1076
+    protected function setPipelineConfigurationFields($fields) {
1120 1077
         if (!$this->config()->get('allow_web_editing')) {
1121 1078
             return;
1122 1079
         }
@@ -1144,8 +1101,7 @@  discard block
 block discarded – undo
1144 1101
 
1145 1102
     /**
1146 1103
      */
1147
-    public function onBeforeWrite()
1148
-    {
1104
+    public function onBeforeWrite() {
1149 1105
         parent::onBeforeWrite();
1150 1106
         if ($this->Name && $this->Name . '.rb' != $this->Filename) {
1151 1107
             $this->Filename = $this->Name . '.rb';
@@ -1155,8 +1111,7 @@  discard block
 block discarded – undo
1155 1111
         $this->writePipelineFile();
1156 1112
     }
1157 1113
 
1158
-    public function onAfterWrite()
1159
-    {
1114
+    public function onAfterWrite() {
1160 1115
         parent::onAfterWrite();
1161 1116
 
1162 1117
         if ($this->Usage == 'Production' || $this->Usage == 'UAT') {
@@ -1176,8 +1131,7 @@  discard block
 block discarded – undo
1176 1131
     /**
1177 1132
      * Ensure that environment paths are setup on the local filesystem
1178 1133
      */
1179
-    protected function checkEnvironmentPath()
1180
-    {
1134
+    protected function checkEnvironmentPath() {
1181 1135
         // Create folder if it doesn't exist
1182 1136
         $configDir = dirname($this->getConfigFilename());
1183 1137
         if (!file_exists($configDir) && $configDir) {
@@ -1188,8 +1142,7 @@  discard block
 block discarded – undo
1188 1142
     /**
1189 1143
      * Write the deployment config file to filesystem
1190 1144
      */
1191
-    protected function writeConfigFile()
1192
-    {
1145
+    protected function writeConfigFile() {
1193 1146
         if (!$this->config()->get('allow_web_editing')) {
1194 1147
             return;
1195 1148
         }
@@ -1209,8 +1162,7 @@  discard block
 block discarded – undo
1209 1162
     /**
1210 1163
      * Write the pipeline config file to filesystem
1211 1164
      */
1212
-    protected function writePipelineFile()
1213
-    {
1165
+    protected function writePipelineFile() {
1214 1166
         if (!$this->config()->get('allow_web_editing')) {
1215 1167
             return;
1216 1168
         }
@@ -1227,8 +1179,7 @@  discard block
 block discarded – undo
1227 1179
     /**
1228 1180
      * Delete any related config files
1229 1181
      */
1230
-    public function onAfterDelete()
1231
-    {
1182
+    public function onAfterDelete() {
1232 1183
         parent::onAfterDelete();
1233 1184
         // Create a basic new environment config from a template
1234 1185
         if ($this->config()->get('allow_web_editing') && $this->envFileExists()) {
@@ -1244,8 +1195,7 @@  discard block
 block discarded – undo
1244 1195
     /**
1245 1196
      * @return string
1246 1197
      */
1247
-    protected function getEnvironmentConfig()
1248
-    {
1198
+    protected function getEnvironmentConfig() {
1249 1199
         if (!$this->envFileExists()) {
1250 1200
             return '';
1251 1201
         }
@@ -1255,8 +1205,7 @@  discard block
 block discarded – undo
1255 1205
     /**
1256 1206
      * @return boolean
1257 1207
      */
1258
-    protected function envFileExists()
1259
-    {
1208
+    protected function envFileExists() {
1260 1209
         if (!$this->getConfigFilename()) {
1261 1210
             return false;
1262 1211
         }
@@ -1268,8 +1217,7 @@  discard block
 block discarded – undo
1268 1217
      *
1269 1218
      * @return string
1270 1219
      */
1271
-    public function getConfigFilename()
1272
-    {
1220
+    public function getConfigFilename() {
1273 1221
         if (!$this->Project()->exists()) {
1274 1222
             return '';
1275 1223
         }
@@ -1285,8 +1233,7 @@  discard block
 block discarded – undo
1285 1233
      *
1286 1234
      * @return string
1287 1235
      */
1288
-    public function getPipelineFilename()
1289
-    {
1236
+    public function getPipelineFilename() {
1290 1237
         $name = $this->getConfigFilename();
1291 1238
         if (!$name) {
1292 1239
             return null;
@@ -1302,8 +1249,7 @@  discard block
 block discarded – undo
1302 1249
      *
1303 1250
      * @return boolean
1304 1251
      */
1305
-    protected function pipelineFileExists()
1306
-    {
1252
+    protected function pipelineFileExists() {
1307 1253
         $filename = $this->getPipelineFilename();
1308 1254
         if (empty($filename)) {
1309 1255
             return false;
@@ -1319,8 +1265,7 @@  discard block
 block discarded – undo
1319 1265
      * @return object Either an {@link ArrayList} or {@link ArrayData} object, or the original item ($array) if $array
1320 1266
      * isn't an array.
1321 1267
      */
1322
-    public static function array_to_viewabledata($array)
1323
-    {
1268
+    public static function array_to_viewabledata($array) {
1324 1269
         // Don't transform non-arrays
1325 1270
         if (!is_array($array)) {
1326 1271
             return $array;
@@ -1354,8 +1299,7 @@  discard block
 block discarded – undo
1354 1299
      *
1355 1300
      * @return DataList
1356 1301
      */
1357
-    public function getDependentFilteredCommits()
1358
-    {
1302
+    public function getDependentFilteredCommits() {
1359 1303
         // check if this environment depends on another environemnt
1360 1304
         $dependsOnEnv = $this->DependsOnEnvironment();
1361 1305
         if (empty($dependsOnEnv)) {
@@ -1386,8 +1330,7 @@  discard block
 block discarded – undo
1386 1330
      *
1387 1331
      * @param DeploynautLogFile $log
1388 1332
      */
1389
-    public function enableMaintenace($log)
1390
-    {
1333
+    public function enableMaintenace($log) {
1391 1334
         $this->Backend()
1392 1335
             ->enableMaintenance($this, $log, $this->Project());
1393 1336
     }
@@ -1397,14 +1340,12 @@  discard block
 block discarded – undo
1397 1340
      *
1398 1341
      * @param DeploynautLogFile $log
1399 1342
      */
1400
-    public function disableMaintenance($log)
1401
-    {
1343
+    public function disableMaintenance($log) {
1402 1344
         $this->Backend()
1403 1345
             ->disableMaintenance($this, $log, $this->Project());
1404 1346
     }
1405 1347
 
1406
-    protected function validate()
1407
-    {
1348
+    protected function validate() {
1408 1349
         $result = parent::validate();
1409 1350
         $backend = $this->Backend();
1410 1351
 
Please login to merge, or discard this patch.
code/model/DNFailedCommits.php 2 patches
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -9,11 +9,11 @@
 block discarded – undo
9 9
 class DNFailedCommits extends DNFilteredCommits
10 10
 {
11 11
 
12
-    private $description = 'Commits that have failed in the past';
12
+	private $description = 'Commits that have failed in the past';
13 13
 
14
-    public function __construct()
15
-    {
16
-        parent::__construct();
17
-        $this->setFilter('Failed');
18
-    }
14
+	public function __construct()
15
+	{
16
+		parent::__construct();
17
+		$this->setFilter('Failed');
18
+	}
19 19
 }
Please login to merge, or discard this patch.
Braces   +2 added lines, -4 removed lines patch added patch discarded remove patch
@@ -6,13 +6,11 @@
 block discarded – undo
6 6
  *  Class to represent failed commits
7 7
  *
8 8
  */
9
-class DNFailedCommits extends DNFilteredCommits
10
-{
9
+class DNFailedCommits extends DNFilteredCommits {
11 10
 
12 11
     private $description = 'Commits that have failed in the past';
13 12
 
14
-    public function __construct()
15
-    {
13
+    public function __construct() {
16 14
         parent::__construct();
17 15
         $this->setFilter('Failed');
18 16
     }
Please login to merge, or discard this patch.
code/model/DNFilteredCommits.php 3 patches
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -30,43 +30,43 @@
 block discarded – undo
30 30
 class DNFilteredCommits extends Object
31 31
 {
32 32
 
33
-    public $env;
33
+	public $env;
34 34
 
35
-    private $filter;
35
+	private $filter;
36 36
 
37
-    private $environmentExceptionError = 'Environment has not been set';
37
+	private $environmentExceptionError = 'Environment has not been set';
38 38
 
39
-    // default sort to be by the latest commit
40
-    protected $sort = 'DESC';
39
+	// default sort to be by the latest commit
40
+	protected $sort = 'DESC';
41 41
 
42
-    public function setFilter($filter = 'Finished')
43
-    {
44
-        $this->filter = $filter;
45
-    }
42
+	public function setFilter($filter = 'Finished')
43
+	{
44
+		$this->filter = $filter;
45
+	}
46 46
 
47
-    public function getCommits()
48
-    {
49
-        if (!isset($this->env)) {
50
-            $this->environmentNotSetException();
51
-        }
52
-        $successfulCommits =
53
-            $this->env->DeployHistory()->filter(
54
-                array('Status' => $this->filter, 'EnvironmentID' => $this->env->ID)
55
-            )->sort('Created', $this->sort);
56
-        return $successfulCommits;
57
-    }
47
+	public function getCommits()
48
+	{
49
+		if (!isset($this->env)) {
50
+			$this->environmentNotSetException();
51
+		}
52
+		$successfulCommits =
53
+			$this->env->DeployHistory()->filter(
54
+				array('Status' => $this->filter, 'EnvironmentID' => $this->env->ID)
55
+			)->sort('Created', $this->sort);
56
+		return $successfulCommits;
57
+	}
58 58
 
59
-    public function getLatestCommit()
60
-    {
61
-        if (!isset($this->env)) {
62
-            $this->environmentNotSetException();
63
-        }
64
-        $commits = $this->getCommits();
65
-        return $commits->first();
66
-    }
59
+	public function getLatestCommit()
60
+	{
61
+		if (!isset($this->env)) {
62
+			$this->environmentNotSetException();
63
+		}
64
+		$commits = $this->getCommits();
65
+		return $commits->first();
66
+	}
67 67
 
68
-    private function environmentNotSetException()
69
-    {
70
-        throw new Exception($this->environmentExceptionError);
71
-    }
68
+	private function environmentNotSetException()
69
+	{
70
+		throw new Exception($this->environmentExceptionError);
71
+	}
72 72
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -46,7 +46,7 @@  discard block
 block discarded – undo
46 46
 
47 47
     public function getCommits()
48 48
     {
49
-        if (!isset($this->env)) {
49
+        if(!isset($this->env)) {
50 50
             $this->environmentNotSetException();
51 51
         }
52 52
         $successfulCommits =
@@ -58,7 +58,7 @@  discard block
 block discarded – undo
58 58
 
59 59
     public function getLatestCommit()
60 60
     {
61
-        if (!isset($this->env)) {
61
+        if(!isset($this->env)) {
62 62
             $this->environmentNotSetException();
63 63
         }
64 64
         $commits = $this->getCommits();
Please login to merge, or discard this patch.
Braces   +5 added lines, -10 removed lines patch added patch discarded remove patch
@@ -27,8 +27,7 @@  discard block
 block discarded – undo
27 27
  *    PerformTestOn: 'ThisEnvironment'
28 28
  *
29 29
  */
30
-class DNFilteredCommits extends Object
31
-{
30
+class DNFilteredCommits extends Object {
32 31
 
33 32
     public $env;
34 33
 
@@ -39,13 +38,11 @@  discard block
 block discarded – undo
39 38
     // default sort to be by the latest commit
40 39
     protected $sort = 'DESC';
41 40
 
42
-    public function setFilter($filter = 'Finished')
43
-    {
41
+    public function setFilter($filter = 'Finished') {
44 42
         $this->filter = $filter;
45 43
     }
46 44
 
47
-    public function getCommits()
48
-    {
45
+    public function getCommits() {
49 46
         if (!isset($this->env)) {
50 47
             $this->environmentNotSetException();
51 48
         }
@@ -56,8 +53,7 @@  discard block
 block discarded – undo
56 53
         return $successfulCommits;
57 54
     }
58 55
 
59
-    public function getLatestCommit()
60
-    {
56
+    public function getLatestCommit() {
61 57
         if (!isset($this->env)) {
62 58
             $this->environmentNotSetException();
63 59
         }
@@ -65,8 +61,7 @@  discard block
 block discarded – undo
65 61
         return $commits->first();
66 62
     }
67 63
 
68
-    private function environmentNotSetException()
69
-    {
64
+    private function environmentNotSetException() {
70 65
         throw new Exception($this->environmentExceptionError);
71 66
     }
72 67
 }
Please login to merge, or discard this patch.
code/model/DNFinishedCommits.php 2 patches
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -8,14 +8,14 @@
 block discarded – undo
8 8
  */
9 9
 class DNFinishedCommits extends DNFilteredCommits
10 10
 {
11
-    /**
12
-     * @var string
13
-     */
14
-    private $description = 'Commits successfully deployed in the past';
11
+	/**
12
+	 * @var string
13
+	 */
14
+	private $description = 'Commits successfully deployed in the past';
15 15
 
16
-    public function __construct()
17
-    {
18
-        parent::__construct();
19
-        $this->setFilter('Finished');
20
-    }
16
+	public function __construct()
17
+	{
18
+		parent::__construct();
19
+		$this->setFilter('Finished');
20
+	}
21 21
 }
Please login to merge, or discard this patch.
Braces   +2 added lines, -4 removed lines patch added patch discarded remove patch
@@ -6,15 +6,13 @@
 block discarded – undo
6 6
  *  Class to represent finished commits
7 7
  *
8 8
  */
9
-class DNFinishedCommits extends DNFilteredCommits
10
-{
9
+class DNFinishedCommits extends DNFilteredCommits {
11 10
     /**
12 11
      * @var string
13 12
      */
14 13
     private $description = 'Commits successfully deployed in the past';
15 14
 
16
-    public function __construct()
17
-    {
15
+    public function __construct() {
18 16
         parent::__construct();
19 17
         $this->setFilter('Finished');
20 18
     }
Please login to merge, or discard this patch.
code/model/DNProject.php 3 patches
Indentation   +1157 added lines, -1157 removed lines patch added patch discarded remove patch
@@ -15,887 +15,887 @@  discard block
 block discarded – undo
15 15
 class DNProject extends DataObject
16 16
 {
17 17
 
18
-    /**
19
-     * @var array
20
-     */
21
-    public static $db = array(
22
-        "Name" => "Varchar",
23
-        "CVSPath" => "Varchar(255)",
24
-        "DiskQuotaMB" => "Int",
25
-        "AllowedEnvironmentType" => "Varchar(255)",
26
-    );
27
-
28
-    /**
29
-     * @var array
30
-     */
31
-    public static $has_many = array(
32
-        "Environments" => "DNEnvironment",
33
-        "CreateEnvironments" => "DNCreateEnvironment"
34
-    );
35
-
36
-    /**
37
-     * @var array
38
-     */
39
-    public static $many_many = array(
40
-        "Viewers" => "Group",
41
-        'StarredBy' => "Member"
42
-    );
43
-
44
-    /**
45
-     * @var array
46
-     */
47
-    public static $summary_fields = array(
48
-        "Name",
49
-        "ViewersList",
50
-    );
51
-
52
-    /**
53
-     * @var array
54
-     */
55
-    public static $searchable_fields = array(
56
-        "Name",
57
-    );
58
-
59
-    /**
60
-     * @var string
61
-     */
62
-    private static $singular_name = 'Project';
63
-
64
-    /**
65
-     * @var string
66
-     */
67
-    private static $plural_name = 'Projects';
68
-
69
-    /**
70
-     * @var string
71
-     */
72
-    private static $default_sort = 'Name';
73
-
74
-    /**
75
-     * Display the repository URL on the project page.
76
-     *
77
-     * @var bool
78
-     */
79
-    private static $show_repository_url = false;
80
-
81
-    /**
82
-     * In-memory cache for currentBuilds per environment since fetching them from
83
-     * disk is pretty resource hungry.
84
-     *
85
-     * @var array
86
-     */
87
-    protected static $relation_cache = array();
88
-
89
-    /**
90
-     * In-memory cache to determine whether clone repo was called.
91
-     * @var array
92
-     */
93
-    private static $has_cloned_cache = array();
94
-
95
-    /**
96
-     * @var bool|Member
97
-     */
98
-    protected static $_current_member_cache = null;
99
-
100
-    /**
101
-     * Used by the sync task
102
-     *
103
-     * @param string $path
104
-     * @return \DNProject
105
-     */
106
-    public static function create_from_path($path)
107
-    {
108
-        $project = DNProject::create();
109
-        $project->Name = $path;
110
-        $project->write();
111
-
112
-        // add the administrators group as the viewers of the new project
113
-        $adminGroup = Group::get()->filter('Code', 'administrators')->first();
114
-        if ($adminGroup && $adminGroup->exists()) {
115
-            $project->Viewers()->add($adminGroup);
116
-        }
117
-        return $project;
118
-    }
119
-
120
-    /**
121
-     * Return the used quota in MB.
122
-     *
123
-     * @param int $round Number of decimal places to round to
124
-     * @return double The used quota size in MB
125
-     */
126
-    public function getUsedQuotaMB($round = 2)
127
-    {
128
-        $size = 0;
129
-
130
-        foreach ($this->Environments() as $environment) {
131
-            foreach ($environment->DataArchives()->filter('IsBackup', 0) as $archive) {
132
-                $size += $archive->ArchiveFile()->getAbsoluteSize();
133
-            }
134
-        }
135
-
136
-        // convert bytes to megabytes and round
137
-        return round(($size / 1024) / 1024, $round);
138
-    }
139
-
140
-    /**
141
-     * Getter for DiskQuotaMB field to provide a default for existing
142
-     * records that have no quota field set, as it will need to default
143
-     * to a globally set size.
144
-     *
145
-     * @return string|int The quota size in MB
146
-     */
147
-    public function getDiskQuotaMB()
148
-    {
149
-        $size = $this->getField('DiskQuotaMB');
150
-
151
-        if (empty($size)) {
152
-            $defaults = $this->config()->get('defaults');
153
-            $size = (isset($defaults['DiskQuotaMB'])) ? $defaults['DiskQuotaMB'] : 0;
154
-        }
155
-
156
-        return $size;
157
-    }
158
-
159
-    /**
160
-     * Has the disk quota been exceeded?
161
-     *
162
-     * @return boolean
163
-     */
164
-    public function HasExceededDiskQuota()
165
-    {
166
-        return $this->getUsedQuotaMB(0) >= $this->getDiskQuotaMB();
167
-    }
168
-
169
-    /**
170
-     * Is there a disk quota set for this project?
171
-     *
172
-     * @return boolean
173
-     */
174
-    public function HasDiskQuota()
175
-    {
176
-        return $this->getDiskQuotaMB() > 0;
177
-    }
178
-
179
-    /**
180
-     * Returns the current disk quota usage as a percentage
181
-     *
182
-     * @return int
183
-     */
184
-    public function DiskQuotaUsagePercent()
185
-    {
186
-        $quota = $this->getDiskQuotaMB();
187
-        if ($quota > 0) {
188
-            return $this->getUsedQuotaMB() * 100 / $quota;
189
-        }
190
-        return 100;
191
-    }
192
-
193
-    /**
194
-     * Get the menu to be shown on projects
195
-     *
196
-     * @return ArrayList
197
-     */
198
-    public function Menu()
199
-    {
200
-        $list = new ArrayList();
201
-
202
-        $controller = Controller::curr();
203
-        $actionType = $controller->getField('CurrentActionType');
204
-
205
-        if (DNRoot::FlagSnapshotsEnabled() && $this->isProjectReady()) {
206
-            $list->push(new ArrayData(array(
207
-                'Link' => sprintf('naut/project/%s/snapshots', $this->Name),
208
-                'Title' => 'Snapshots',
209
-                'IsCurrent' => $this->isSection() && $controller->getAction() == 'snapshots',
210
-                'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_SNAPSHOT
211
-            )));
212
-        }
213
-
214
-        $this->extend('updateMenu', $list);
215
-
216
-        return $list;
217
-    }
218
-
219
-    /**
220
-     * Is this project currently at the root level of the controller that handles it?
221
-     *
222
-     * @return bool
223
-     */
224
-    public function isCurrent()
225
-    {
226
-        return $this->isSection() && Controller::curr()->getAction() == 'project';
227
-    }
228
-
229
-    /**
230
-     * Return the current object from $this->Menu()
231
-     * Good for making titles and things
232
-     *
233
-     * @return DataObject
234
-     */
235
-    public function CurrentMenu()
236
-    {
237
-        return $this->Menu()->filter('IsSection', true)->First();
238
-    }
239
-
240
-    /**
241
-     * Is this project currently in a controller that is handling it or performing a sub-task?
242
-     *
243
-     * @return bool
244
-     */
245
-    public function isSection()
246
-    {
247
-        $controller = Controller::curr();
248
-        $project = $controller->getField('CurrentProject');
249
-        return $project && $this->ID == $project->ID;
250
-    }
251
-
252
-    /**
253
-     * Restrict access to viewing this project
254
-     *
255
-     * @param Member|null $member
256
-     * @return boolean
257
-     */
258
-    public function canView($member = null)
259
-    {
260
-        if (!$member) {
261
-            $member = Member::currentUser();
262
-        }
263
-
264
-        if (Permission::checkMember($member, 'ADMIN')) {
265
-            return true;
266
-        }
267
-
268
-        return $member->inGroups($this->Viewers());
269
-    }
270
-
271
-    /**
272
-     * @param Member|null $member
273
-     *
274
-     * @return bool
275
-     */
276
-    public function canRestore($member = null)
277
-    {
278
-        if ($this->allowedAny(
279
-            array(
280
-                DNRoot::ALLOW_PROD_SNAPSHOT,
281
-                DNRoot::ALLOW_NON_PROD_SNAPSHOT
282
-            ),
283
-            $member
284
-        )) {
285
-            return true;
286
-        }
287
-
288
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
289
-            return $env->canRestore($member);
290
-        })->Count();
291
-    }
292
-
293
-    /**
294
-     * @param Member|null $member
295
-     * @return bool
296
-     */
297
-    public function canBackup($member = null)
298
-    {
299
-        if ($this->allowedAny(
300
-            array(
301
-                DNRoot::ALLOW_PROD_SNAPSHOT,
302
-                DNRoot::ALLOW_NON_PROD_SNAPSHOT
303
-            ),
304
-            $member
305
-        )) {
306
-            return true;
307
-        }
308
-
309
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
310
-            return $env->canBackup($member);
311
-        })->Count();
312
-    }
313
-
314
-    /**
315
-     * @param Member|null $member
316
-     * @return bool
317
-     */
318
-    public function canUploadArchive($member = null)
319
-    {
320
-        if ($this->allowedAny(
321
-            array(
322
-                DNRoot::ALLOW_PROD_SNAPSHOT,
323
-                DNRoot::ALLOW_NON_PROD_SNAPSHOT
324
-            ),
325
-            $member
326
-        )) {
327
-            return true;
328
-        }
329
-
330
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
331
-            return $env->canUploadArchive($member);
332
-        })->Count();
333
-    }
334
-
335
-    /**
336
-     * @param Member|null $member
337
-     * @return bool
338
-     */
339
-    public function canDownloadArchive($member = null)
340
-    {
341
-        if ($this->allowedAny(
342
-            array(
343
-                DNRoot::ALLOW_PROD_SNAPSHOT,
344
-                DNRoot::ALLOW_NON_PROD_SNAPSHOT
345
-            ),
346
-            $member
347
-        )) {
348
-            return true;
349
-        }
350
-
351
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
352
-            return $env->canDownloadArchive($member);
353
-        })->Count();
354
-    }
355
-
356
-    /**
357
-     * This is a permission check for the front-end only.
358
-     *
359
-     * Only admins can create environments for now. Also, we need to check the value
360
-     * of AllowedEnvironmentType which dictates which backend to use to render the form.
361
-     *
362
-     * @param Member|null $member
363
-     *
364
-     * @return bool
365
-     */
366
-    public function canCreateEnvironments($member = null)
367
-    {
368
-        $envType = $this->AllowedEnvironmentType;
369
-        if ($envType) {
370
-            $env = Injector::inst()->get($envType);
371
-            if ($env instanceof EnvironmentCreateBackend) {
372
-                return $this->allowed(DNRoot::ALLOW_CREATE_ENVIRONMENT, $member);
373
-            }
374
-        }
375
-        return false;
376
-    }
377
-
378
-    /**
379
-     * @return DataList
380
-     */
381
-    public function DataArchives()
382
-    {
383
-        $envIds = $this->Environments()->column('ID');
384
-        return DNDataArchive::get()->filter('EnvironmentID', $envIds);
385
-    }
386
-
387
-    /**
388
-     * Return all archives which are "manual upload requests",
389
-     * meaning they don't have a file attached to them (yet).
390
-     *
391
-     * @return DataList
392
-     */
393
-    public function PendingManualUploadDataArchives()
394
-    {
395
-        return $this->DataArchives()->filter('ArchiveFileID', null);
396
-    }
397
-
398
-    /**
399
-     * Build an environment variable array to be used with this project.
400
-     *
401
-     * This is relevant if every project needs to use an individual SSH pubkey.
402
-     *
403
-     * Include this with all Gitonomy\Git\Repository, and
404
-     * \Symfony\Component\Process\Processes.
405
-     *
406
-     * @return array
407
-     */
408
-    public function getProcessEnv()
409
-    {
410
-        if (file_exists($this->getPrivateKeyPath())) {
411
-            // Key-pair is available, use it.
412
-            $processEnv = array(
413
-                'IDENT_KEY' => $this->getPrivateKeyPath(),
414
-                'GIT_SSH' => BASE_PATH . "/deploynaut/git-deploy.sh"
415
-            );
416
-        } else {
417
-            $processEnv = array();
418
-        }
419
-        $this->extend('updateProcessEnv', $processEnv);
420
-
421
-        return $processEnv;
422
-    }
423
-
424
-    /**
425
-     * Get a string of people allowed to view this project
426
-     *
427
-     * @return string
428
-     */
429
-    public function getViewersList()
430
-    {
431
-        return implode(", ", $this->Viewers()->column("Title"));
432
-    }
433
-
434
-    /**
435
-     * @return DNData
436
-     */
437
-    public function DNData()
438
-    {
439
-        return DNData::inst();
440
-    }
441
-
442
-    /**
443
-     * Provides a DNBuildList of builds found in this project.
444
-     *
445
-     * @return DNReferenceList
446
-     */
447
-    public function DNBuildList()
448
-    {
449
-        return DNReferenceList::create($this, $this->DNData());
450
-    }
451
-
452
-    /**
453
-     * Provides a list of the branches in this project.
454
-     *
455
-     * @return DNBranchList
456
-     */
457
-    public function DNBranchList()
458
-    {
459
-        if ($this->CVSPath && !$this->repoExists()) {
460
-            $this->cloneRepo();
461
-        }
462
-        return DNBranchList::create($this, $this->DNData());
463
-    }
464
-
465
-    /**
466
-     * Provides a list of the tags in this project.
467
-     *
468
-     * @return DNReferenceList
469
-     */
470
-    public function DNTagList()
471
-    {
472
-        if ($this->CVSPath && !$this->repoExists()) {
473
-            $this->cloneRepo();
474
-        }
475
-        return DNReferenceList::create($this, $this->DNData(), null, null, true);
476
-    }
477
-
478
-    /**
479
-     * @return false|Gitonomy\Git\Repository
480
-     */
481
-    public function getRepository()
482
-    {
483
-        if (!$this->repoExists()) {
484
-            return false;
485
-        }
486
-
487
-        return new Gitonomy\Git\Repository($this->getLocalCVSPath());
488
-    }
489
-
490
-    /**
491
-     * Provides a list of environments found in this project.
492
-     * CAUTION: filterByCallback will change this into an ArrayList!
493
-     *
494
-     * @return ArrayList
495
-     */
496
-    public function DNEnvironmentList()
497
-    {
498
-        if (!self::$_current_member_cache) {
499
-            self::$_current_member_cache = Member::currentUser();
500
-        }
501
-
502
-        if (self::$_current_member_cache === false) {
503
-            return new ArrayList();
504
-        }
505
-
506
-        $currentMember = self::$_current_member_cache;
507
-        return $this->Environments()
508
-            ->filterByCallBack(function ($item) use ($currentMember) {
509
-                return $item->canView($currentMember);
510
-            });
511
-    }
512
-
513
-    /**
514
-     * @param string $usage
515
-     * @return ArrayList
516
-     */
517
-    public function EnvironmentsByUsage($usage)
518
-    {
519
-        return $this->DNEnvironmentList()->filter('Usage', $usage);
520
-    }
521
-
522
-    /**
523
-     * Returns a map of envrionment name to build name
524
-     *
525
-     * @return false|DNDeployment
526
-     */
527
-    public function currentBuilds()
528
-    {
529
-        if (!isset(self::$relation_cache['currentBuilds.'.$this->ID])) {
530
-            $currentBuilds = array();
531
-            foreach ($this->Environments() as $env) {
532
-                $currentBuilds[$env->Name] = $env->CurrentBuild();
533
-            }
534
-            self::$relation_cache['currentBuilds.'.$this->ID] = $currentBuilds;
535
-        }
536
-        return self::$relation_cache['currentBuilds.'.$this->ID];
537
-    }
538
-
539
-    /**
540
-     * @param string
541
-     * @return string
542
-     */
543
-    public function Link($action = '')
544
-    {
545
-        return Controller::join_links("naut", "project", $this->Name, $action);
546
-    }
547
-
548
-    /**
549
-     * @return string|null
550
-     */
551
-    public function CreateEnvironmentLink()
552
-    {
553
-        if ($this->canCreateEnvironments()) {
554
-            return $this->Link('createenv');
555
-        }
556
-        return null;
557
-    }
558
-
559
-    /**
560
-     * @return string
561
-     */
562
-    public function ToggleStarLink()
563
-    {
564
-        return $this->Link('/star');
565
-    }
566
-
567
-    /**
568
-     * @return bool
569
-     */
570
-    public function IsStarred()
571
-    {
572
-        $member = Member::currentUser();
573
-        if ($member === null) {
574
-            return false;
575
-        }
576
-        $favourited = $this->StarredBy()->filter('MemberID', $member->ID);
577
-        if ($favourited->count() == 0) {
578
-            return false;
579
-        }
580
-        return true;
581
-    }
582
-
583
-    /**
584
-     * @param string $action
585
-     * @return string
586
-     */
587
-    public function APILink($action)
588
-    {
589
-        return Controller::join_links("naut", "api", $this->Name, $action);
590
-    }
591
-
592
-    /**
593
-     * @return FieldList
594
-     */
595
-    public function getCMSFields()
596
-    {
597
-        $fields = parent::getCMSFields();
598
-
599
-        /** @var GridField $environments */
600
-        $environments = $fields->dataFieldByName("Environments");
601
-
602
-        $fields->fieldByName("Root")->removeByName("Viewers");
603
-        $fields->fieldByName("Root")->removeByName("Environments");
604
-        $fields->fieldByName("Root")->removeByName("LocalCVSPath");
605
-
606
-        $diskQuotaDesc = 'This is the maximum amount of disk space (in megabytes) that all environments within this '
607
-            . 'project can use for stored snapshots';
608
-        $fields->dataFieldByName('DiskQuotaMB')->setDescription($diskQuotaDesc);
609
-
610
-        $projectNameDesc = 'Changing the name will <strong>reset</strong> the deploy configuration and avoid using non'
611
-            . 'alphanumeric characters';
612
-        $fields->fieldByName('Root.Main.Name')
613
-            ->setTitle('Project name')
614
-            ->setDescription($projectNameDesc);
615
-
616
-        $fields->fieldByName('Root.Main.CVSPath')
617
-            ->setTitle('Git repository')
618
-            ->setDescription('E.g. [email protected]:silverstripe/silverstripe-installer.git');
619
-
620
-        $workspaceField = new ReadonlyField('LocalWorkspace', 'Git workspace', $this->getLocalCVSPath());
621
-        $workspaceField->setDescription('This is where the GIT repository are located on this server');
622
-        $fields->insertAfter($workspaceField, 'CVSPath');
623
-
624
-        $readAccessGroups = ListboxField::create('Viewers', 'Project viewers', Group::get()->map()->toArray())
625
-            ->setMultiple(true)
626
-            ->setDescription('These groups can view the project in the front-end.');
627
-        $fields->addFieldToTab("Root.Main", $readAccessGroups);
628
-
629
-        $this->setCreateProjectFolderField($fields);
630
-        $this->setEnvironmentFields($fields, $environments);
631
-
632
-        $environmentTypes = ClassInfo::implementorsOf('EnvironmentCreateBackend');
633
-        $types = array();
634
-        foreach ($environmentTypes as $type) {
635
-            $types[$type] = $type;
636
-        }
637
-
638
-        $fields->addFieldsToTab('Root.Main', array(
639
-            DropdownField::create(
640
-                'AllowedEnvironmentType',
641
-                'Allowed Environment Type',
642
-                $types
643
-            )->setDescription('This defined which form to show on the front end for '
644
-                . 'environment creation. This will not affect backend functionality.')
645
-            ->setEmptyString(' - None - '),
646
-        ));
647
-
648
-        return $fields;
649
-    }
650
-
651
-    /**
652
-     * If there isn't a capistrano env project folder, show options to create one
653
-     *
654
-     * @param FieldList $fields
655
-     */
656
-    public function setCreateProjectFolderField(&$fields)
657
-    {
658
-        // Check if the capistrano project folder exists
659
-        if (!$this->Name) {
660
-            return;
661
-        }
662
-
663
-        if ($this->projectFolderExists()) {
664
-            return;
665
-        }
666
-
667
-        $createFolderNotice = new LabelField('CreateEnvFolderNotice', 'Warning: No Capistrano project folder exists');
668
-        $createFolderNotice->addExtraClass('message warning');
669
-        $fields->insertBefore($createFolderNotice, 'Name');
670
-        $createFolderField = new CheckboxField('CreateEnvFolder', 'Create folder');
671
-        $createFolderField->setDescription('Would you like to create the capistrano project folder?');
672
-        $fields->insertAfter($createFolderField, 'CreateEnvFolderNotice');
673
-    }
674
-
675
-    /**
676
-     * @return boolean
677
-     */
678
-    public function projectFolderExists()
679
-    {
680
-        return file_exists($this->getProjectFolderPath());
681
-    }
682
-
683
-    /**
684
-     * @return bool
685
-     */
686
-    public function repoExists()
687
-    {
688
-        return file_exists(sprintf('%s/HEAD', $this->getLocalCVSPath()));
689
-    }
690
-
691
-    /**
692
-     * Setup a job to clone a git repository.
693
-     * @return string resque token
694
-     */
695
-    public function cloneRepo()
696
-    {
697
-        // Avoid this being called multiple times in the same request
698
-        if (!isset(self::$has_cloned_cache[$this->ID])) {
699
-            $fetch = DNGitFetch::create();
700
-            $fetch->ProjectID = $this->ID;
701
-            $fetch->write();
702
-
703
-            // passing true here tells DNGitFetch to force a git clone, otherwise
704
-            // it will just update the repo if it already exists. We want to ensure
705
-            // we're always cloning a new repo in this case, as the git URL may have changed.
706
-            $fetch->start(true);
707
-
708
-            self::$has_cloned_cache[$this->ID] = true;
709
-        }
710
-    }
711
-
712
-    /**
713
-     * @return string
714
-     */
715
-    public function getLocalCVSPath()
716
-    {
717
-        return sprintf('%s/%s', DEPLOYNAUT_LOCAL_VCS_PATH, $this->Name);
718
-    }
719
-
720
-    public function onBeforeWrite()
721
-    {
722
-        parent::onBeforeWrite();
723
-
724
-        if ($this->CreateEnvFolder && !file_exists($this->getProjectFolderPath())) {
725
-            mkdir($this->getProjectFolderPath());
726
-        }
727
-    }
728
-
729
-    public function onAfterWrite()
730
-    {
731
-        parent::onAfterWrite();
732
-
733
-        if (!$this->CVSPath) {
734
-            return;
735
-        }
736
-
737
-        $changedFields = $this->getChangedFields(true, 2);
738
-        if (isset($changedFields['CVSPath']) || isset($changedFields['Name'])) {
739
-            $this->cloneRepo();
740
-        }
741
-    }
742
-
743
-    /**
744
-     * Delete related environments and folders
745
-     */
746
-    public function onAfterDelete()
747
-    {
748
-        parent::onAfterDelete();
749
-
750
-        // Delete related environments
751
-        foreach ($this->Environments() as $env) {
752
-            $env->delete();
753
-        }
754
-
755
-        // Delete local repository
756
-        if (file_exists($this->getLocalCVSPath())) {
757
-            Filesystem::removeFolder($this->getLocalCVSPath());
758
-        }
759
-
760
-        // Delete project template
761
-        if (file_exists($this->getProjectFolderPath()) && Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
762
-            Filesystem::removeFolder($this->getProjectFolderPath());
763
-        }
764
-
765
-        // Delete the deploy key
766
-        if (file_exists($this->getKeyDir())) {
767
-            Filesystem::removeFolder($this->getKeyDir());
768
-        }
769
-    }
770
-
771
-    /**
772
-     * Fetch the public key for this project.
773
-     *
774
-     * @return string|void
775
-     */
776
-    public function getPublicKey()
777
-    {
778
-        $key = $this->getPublicKeyPath();
779
-
780
-        if (file_exists($key)) {
781
-            return trim(file_get_contents($key));
782
-        }
783
-    }
784
-
785
-    /**
786
-     * This returns that path of the public key if a key directory is set. It doesn't check whether the file exists.
787
-     *
788
-     * @return string|null
789
-     */
790
-    public function getPublicKeyPath()
791
-    {
792
-        if ($privateKey = $this->getPrivateKeyPath()) {
793
-            return $privateKey . '.pub';
794
-        }
795
-        return null;
796
-    }
797
-
798
-    /**
799
-     * This returns that path of the private key if a key directory is set. It doesn't check whether the file exists.
800
-     *
801
-     * @return string|null
802
-     */
803
-    public function getPrivateKeyPath()
804
-    {
805
-        $keyDir = $this->getKeyDir();
806
-        if (!empty($keyDir)) {
807
-            $filter = FileNameFilter::create();
808
-            $name = $filter->filter($this->Name);
809
-            return $keyDir . '/' . $name;
810
-        }
811
-        return null;
812
-    }
813
-
814
-    /**
815
-     * Returns the location of the projects key dir if one exists.
816
-     *
817
-     * @return string|null
818
-     */
819
-    public function getKeyDir()
820
-    {
821
-        $keyDir = $this->DNData()->getKeyDir();
822
-        if (!$keyDir) {
823
-            return null;
824
-        }
825
-
826
-        $filter = FileNameFilter::create();
827
-        $name = $filter->filter($this->Name);
828
-
829
-        return $this->DNData()->getKeyDir() . '/' . $name;
830
-    }
831
-
832
-    /**
833
-     * Setup a gridfield for the environment configs
834
-     *
835
-     * @param FieldList $fields
836
-     * @param GridField $environments
837
-     */
838
-    protected function setEnvironmentFields(&$fields, $environments)
839
-    {
840
-        if (!$environments) {
841
-            return;
842
-        }
843
-
844
-        $environments->getConfig()->addComponent(new GridFieldAddNewMultiClass());
845
-        $environments->getConfig()->removeComponentsByType('GridFieldAddNewButton');
846
-        $environments->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
847
-        $environments->getConfig()->removeComponentsByType('GridFieldDeleteAction');
848
-        $environments->getConfig()->removeComponentsByType('GridFieldPageCount');
849
-        if (Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
850
-            $addNewRelease = new GridFieldAddNewButton('toolbar-header-right');
851
-            $addNewRelease->setButtonName('Add');
852
-            $environments->getConfig()->addComponent($addNewRelease);
853
-        }
854
-
855
-        $fields->addFieldToTab("Root.Main", $environments);
856
-    }
857
-
858
-    /**
859
-     * Provide current repository URL to the users.
860
-     *
861
-     * @return void|string
862
-     */
863
-    public function getRepositoryURL()
864
-    {
865
-        $showUrl = Config::inst()->get($this->class, 'show_repository_url');
866
-        if ($showUrl) {
867
-            return $this->CVSPath;
868
-        }
869
-    }
870
-
871
-    /**
872
-     * Whitelist configuration that describes how to convert a repository URL into a link
873
-     * to a web user interface for that URL
874
-     *
875
-     * Consists of a hash of "full.lower.case.domain" => {configuration} key/value pairs
876
-     *
877
-     * {configuration} can either be boolean true to auto-detect both the host and the
878
-     * name of the UI provider, or a nested array that overrides either one or both
879
-     * of the auto-detected valyes
880
-     *
881
-     * @var array
882
-     */
883
-    private static $repository_interfaces = array(
884
-        'github.com' => array(
885
-            'icon' => 'deploynaut/img/github.png',
886
-            'name' => 'Github.com',
887
-        ),
888
-        'bitbucket.org' => array(
889
-            'commit' => 'commits',
890
-            'name' => 'Bitbucket.org',
891
-        ),
892
-        'repo.or.cz' => array(
893
-            'scheme' => 'http',
894
-            'name' => 'repo.or.cz',
895
-            'regex' => array('^(.*)$' => '/w$1'),
896
-        ),
897
-
898
-        /* Example for adding your own gitlab repository and override all auto-detected values (with their defaults)
18
+	/**
19
+	 * @var array
20
+	 */
21
+	public static $db = array(
22
+		"Name" => "Varchar",
23
+		"CVSPath" => "Varchar(255)",
24
+		"DiskQuotaMB" => "Int",
25
+		"AllowedEnvironmentType" => "Varchar(255)",
26
+	);
27
+
28
+	/**
29
+	 * @var array
30
+	 */
31
+	public static $has_many = array(
32
+		"Environments" => "DNEnvironment",
33
+		"CreateEnvironments" => "DNCreateEnvironment"
34
+	);
35
+
36
+	/**
37
+	 * @var array
38
+	 */
39
+	public static $many_many = array(
40
+		"Viewers" => "Group",
41
+		'StarredBy' => "Member"
42
+	);
43
+
44
+	/**
45
+	 * @var array
46
+	 */
47
+	public static $summary_fields = array(
48
+		"Name",
49
+		"ViewersList",
50
+	);
51
+
52
+	/**
53
+	 * @var array
54
+	 */
55
+	public static $searchable_fields = array(
56
+		"Name",
57
+	);
58
+
59
+	/**
60
+	 * @var string
61
+	 */
62
+	private static $singular_name = 'Project';
63
+
64
+	/**
65
+	 * @var string
66
+	 */
67
+	private static $plural_name = 'Projects';
68
+
69
+	/**
70
+	 * @var string
71
+	 */
72
+	private static $default_sort = 'Name';
73
+
74
+	/**
75
+	 * Display the repository URL on the project page.
76
+	 *
77
+	 * @var bool
78
+	 */
79
+	private static $show_repository_url = false;
80
+
81
+	/**
82
+	 * In-memory cache for currentBuilds per environment since fetching them from
83
+	 * disk is pretty resource hungry.
84
+	 *
85
+	 * @var array
86
+	 */
87
+	protected static $relation_cache = array();
88
+
89
+	/**
90
+	 * In-memory cache to determine whether clone repo was called.
91
+	 * @var array
92
+	 */
93
+	private static $has_cloned_cache = array();
94
+
95
+	/**
96
+	 * @var bool|Member
97
+	 */
98
+	protected static $_current_member_cache = null;
99
+
100
+	/**
101
+	 * Used by the sync task
102
+	 *
103
+	 * @param string $path
104
+	 * @return \DNProject
105
+	 */
106
+	public static function create_from_path($path)
107
+	{
108
+		$project = DNProject::create();
109
+		$project->Name = $path;
110
+		$project->write();
111
+
112
+		// add the administrators group as the viewers of the new project
113
+		$adminGroup = Group::get()->filter('Code', 'administrators')->first();
114
+		if ($adminGroup && $adminGroup->exists()) {
115
+			$project->Viewers()->add($adminGroup);
116
+		}
117
+		return $project;
118
+	}
119
+
120
+	/**
121
+	 * Return the used quota in MB.
122
+	 *
123
+	 * @param int $round Number of decimal places to round to
124
+	 * @return double The used quota size in MB
125
+	 */
126
+	public function getUsedQuotaMB($round = 2)
127
+	{
128
+		$size = 0;
129
+
130
+		foreach ($this->Environments() as $environment) {
131
+			foreach ($environment->DataArchives()->filter('IsBackup', 0) as $archive) {
132
+				$size += $archive->ArchiveFile()->getAbsoluteSize();
133
+			}
134
+		}
135
+
136
+		// convert bytes to megabytes and round
137
+		return round(($size / 1024) / 1024, $round);
138
+	}
139
+
140
+	/**
141
+	 * Getter for DiskQuotaMB field to provide a default for existing
142
+	 * records that have no quota field set, as it will need to default
143
+	 * to a globally set size.
144
+	 *
145
+	 * @return string|int The quota size in MB
146
+	 */
147
+	public function getDiskQuotaMB()
148
+	{
149
+		$size = $this->getField('DiskQuotaMB');
150
+
151
+		if (empty($size)) {
152
+			$defaults = $this->config()->get('defaults');
153
+			$size = (isset($defaults['DiskQuotaMB'])) ? $defaults['DiskQuotaMB'] : 0;
154
+		}
155
+
156
+		return $size;
157
+	}
158
+
159
+	/**
160
+	 * Has the disk quota been exceeded?
161
+	 *
162
+	 * @return boolean
163
+	 */
164
+	public function HasExceededDiskQuota()
165
+	{
166
+		return $this->getUsedQuotaMB(0) >= $this->getDiskQuotaMB();
167
+	}
168
+
169
+	/**
170
+	 * Is there a disk quota set for this project?
171
+	 *
172
+	 * @return boolean
173
+	 */
174
+	public function HasDiskQuota()
175
+	{
176
+		return $this->getDiskQuotaMB() > 0;
177
+	}
178
+
179
+	/**
180
+	 * Returns the current disk quota usage as a percentage
181
+	 *
182
+	 * @return int
183
+	 */
184
+	public function DiskQuotaUsagePercent()
185
+	{
186
+		$quota = $this->getDiskQuotaMB();
187
+		if ($quota > 0) {
188
+			return $this->getUsedQuotaMB() * 100 / $quota;
189
+		}
190
+		return 100;
191
+	}
192
+
193
+	/**
194
+	 * Get the menu to be shown on projects
195
+	 *
196
+	 * @return ArrayList
197
+	 */
198
+	public function Menu()
199
+	{
200
+		$list = new ArrayList();
201
+
202
+		$controller = Controller::curr();
203
+		$actionType = $controller->getField('CurrentActionType');
204
+
205
+		if (DNRoot::FlagSnapshotsEnabled() && $this->isProjectReady()) {
206
+			$list->push(new ArrayData(array(
207
+				'Link' => sprintf('naut/project/%s/snapshots', $this->Name),
208
+				'Title' => 'Snapshots',
209
+				'IsCurrent' => $this->isSection() && $controller->getAction() == 'snapshots',
210
+				'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_SNAPSHOT
211
+			)));
212
+		}
213
+
214
+		$this->extend('updateMenu', $list);
215
+
216
+		return $list;
217
+	}
218
+
219
+	/**
220
+	 * Is this project currently at the root level of the controller that handles it?
221
+	 *
222
+	 * @return bool
223
+	 */
224
+	public function isCurrent()
225
+	{
226
+		return $this->isSection() && Controller::curr()->getAction() == 'project';
227
+	}
228
+
229
+	/**
230
+	 * Return the current object from $this->Menu()
231
+	 * Good for making titles and things
232
+	 *
233
+	 * @return DataObject
234
+	 */
235
+	public function CurrentMenu()
236
+	{
237
+		return $this->Menu()->filter('IsSection', true)->First();
238
+	}
239
+
240
+	/**
241
+	 * Is this project currently in a controller that is handling it or performing a sub-task?
242
+	 *
243
+	 * @return bool
244
+	 */
245
+	public function isSection()
246
+	{
247
+		$controller = Controller::curr();
248
+		$project = $controller->getField('CurrentProject');
249
+		return $project && $this->ID == $project->ID;
250
+	}
251
+
252
+	/**
253
+	 * Restrict access to viewing this project
254
+	 *
255
+	 * @param Member|null $member
256
+	 * @return boolean
257
+	 */
258
+	public function canView($member = null)
259
+	{
260
+		if (!$member) {
261
+			$member = Member::currentUser();
262
+		}
263
+
264
+		if (Permission::checkMember($member, 'ADMIN')) {
265
+			return true;
266
+		}
267
+
268
+		return $member->inGroups($this->Viewers());
269
+	}
270
+
271
+	/**
272
+	 * @param Member|null $member
273
+	 *
274
+	 * @return bool
275
+	 */
276
+	public function canRestore($member = null)
277
+	{
278
+		if ($this->allowedAny(
279
+			array(
280
+				DNRoot::ALLOW_PROD_SNAPSHOT,
281
+				DNRoot::ALLOW_NON_PROD_SNAPSHOT
282
+			),
283
+			$member
284
+		)) {
285
+			return true;
286
+		}
287
+
288
+		return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
289
+			return $env->canRestore($member);
290
+		})->Count();
291
+	}
292
+
293
+	/**
294
+	 * @param Member|null $member
295
+	 * @return bool
296
+	 */
297
+	public function canBackup($member = null)
298
+	{
299
+		if ($this->allowedAny(
300
+			array(
301
+				DNRoot::ALLOW_PROD_SNAPSHOT,
302
+				DNRoot::ALLOW_NON_PROD_SNAPSHOT
303
+			),
304
+			$member
305
+		)) {
306
+			return true;
307
+		}
308
+
309
+		return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
310
+			return $env->canBackup($member);
311
+		})->Count();
312
+	}
313
+
314
+	/**
315
+	 * @param Member|null $member
316
+	 * @return bool
317
+	 */
318
+	public function canUploadArchive($member = null)
319
+	{
320
+		if ($this->allowedAny(
321
+			array(
322
+				DNRoot::ALLOW_PROD_SNAPSHOT,
323
+				DNRoot::ALLOW_NON_PROD_SNAPSHOT
324
+			),
325
+			$member
326
+		)) {
327
+			return true;
328
+		}
329
+
330
+		return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
331
+			return $env->canUploadArchive($member);
332
+		})->Count();
333
+	}
334
+
335
+	/**
336
+	 * @param Member|null $member
337
+	 * @return bool
338
+	 */
339
+	public function canDownloadArchive($member = null)
340
+	{
341
+		if ($this->allowedAny(
342
+			array(
343
+				DNRoot::ALLOW_PROD_SNAPSHOT,
344
+				DNRoot::ALLOW_NON_PROD_SNAPSHOT
345
+			),
346
+			$member
347
+		)) {
348
+			return true;
349
+		}
350
+
351
+		return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
352
+			return $env->canDownloadArchive($member);
353
+		})->Count();
354
+	}
355
+
356
+	/**
357
+	 * This is a permission check for the front-end only.
358
+	 *
359
+	 * Only admins can create environments for now. Also, we need to check the value
360
+	 * of AllowedEnvironmentType which dictates which backend to use to render the form.
361
+	 *
362
+	 * @param Member|null $member
363
+	 *
364
+	 * @return bool
365
+	 */
366
+	public function canCreateEnvironments($member = null)
367
+	{
368
+		$envType = $this->AllowedEnvironmentType;
369
+		if ($envType) {
370
+			$env = Injector::inst()->get($envType);
371
+			if ($env instanceof EnvironmentCreateBackend) {
372
+				return $this->allowed(DNRoot::ALLOW_CREATE_ENVIRONMENT, $member);
373
+			}
374
+		}
375
+		return false;
376
+	}
377
+
378
+	/**
379
+	 * @return DataList
380
+	 */
381
+	public function DataArchives()
382
+	{
383
+		$envIds = $this->Environments()->column('ID');
384
+		return DNDataArchive::get()->filter('EnvironmentID', $envIds);
385
+	}
386
+
387
+	/**
388
+	 * Return all archives which are "manual upload requests",
389
+	 * meaning they don't have a file attached to them (yet).
390
+	 *
391
+	 * @return DataList
392
+	 */
393
+	public function PendingManualUploadDataArchives()
394
+	{
395
+		return $this->DataArchives()->filter('ArchiveFileID', null);
396
+	}
397
+
398
+	/**
399
+	 * Build an environment variable array to be used with this project.
400
+	 *
401
+	 * This is relevant if every project needs to use an individual SSH pubkey.
402
+	 *
403
+	 * Include this with all Gitonomy\Git\Repository, and
404
+	 * \Symfony\Component\Process\Processes.
405
+	 *
406
+	 * @return array
407
+	 */
408
+	public function getProcessEnv()
409
+	{
410
+		if (file_exists($this->getPrivateKeyPath())) {
411
+			// Key-pair is available, use it.
412
+			$processEnv = array(
413
+				'IDENT_KEY' => $this->getPrivateKeyPath(),
414
+				'GIT_SSH' => BASE_PATH . "/deploynaut/git-deploy.sh"
415
+			);
416
+		} else {
417
+			$processEnv = array();
418
+		}
419
+		$this->extend('updateProcessEnv', $processEnv);
420
+
421
+		return $processEnv;
422
+	}
423
+
424
+	/**
425
+	 * Get a string of people allowed to view this project
426
+	 *
427
+	 * @return string
428
+	 */
429
+	public function getViewersList()
430
+	{
431
+		return implode(", ", $this->Viewers()->column("Title"));
432
+	}
433
+
434
+	/**
435
+	 * @return DNData
436
+	 */
437
+	public function DNData()
438
+	{
439
+		return DNData::inst();
440
+	}
441
+
442
+	/**
443
+	 * Provides a DNBuildList of builds found in this project.
444
+	 *
445
+	 * @return DNReferenceList
446
+	 */
447
+	public function DNBuildList()
448
+	{
449
+		return DNReferenceList::create($this, $this->DNData());
450
+	}
451
+
452
+	/**
453
+	 * Provides a list of the branches in this project.
454
+	 *
455
+	 * @return DNBranchList
456
+	 */
457
+	public function DNBranchList()
458
+	{
459
+		if ($this->CVSPath && !$this->repoExists()) {
460
+			$this->cloneRepo();
461
+		}
462
+		return DNBranchList::create($this, $this->DNData());
463
+	}
464
+
465
+	/**
466
+	 * Provides a list of the tags in this project.
467
+	 *
468
+	 * @return DNReferenceList
469
+	 */
470
+	public function DNTagList()
471
+	{
472
+		if ($this->CVSPath && !$this->repoExists()) {
473
+			$this->cloneRepo();
474
+		}
475
+		return DNReferenceList::create($this, $this->DNData(), null, null, true);
476
+	}
477
+
478
+	/**
479
+	 * @return false|Gitonomy\Git\Repository
480
+	 */
481
+	public function getRepository()
482
+	{
483
+		if (!$this->repoExists()) {
484
+			return false;
485
+		}
486
+
487
+		return new Gitonomy\Git\Repository($this->getLocalCVSPath());
488
+	}
489
+
490
+	/**
491
+	 * Provides a list of environments found in this project.
492
+	 * CAUTION: filterByCallback will change this into an ArrayList!
493
+	 *
494
+	 * @return ArrayList
495
+	 */
496
+	public function DNEnvironmentList()
497
+	{
498
+		if (!self::$_current_member_cache) {
499
+			self::$_current_member_cache = Member::currentUser();
500
+		}
501
+
502
+		if (self::$_current_member_cache === false) {
503
+			return new ArrayList();
504
+		}
505
+
506
+		$currentMember = self::$_current_member_cache;
507
+		return $this->Environments()
508
+			->filterByCallBack(function ($item) use ($currentMember) {
509
+				return $item->canView($currentMember);
510
+			});
511
+	}
512
+
513
+	/**
514
+	 * @param string $usage
515
+	 * @return ArrayList
516
+	 */
517
+	public function EnvironmentsByUsage($usage)
518
+	{
519
+		return $this->DNEnvironmentList()->filter('Usage', $usage);
520
+	}
521
+
522
+	/**
523
+	 * Returns a map of envrionment name to build name
524
+	 *
525
+	 * @return false|DNDeployment
526
+	 */
527
+	public function currentBuilds()
528
+	{
529
+		if (!isset(self::$relation_cache['currentBuilds.'.$this->ID])) {
530
+			$currentBuilds = array();
531
+			foreach ($this->Environments() as $env) {
532
+				$currentBuilds[$env->Name] = $env->CurrentBuild();
533
+			}
534
+			self::$relation_cache['currentBuilds.'.$this->ID] = $currentBuilds;
535
+		}
536
+		return self::$relation_cache['currentBuilds.'.$this->ID];
537
+	}
538
+
539
+	/**
540
+	 * @param string
541
+	 * @return string
542
+	 */
543
+	public function Link($action = '')
544
+	{
545
+		return Controller::join_links("naut", "project", $this->Name, $action);
546
+	}
547
+
548
+	/**
549
+	 * @return string|null
550
+	 */
551
+	public function CreateEnvironmentLink()
552
+	{
553
+		if ($this->canCreateEnvironments()) {
554
+			return $this->Link('createenv');
555
+		}
556
+		return null;
557
+	}
558
+
559
+	/**
560
+	 * @return string
561
+	 */
562
+	public function ToggleStarLink()
563
+	{
564
+		return $this->Link('/star');
565
+	}
566
+
567
+	/**
568
+	 * @return bool
569
+	 */
570
+	public function IsStarred()
571
+	{
572
+		$member = Member::currentUser();
573
+		if ($member === null) {
574
+			return false;
575
+		}
576
+		$favourited = $this->StarredBy()->filter('MemberID', $member->ID);
577
+		if ($favourited->count() == 0) {
578
+			return false;
579
+		}
580
+		return true;
581
+	}
582
+
583
+	/**
584
+	 * @param string $action
585
+	 * @return string
586
+	 */
587
+	public function APILink($action)
588
+	{
589
+		return Controller::join_links("naut", "api", $this->Name, $action);
590
+	}
591
+
592
+	/**
593
+	 * @return FieldList
594
+	 */
595
+	public function getCMSFields()
596
+	{
597
+		$fields = parent::getCMSFields();
598
+
599
+		/** @var GridField $environments */
600
+		$environments = $fields->dataFieldByName("Environments");
601
+
602
+		$fields->fieldByName("Root")->removeByName("Viewers");
603
+		$fields->fieldByName("Root")->removeByName("Environments");
604
+		$fields->fieldByName("Root")->removeByName("LocalCVSPath");
605
+
606
+		$diskQuotaDesc = 'This is the maximum amount of disk space (in megabytes) that all environments within this '
607
+			. 'project can use for stored snapshots';
608
+		$fields->dataFieldByName('DiskQuotaMB')->setDescription($diskQuotaDesc);
609
+
610
+		$projectNameDesc = 'Changing the name will <strong>reset</strong> the deploy configuration and avoid using non'
611
+			. 'alphanumeric characters';
612
+		$fields->fieldByName('Root.Main.Name')
613
+			->setTitle('Project name')
614
+			->setDescription($projectNameDesc);
615
+
616
+		$fields->fieldByName('Root.Main.CVSPath')
617
+			->setTitle('Git repository')
618
+			->setDescription('E.g. [email protected]:silverstripe/silverstripe-installer.git');
619
+
620
+		$workspaceField = new ReadonlyField('LocalWorkspace', 'Git workspace', $this->getLocalCVSPath());
621
+		$workspaceField->setDescription('This is where the GIT repository are located on this server');
622
+		$fields->insertAfter($workspaceField, 'CVSPath');
623
+
624
+		$readAccessGroups = ListboxField::create('Viewers', 'Project viewers', Group::get()->map()->toArray())
625
+			->setMultiple(true)
626
+			->setDescription('These groups can view the project in the front-end.');
627
+		$fields->addFieldToTab("Root.Main", $readAccessGroups);
628
+
629
+		$this->setCreateProjectFolderField($fields);
630
+		$this->setEnvironmentFields($fields, $environments);
631
+
632
+		$environmentTypes = ClassInfo::implementorsOf('EnvironmentCreateBackend');
633
+		$types = array();
634
+		foreach ($environmentTypes as $type) {
635
+			$types[$type] = $type;
636
+		}
637
+
638
+		$fields->addFieldsToTab('Root.Main', array(
639
+			DropdownField::create(
640
+				'AllowedEnvironmentType',
641
+				'Allowed Environment Type',
642
+				$types
643
+			)->setDescription('This defined which form to show on the front end for '
644
+				. 'environment creation. This will not affect backend functionality.')
645
+			->setEmptyString(' - None - '),
646
+		));
647
+
648
+		return $fields;
649
+	}
650
+
651
+	/**
652
+	 * If there isn't a capistrano env project folder, show options to create one
653
+	 *
654
+	 * @param FieldList $fields
655
+	 */
656
+	public function setCreateProjectFolderField(&$fields)
657
+	{
658
+		// Check if the capistrano project folder exists
659
+		if (!$this->Name) {
660
+			return;
661
+		}
662
+
663
+		if ($this->projectFolderExists()) {
664
+			return;
665
+		}
666
+
667
+		$createFolderNotice = new LabelField('CreateEnvFolderNotice', 'Warning: No Capistrano project folder exists');
668
+		$createFolderNotice->addExtraClass('message warning');
669
+		$fields->insertBefore($createFolderNotice, 'Name');
670
+		$createFolderField = new CheckboxField('CreateEnvFolder', 'Create folder');
671
+		$createFolderField->setDescription('Would you like to create the capistrano project folder?');
672
+		$fields->insertAfter($createFolderField, 'CreateEnvFolderNotice');
673
+	}
674
+
675
+	/**
676
+	 * @return boolean
677
+	 */
678
+	public function projectFolderExists()
679
+	{
680
+		return file_exists($this->getProjectFolderPath());
681
+	}
682
+
683
+	/**
684
+	 * @return bool
685
+	 */
686
+	public function repoExists()
687
+	{
688
+		return file_exists(sprintf('%s/HEAD', $this->getLocalCVSPath()));
689
+	}
690
+
691
+	/**
692
+	 * Setup a job to clone a git repository.
693
+	 * @return string resque token
694
+	 */
695
+	public function cloneRepo()
696
+	{
697
+		// Avoid this being called multiple times in the same request
698
+		if (!isset(self::$has_cloned_cache[$this->ID])) {
699
+			$fetch = DNGitFetch::create();
700
+			$fetch->ProjectID = $this->ID;
701
+			$fetch->write();
702
+
703
+			// passing true here tells DNGitFetch to force a git clone, otherwise
704
+			// it will just update the repo if it already exists. We want to ensure
705
+			// we're always cloning a new repo in this case, as the git URL may have changed.
706
+			$fetch->start(true);
707
+
708
+			self::$has_cloned_cache[$this->ID] = true;
709
+		}
710
+	}
711
+
712
+	/**
713
+	 * @return string
714
+	 */
715
+	public function getLocalCVSPath()
716
+	{
717
+		return sprintf('%s/%s', DEPLOYNAUT_LOCAL_VCS_PATH, $this->Name);
718
+	}
719
+
720
+	public function onBeforeWrite()
721
+	{
722
+		parent::onBeforeWrite();
723
+
724
+		if ($this->CreateEnvFolder && !file_exists($this->getProjectFolderPath())) {
725
+			mkdir($this->getProjectFolderPath());
726
+		}
727
+	}
728
+
729
+	public function onAfterWrite()
730
+	{
731
+		parent::onAfterWrite();
732
+
733
+		if (!$this->CVSPath) {
734
+			return;
735
+		}
736
+
737
+		$changedFields = $this->getChangedFields(true, 2);
738
+		if (isset($changedFields['CVSPath']) || isset($changedFields['Name'])) {
739
+			$this->cloneRepo();
740
+		}
741
+	}
742
+
743
+	/**
744
+	 * Delete related environments and folders
745
+	 */
746
+	public function onAfterDelete()
747
+	{
748
+		parent::onAfterDelete();
749
+
750
+		// Delete related environments
751
+		foreach ($this->Environments() as $env) {
752
+			$env->delete();
753
+		}
754
+
755
+		// Delete local repository
756
+		if (file_exists($this->getLocalCVSPath())) {
757
+			Filesystem::removeFolder($this->getLocalCVSPath());
758
+		}
759
+
760
+		// Delete project template
761
+		if (file_exists($this->getProjectFolderPath()) && Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
762
+			Filesystem::removeFolder($this->getProjectFolderPath());
763
+		}
764
+
765
+		// Delete the deploy key
766
+		if (file_exists($this->getKeyDir())) {
767
+			Filesystem::removeFolder($this->getKeyDir());
768
+		}
769
+	}
770
+
771
+	/**
772
+	 * Fetch the public key for this project.
773
+	 *
774
+	 * @return string|void
775
+	 */
776
+	public function getPublicKey()
777
+	{
778
+		$key = $this->getPublicKeyPath();
779
+
780
+		if (file_exists($key)) {
781
+			return trim(file_get_contents($key));
782
+		}
783
+	}
784
+
785
+	/**
786
+	 * This returns that path of the public key if a key directory is set. It doesn't check whether the file exists.
787
+	 *
788
+	 * @return string|null
789
+	 */
790
+	public function getPublicKeyPath()
791
+	{
792
+		if ($privateKey = $this->getPrivateKeyPath()) {
793
+			return $privateKey . '.pub';
794
+		}
795
+		return null;
796
+	}
797
+
798
+	/**
799
+	 * This returns that path of the private key if a key directory is set. It doesn't check whether the file exists.
800
+	 *
801
+	 * @return string|null
802
+	 */
803
+	public function getPrivateKeyPath()
804
+	{
805
+		$keyDir = $this->getKeyDir();
806
+		if (!empty($keyDir)) {
807
+			$filter = FileNameFilter::create();
808
+			$name = $filter->filter($this->Name);
809
+			return $keyDir . '/' . $name;
810
+		}
811
+		return null;
812
+	}
813
+
814
+	/**
815
+	 * Returns the location of the projects key dir if one exists.
816
+	 *
817
+	 * @return string|null
818
+	 */
819
+	public function getKeyDir()
820
+	{
821
+		$keyDir = $this->DNData()->getKeyDir();
822
+		if (!$keyDir) {
823
+			return null;
824
+		}
825
+
826
+		$filter = FileNameFilter::create();
827
+		$name = $filter->filter($this->Name);
828
+
829
+		return $this->DNData()->getKeyDir() . '/' . $name;
830
+	}
831
+
832
+	/**
833
+	 * Setup a gridfield for the environment configs
834
+	 *
835
+	 * @param FieldList $fields
836
+	 * @param GridField $environments
837
+	 */
838
+	protected function setEnvironmentFields(&$fields, $environments)
839
+	{
840
+		if (!$environments) {
841
+			return;
842
+		}
843
+
844
+		$environments->getConfig()->addComponent(new GridFieldAddNewMultiClass());
845
+		$environments->getConfig()->removeComponentsByType('GridFieldAddNewButton');
846
+		$environments->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
847
+		$environments->getConfig()->removeComponentsByType('GridFieldDeleteAction');
848
+		$environments->getConfig()->removeComponentsByType('GridFieldPageCount');
849
+		if (Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
850
+			$addNewRelease = new GridFieldAddNewButton('toolbar-header-right');
851
+			$addNewRelease->setButtonName('Add');
852
+			$environments->getConfig()->addComponent($addNewRelease);
853
+		}
854
+
855
+		$fields->addFieldToTab("Root.Main", $environments);
856
+	}
857
+
858
+	/**
859
+	 * Provide current repository URL to the users.
860
+	 *
861
+	 * @return void|string
862
+	 */
863
+	public function getRepositoryURL()
864
+	{
865
+		$showUrl = Config::inst()->get($this->class, 'show_repository_url');
866
+		if ($showUrl) {
867
+			return $this->CVSPath;
868
+		}
869
+	}
870
+
871
+	/**
872
+	 * Whitelist configuration that describes how to convert a repository URL into a link
873
+	 * to a web user interface for that URL
874
+	 *
875
+	 * Consists of a hash of "full.lower.case.domain" => {configuration} key/value pairs
876
+	 *
877
+	 * {configuration} can either be boolean true to auto-detect both the host and the
878
+	 * name of the UI provider, or a nested array that overrides either one or both
879
+	 * of the auto-detected valyes
880
+	 *
881
+	 * @var array
882
+	 */
883
+	private static $repository_interfaces = array(
884
+		'github.com' => array(
885
+			'icon' => 'deploynaut/img/github.png',
886
+			'name' => 'Github.com',
887
+		),
888
+		'bitbucket.org' => array(
889
+			'commit' => 'commits',
890
+			'name' => 'Bitbucket.org',
891
+		),
892
+		'repo.or.cz' => array(
893
+			'scheme' => 'http',
894
+			'name' => 'repo.or.cz',
895
+			'regex' => array('^(.*)$' => '/w$1'),
896
+		),
897
+
898
+		/* Example for adding your own gitlab repository and override all auto-detected values (with their defaults)
899 899
         'gitlab.mysite.com' => array(
900 900
             'icon' => 'deploynaut/img/git.png',
901 901
             'host' => 'gitlab.mysite.com',
@@ -904,280 +904,280 @@  discard block
 block discarded – undo
904 904
             'commit' => "commit"
905 905
         ),
906 906
         */
907
-    );
908
-
909
-    /**
910
-     * Get a ViewableData structure describing the UI tool that lets the user view the repository code
911
-     *
912
-     * @return ArrayData
913
-     */
914
-    public function getRepositoryInterface()
915
-    {
916
-        $interfaces = $this->config()->repository_interfaces;
917
-
918
-        /* Look for each whitelisted hostname */
919
-        foreach ($interfaces as $host => $interface) {
920
-            /* See if the CVS Path is for this hostname, followed by some junk (maybe a port), then the path */
921
-            if (preg_match('{^[^.]*' . $host . '(.*?)([/a-zA-Z].+)}', $this->CVSPath, $match)) {
922
-                $path = $match[2];
923
-
924
-                $scheme = isset($interface['scheme']) ? $interface['scheme'] : 'https';
925
-                $host = isset($interface['host']) ? $interface['host'] : $host;
926
-                $regex = isset($interface['regex']) ? $interface['regex'] : array('\.git$' => '');
927
-
928
-                $components = explode('.', $host);
929
-
930
-                foreach ($regex as $pattern => $replacement) {
931
-                    $path = preg_replace('/' . $pattern . '/', $replacement, $path);
932
-                }
933
-
934
-                $uxurl = Controller::join_links($scheme . '://', $host, $path);
935
-
936
-                if (array_key_exists('commit', $interface) && $interface['commit'] == false) {
937
-                    $commiturl = false;
938
-                } else {
939
-                    $commiturl = Controller::join_links(
940
-                        $uxurl,
941
-                        isset($interface['commit']) ? $interface['commit'] : 'commit'
942
-                    );
943
-                }
944
-
945
-                return new ArrayData(array(
946
-                    'Name'      => isset($interface['name']) ? $interface['name'] : ucfirst($components[0]),
947
-                    'Icon'      => isset($interface['icon']) ? $interface['icon'] : 'deploynaut/img/git.png',
948
-                    'URL'       => $uxurl,
949
-                    'CommitURL' => $commiturl
950
-                ));
951
-            }
952
-        }
953
-    }
954
-
955
-    /**
956
-     * @return string
957
-     */
958
-    protected function getProjectFolderPath()
959
-    {
960
-        return sprintf('%s/%s', $this->DNData()->getEnvironmentDir(), $this->Name);
961
-    }
962
-
963
-    /**
964
-     * Convenience wrapper for a single permission code.
965
-     *
966
-     * @param string $code
967
-     * @return SS_List
968
-     */
969
-    public function whoIsAllowed($code)
970
-    {
971
-        return $this->whoIsAllowedAny(array($code));
972
-    }
973
-
974
-    /**
975
-     * List members who have $codes on this project.
976
-     * Does not support Permission::DENY_PERMISSION malarky, same as Permission::get_groups_by_permission anyway...
977
-     *
978
-     * @param array|string $codes
979
-     * @return SS_List
980
-     */
981
-    public function whoIsAllowedAny($codes)
982
-    {
983
-        if (!is_array($codes)) {
984
-            $codes = array($codes);
985
-        }
986
-
987
-        $SQLa_codes = Convert::raw2sql($codes);
988
-        $SQL_codes = join("','", $SQLa_codes);
989
-
990
-        return DataObject::get('Member')
991
-            ->where("\"PermissionRoleCode\".\"Code\" IN ('$SQL_codes') OR \"Permission\".\"Code\" IN ('$SQL_codes')")
992
-            ->filter("DNProject_Viewers.DNProjectID", $this->ID)
993
-            ->leftJoin('Group_Members', "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"")
994
-            ->leftJoin('Group', "\"Group_Members\".\"GroupID\" = \"Group\".\"ID\"")
995
-            ->leftJoin('DNProject_Viewers', "\"DNProject_Viewers\".\"GroupID\" = \"Group\".\"ID\"")
996
-            ->leftJoin('Permission', "\"Permission\".\"GroupID\" = \"Group\".\"ID\"")
997
-            ->leftJoin('Group_Roles', "\"Group_Roles\".\"GroupID\" = \"Group\".\"ID\"")
998
-            ->leftJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\" = \"PermissionRole\".\"ID\"")
999
-            ->leftJoin('PermissionRoleCode', "\"PermissionRoleCode\".\"RoleID\" = \"PermissionRole\".\"ID\"");
1000
-    }
1001
-
1002
-    /**
1003
-     * Convenience wrapper for a single permission code.
1004
-     *
1005
-     * @param string $code
1006
-     * @param Member|null $member
1007
-     *
1008
-     * @return bool
1009
-     */
1010
-    public function allowed($code, $member = null)
1011
-    {
1012
-        return $this->allowedAny(array($code), $member);
1013
-    }
1014
-
1015
-    /**
1016
-     * Checks if a group is allowed to the project and the permission code
1017
-     *
1018
-     * @param string $permissionCode
1019
-     * @param Group $group
1020
-     *
1021
-     * @return bool
1022
-     */
1023
-    public function groupAllowed($permissionCode, Group $group)
1024
-    {
1025
-        $viewers = $this->Viewers();
1026
-        if (!$viewers->find('ID', $group->ID)) {
1027
-            return false;
1028
-        }
1029
-        $groups = Permission::get_groups_by_permission($permissionCode);
1030
-        if (!$groups->find('ID', $group->ID)) {
1031
-            return false;
1032
-        }
1033
-        return true;
1034
-    }
1035
-
1036
-    /**
1037
-     * Check if member has a permission code in this project.
1038
-     *
1039
-     * @param array|string $codes
1040
-     * @param Member|null $member
1041
-     *
1042
-     * @return bool
1043
-     */
1044
-    public function allowedAny($codes, $member = null)
1045
-    {
1046
-        if (!$member) {
1047
-            $member = Member::currentUser();
1048
-        }
1049
-
1050
-        if (Permission::checkMember($member, 'ADMIN')) {
1051
-            return true;
1052
-        }
1053
-
1054
-        $hits = $this->whoIsAllowedAny($codes)->filter('Member.ID', $member->ID)->count();
1055
-        return ($hits>0 ? true : false);
1056
-    }
1057
-
1058
-    /**
1059
-     * Checks if the environment has been fully built.
1060
-     *
1061
-     * @return bool
1062
-     */
1063
-    public function isProjectReady()
1064
-    {
1065
-        if ($this->getRunningInitialEnvironmentCreations()->count() > 0) {
1066
-            // We're still creating the initial environments for this project so we're
1067
-            // not quite done
1068
-            return false;
1069
-        }
1070
-
1071
-        // Provide a hook for further checks. Logic stolen from
1072
-        // {@see DataObject::extendedCan()}
1073
-        $isDone = $this->extend('isProjectReady');
1074
-        if ($isDone && is_array($isDone)) {
1075
-            $isDone = array_filter($isDone, function ($val) {
1076
-                return !is_null($val);
1077
-            });
1078
-
1079
-            // If anything returns false then we're not ready.
1080
-            if ($isDone) {
1081
-                return min($isDone);
1082
-            }
1083
-        }
1084
-
1085
-        return true;
1086
-    }
1087
-
1088
-    /**
1089
-     * Returns a list of environments still being created.
1090
-     *
1091
-     * @return SS_List
1092
-     */
1093
-    public function getRunningEnvironmentCreations()
1094
-    {
1095
-        return $this->CreateEnvironments()
1096
-            ->filter('Status', ['Queued', 'Started']);
1097
-    }
1098
-
1099
-    /**
1100
-     * Returns a list of initial environments created for this project.
1101
-     *
1102
-     * @return DataList
1103
-     */
1104
-    public function getInitialEnvironmentCreations()
1105
-    {
1106
-        return $this->CreateEnvironments()->filter('IsInitialEnvironment', true);
1107
-    }
1108
-
1109
-    /**
1110
-     * Only returns initial environments that are being created.
1111
-     *
1112
-     * @return DataList
1113
-     */
1114
-    public function getRunningInitialEnvironmentCreations()
1115
-    {
1116
-        return $this->getInitialEnvironmentCreations()
1117
-            ->filter('Status', ['Queued', 'Started']);
1118
-    }
1119
-
1120
-    /**
1121
-     * Returns a list of completed initial environment creations. This includes failed tasks.
1122
-     *
1123
-     * @return DataList
1124
-     */
1125
-    public function getCompleteInitialEnvironmentCreations()
1126
-    {
1127
-        return $this->getInitialEnvironmentCreations()
1128
-            ->exclude('Status', ['Queued', 'Started']);
1129
-    }
1130
-
1131
-    /**
1132
-     * @return ValidationResult
1133
-     */
1134
-    protected function validate()
1135
-    {
1136
-        $validation = parent::validate();
1137
-        if ($validation->valid()) {
1138
-            if (empty($this->Name)) {
1139
-                return $validation->error('The stack must have a name.');
1140
-            }
1141
-
1142
-            // The name is used to build filepaths so should be restricted
1143
-            if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\_]+$/', $this->Name)) {
1144
-                return $validation->error('Project name can only contain alphanumeric, hyphens and underscores.');
1145
-            }
1146
-
1147
-            if (empty($this->CVSPath)) {
1148
-                return $validation->error('You must provide a repository URL.');
1149
-            }
1150
-
1151
-            $existing = DNProject::get()->filter('Name', $this->Name);
1152
-            if ($this->ID) {
1153
-                $existing = $existing->exclude('ID', $this->ID);
1154
-            }
1155
-            if ($existing->count() > 0) {
1156
-                return $validation->error('A stack already exists with that name.');
1157
-            }
1158
-        }
1159
-        return $validation;
1160
-    }
1161
-
1162
-    /**
1163
-     * @param Member $member
1164
-     *
1165
-     * @return bool
1166
-     */
1167
-    public function canCreate($member = null)
1168
-    {
1169
-        if (!$member) {
1170
-            $member = Member::currentUser();
1171
-        }
1172
-        if (!$member) {
1173
-            return false;
1174
-        }
1175
-
1176
-        if (Permission::checkMember($member, 'ADMIN')) {
1177
-            return true;
1178
-        }
1179
-
1180
-        // This calls canCreate on extensions.
1181
-        return parent::canCreate($member);
1182
-    }
907
+	);
908
+
909
+	/**
910
+	 * Get a ViewableData structure describing the UI tool that lets the user view the repository code
911
+	 *
912
+	 * @return ArrayData
913
+	 */
914
+	public function getRepositoryInterface()
915
+	{
916
+		$interfaces = $this->config()->repository_interfaces;
917
+
918
+		/* Look for each whitelisted hostname */
919
+		foreach ($interfaces as $host => $interface) {
920
+			/* See if the CVS Path is for this hostname, followed by some junk (maybe a port), then the path */
921
+			if (preg_match('{^[^.]*' . $host . '(.*?)([/a-zA-Z].+)}', $this->CVSPath, $match)) {
922
+				$path = $match[2];
923
+
924
+				$scheme = isset($interface['scheme']) ? $interface['scheme'] : 'https';
925
+				$host = isset($interface['host']) ? $interface['host'] : $host;
926
+				$regex = isset($interface['regex']) ? $interface['regex'] : array('\.git$' => '');
927
+
928
+				$components = explode('.', $host);
929
+
930
+				foreach ($regex as $pattern => $replacement) {
931
+					$path = preg_replace('/' . $pattern . '/', $replacement, $path);
932
+				}
933
+
934
+				$uxurl = Controller::join_links($scheme . '://', $host, $path);
935
+
936
+				if (array_key_exists('commit', $interface) && $interface['commit'] == false) {
937
+					$commiturl = false;
938
+				} else {
939
+					$commiturl = Controller::join_links(
940
+						$uxurl,
941
+						isset($interface['commit']) ? $interface['commit'] : 'commit'
942
+					);
943
+				}
944
+
945
+				return new ArrayData(array(
946
+					'Name'      => isset($interface['name']) ? $interface['name'] : ucfirst($components[0]),
947
+					'Icon'      => isset($interface['icon']) ? $interface['icon'] : 'deploynaut/img/git.png',
948
+					'URL'       => $uxurl,
949
+					'CommitURL' => $commiturl
950
+				));
951
+			}
952
+		}
953
+	}
954
+
955
+	/**
956
+	 * @return string
957
+	 */
958
+	protected function getProjectFolderPath()
959
+	{
960
+		return sprintf('%s/%s', $this->DNData()->getEnvironmentDir(), $this->Name);
961
+	}
962
+
963
+	/**
964
+	 * Convenience wrapper for a single permission code.
965
+	 *
966
+	 * @param string $code
967
+	 * @return SS_List
968
+	 */
969
+	public function whoIsAllowed($code)
970
+	{
971
+		return $this->whoIsAllowedAny(array($code));
972
+	}
973
+
974
+	/**
975
+	 * List members who have $codes on this project.
976
+	 * Does not support Permission::DENY_PERMISSION malarky, same as Permission::get_groups_by_permission anyway...
977
+	 *
978
+	 * @param array|string $codes
979
+	 * @return SS_List
980
+	 */
981
+	public function whoIsAllowedAny($codes)
982
+	{
983
+		if (!is_array($codes)) {
984
+			$codes = array($codes);
985
+		}
986
+
987
+		$SQLa_codes = Convert::raw2sql($codes);
988
+		$SQL_codes = join("','", $SQLa_codes);
989
+
990
+		return DataObject::get('Member')
991
+			->where("\"PermissionRoleCode\".\"Code\" IN ('$SQL_codes') OR \"Permission\".\"Code\" IN ('$SQL_codes')")
992
+			->filter("DNProject_Viewers.DNProjectID", $this->ID)
993
+			->leftJoin('Group_Members', "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"")
994
+			->leftJoin('Group', "\"Group_Members\".\"GroupID\" = \"Group\".\"ID\"")
995
+			->leftJoin('DNProject_Viewers', "\"DNProject_Viewers\".\"GroupID\" = \"Group\".\"ID\"")
996
+			->leftJoin('Permission', "\"Permission\".\"GroupID\" = \"Group\".\"ID\"")
997
+			->leftJoin('Group_Roles', "\"Group_Roles\".\"GroupID\" = \"Group\".\"ID\"")
998
+			->leftJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\" = \"PermissionRole\".\"ID\"")
999
+			->leftJoin('PermissionRoleCode', "\"PermissionRoleCode\".\"RoleID\" = \"PermissionRole\".\"ID\"");
1000
+	}
1001
+
1002
+	/**
1003
+	 * Convenience wrapper for a single permission code.
1004
+	 *
1005
+	 * @param string $code
1006
+	 * @param Member|null $member
1007
+	 *
1008
+	 * @return bool
1009
+	 */
1010
+	public function allowed($code, $member = null)
1011
+	{
1012
+		return $this->allowedAny(array($code), $member);
1013
+	}
1014
+
1015
+	/**
1016
+	 * Checks if a group is allowed to the project and the permission code
1017
+	 *
1018
+	 * @param string $permissionCode
1019
+	 * @param Group $group
1020
+	 *
1021
+	 * @return bool
1022
+	 */
1023
+	public function groupAllowed($permissionCode, Group $group)
1024
+	{
1025
+		$viewers = $this->Viewers();
1026
+		if (!$viewers->find('ID', $group->ID)) {
1027
+			return false;
1028
+		}
1029
+		$groups = Permission::get_groups_by_permission($permissionCode);
1030
+		if (!$groups->find('ID', $group->ID)) {
1031
+			return false;
1032
+		}
1033
+		return true;
1034
+	}
1035
+
1036
+	/**
1037
+	 * Check if member has a permission code in this project.
1038
+	 *
1039
+	 * @param array|string $codes
1040
+	 * @param Member|null $member
1041
+	 *
1042
+	 * @return bool
1043
+	 */
1044
+	public function allowedAny($codes, $member = null)
1045
+	{
1046
+		if (!$member) {
1047
+			$member = Member::currentUser();
1048
+		}
1049
+
1050
+		if (Permission::checkMember($member, 'ADMIN')) {
1051
+			return true;
1052
+		}
1053
+
1054
+		$hits = $this->whoIsAllowedAny($codes)->filter('Member.ID', $member->ID)->count();
1055
+		return ($hits>0 ? true : false);
1056
+	}
1057
+
1058
+	/**
1059
+	 * Checks if the environment has been fully built.
1060
+	 *
1061
+	 * @return bool
1062
+	 */
1063
+	public function isProjectReady()
1064
+	{
1065
+		if ($this->getRunningInitialEnvironmentCreations()->count() > 0) {
1066
+			// We're still creating the initial environments for this project so we're
1067
+			// not quite done
1068
+			return false;
1069
+		}
1070
+
1071
+		// Provide a hook for further checks. Logic stolen from
1072
+		// {@see DataObject::extendedCan()}
1073
+		$isDone = $this->extend('isProjectReady');
1074
+		if ($isDone && is_array($isDone)) {
1075
+			$isDone = array_filter($isDone, function ($val) {
1076
+				return !is_null($val);
1077
+			});
1078
+
1079
+			// If anything returns false then we're not ready.
1080
+			if ($isDone) {
1081
+				return min($isDone);
1082
+			}
1083
+		}
1084
+
1085
+		return true;
1086
+	}
1087
+
1088
+	/**
1089
+	 * Returns a list of environments still being created.
1090
+	 *
1091
+	 * @return SS_List
1092
+	 */
1093
+	public function getRunningEnvironmentCreations()
1094
+	{
1095
+		return $this->CreateEnvironments()
1096
+			->filter('Status', ['Queued', 'Started']);
1097
+	}
1098
+
1099
+	/**
1100
+	 * Returns a list of initial environments created for this project.
1101
+	 *
1102
+	 * @return DataList
1103
+	 */
1104
+	public function getInitialEnvironmentCreations()
1105
+	{
1106
+		return $this->CreateEnvironments()->filter('IsInitialEnvironment', true);
1107
+	}
1108
+
1109
+	/**
1110
+	 * Only returns initial environments that are being created.
1111
+	 *
1112
+	 * @return DataList
1113
+	 */
1114
+	public function getRunningInitialEnvironmentCreations()
1115
+	{
1116
+		return $this->getInitialEnvironmentCreations()
1117
+			->filter('Status', ['Queued', 'Started']);
1118
+	}
1119
+
1120
+	/**
1121
+	 * Returns a list of completed initial environment creations. This includes failed tasks.
1122
+	 *
1123
+	 * @return DataList
1124
+	 */
1125
+	public function getCompleteInitialEnvironmentCreations()
1126
+	{
1127
+		return $this->getInitialEnvironmentCreations()
1128
+			->exclude('Status', ['Queued', 'Started']);
1129
+	}
1130
+
1131
+	/**
1132
+	 * @return ValidationResult
1133
+	 */
1134
+	protected function validate()
1135
+	{
1136
+		$validation = parent::validate();
1137
+		if ($validation->valid()) {
1138
+			if (empty($this->Name)) {
1139
+				return $validation->error('The stack must have a name.');
1140
+			}
1141
+
1142
+			// The name is used to build filepaths so should be restricted
1143
+			if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\_]+$/', $this->Name)) {
1144
+				return $validation->error('Project name can only contain alphanumeric, hyphens and underscores.');
1145
+			}
1146
+
1147
+			if (empty($this->CVSPath)) {
1148
+				return $validation->error('You must provide a repository URL.');
1149
+			}
1150
+
1151
+			$existing = DNProject::get()->filter('Name', $this->Name);
1152
+			if ($this->ID) {
1153
+				$existing = $existing->exclude('ID', $this->ID);
1154
+			}
1155
+			if ($existing->count() > 0) {
1156
+				return $validation->error('A stack already exists with that name.');
1157
+			}
1158
+		}
1159
+		return $validation;
1160
+	}
1161
+
1162
+	/**
1163
+	 * @param Member $member
1164
+	 *
1165
+	 * @return bool
1166
+	 */
1167
+	public function canCreate($member = null)
1168
+	{
1169
+		if (!$member) {
1170
+			$member = Member::currentUser();
1171
+		}
1172
+		if (!$member) {
1173
+			return false;
1174
+		}
1175
+
1176
+		if (Permission::checkMember($member, 'ADMIN')) {
1177
+			return true;
1178
+		}
1179
+
1180
+		// This calls canCreate on extensions.
1181
+		return parent::canCreate($member);
1182
+	}
1183 1183
 }
Please login to merge, or discard this patch.
Spacing   +73 added lines, -73 removed lines patch added patch discarded remove patch
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 
112 112
         // add the administrators group as the viewers of the new project
113 113
         $adminGroup = Group::get()->filter('Code', 'administrators')->first();
114
-        if ($adminGroup && $adminGroup->exists()) {
114
+        if($adminGroup && $adminGroup->exists()) {
115 115
             $project->Viewers()->add($adminGroup);
116 116
         }
117 117
         return $project;
@@ -127,8 +127,8 @@  discard block
 block discarded – undo
127 127
     {
128 128
         $size = 0;
129 129
 
130
-        foreach ($this->Environments() as $environment) {
131
-            foreach ($environment->DataArchives()->filter('IsBackup', 0) as $archive) {
130
+        foreach($this->Environments() as $environment) {
131
+            foreach($environment->DataArchives()->filter('IsBackup', 0) as $archive) {
132 132
                 $size += $archive->ArchiveFile()->getAbsoluteSize();
133 133
             }
134 134
         }
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
     {
149 149
         $size = $this->getField('DiskQuotaMB');
150 150
 
151
-        if (empty($size)) {
151
+        if(empty($size)) {
152 152
             $defaults = $this->config()->get('defaults');
153 153
             $size = (isset($defaults['DiskQuotaMB'])) ? $defaults['DiskQuotaMB'] : 0;
154 154
         }
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
     public function DiskQuotaUsagePercent()
185 185
     {
186 186
         $quota = $this->getDiskQuotaMB();
187
-        if ($quota > 0) {
187
+        if($quota > 0) {
188 188
             return $this->getUsedQuotaMB() * 100 / $quota;
189 189
         }
190 190
         return 100;
@@ -202,7 +202,7 @@  discard block
 block discarded – undo
202 202
         $controller = Controller::curr();
203 203
         $actionType = $controller->getField('CurrentActionType');
204 204
 
205
-        if (DNRoot::FlagSnapshotsEnabled() && $this->isProjectReady()) {
205
+        if(DNRoot::FlagSnapshotsEnabled() && $this->isProjectReady()) {
206 206
             $list->push(new ArrayData(array(
207 207
                 'Link' => sprintf('naut/project/%s/snapshots', $this->Name),
208 208
                 'Title' => 'Snapshots',
@@ -257,11 +257,11 @@  discard block
 block discarded – undo
257 257
      */
258 258
     public function canView($member = null)
259 259
     {
260
-        if (!$member) {
260
+        if(!$member) {
261 261
             $member = Member::currentUser();
262 262
         }
263 263
 
264
-        if (Permission::checkMember($member, 'ADMIN')) {
264
+        if(Permission::checkMember($member, 'ADMIN')) {
265 265
             return true;
266 266
         }
267 267
 
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
      */
276 276
     public function canRestore($member = null)
277 277
     {
278
-        if ($this->allowedAny(
278
+        if($this->allowedAny(
279 279
             array(
280 280
                 DNRoot::ALLOW_PROD_SNAPSHOT,
281 281
                 DNRoot::ALLOW_NON_PROD_SNAPSHOT
@@ -285,7 +285,7 @@  discard block
 block discarded – undo
285 285
             return true;
286 286
         }
287 287
 
288
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
288
+        return (bool)$this->Environments()->filterByCallback(function($env) use ($member) {
289 289
             return $env->canRestore($member);
290 290
         })->Count();
291 291
     }
@@ -296,7 +296,7 @@  discard block
 block discarded – undo
296 296
      */
297 297
     public function canBackup($member = null)
298 298
     {
299
-        if ($this->allowedAny(
299
+        if($this->allowedAny(
300 300
             array(
301 301
                 DNRoot::ALLOW_PROD_SNAPSHOT,
302 302
                 DNRoot::ALLOW_NON_PROD_SNAPSHOT
@@ -306,7 +306,7 @@  discard block
 block discarded – undo
306 306
             return true;
307 307
         }
308 308
 
309
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
309
+        return (bool)$this->Environments()->filterByCallback(function($env) use ($member) {
310 310
             return $env->canBackup($member);
311 311
         })->Count();
312 312
     }
@@ -317,7 +317,7 @@  discard block
 block discarded – undo
317 317
      */
318 318
     public function canUploadArchive($member = null)
319 319
     {
320
-        if ($this->allowedAny(
320
+        if($this->allowedAny(
321 321
             array(
322 322
                 DNRoot::ALLOW_PROD_SNAPSHOT,
323 323
                 DNRoot::ALLOW_NON_PROD_SNAPSHOT
@@ -327,7 +327,7 @@  discard block
 block discarded – undo
327 327
             return true;
328 328
         }
329 329
 
330
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
330
+        return (bool)$this->Environments()->filterByCallback(function($env) use ($member) {
331 331
             return $env->canUploadArchive($member);
332 332
         })->Count();
333 333
     }
@@ -338,7 +338,7 @@  discard block
 block discarded – undo
338 338
      */
339 339
     public function canDownloadArchive($member = null)
340 340
     {
341
-        if ($this->allowedAny(
341
+        if($this->allowedAny(
342 342
             array(
343 343
                 DNRoot::ALLOW_PROD_SNAPSHOT,
344 344
                 DNRoot::ALLOW_NON_PROD_SNAPSHOT
@@ -348,7 +348,7 @@  discard block
 block discarded – undo
348 348
             return true;
349 349
         }
350 350
 
351
-        return (bool)$this->Environments()->filterByCallback(function ($env) use ($member) {
351
+        return (bool)$this->Environments()->filterByCallback(function($env) use ($member) {
352 352
             return $env->canDownloadArchive($member);
353 353
         })->Count();
354 354
     }
@@ -366,9 +366,9 @@  discard block
 block discarded – undo
366 366
     public function canCreateEnvironments($member = null)
367 367
     {
368 368
         $envType = $this->AllowedEnvironmentType;
369
-        if ($envType) {
369
+        if($envType) {
370 370
             $env = Injector::inst()->get($envType);
371
-            if ($env instanceof EnvironmentCreateBackend) {
371
+            if($env instanceof EnvironmentCreateBackend) {
372 372
                 return $this->allowed(DNRoot::ALLOW_CREATE_ENVIRONMENT, $member);
373 373
             }
374 374
         }
@@ -407,7 +407,7 @@  discard block
 block discarded – undo
407 407
      */
408 408
     public function getProcessEnv()
409 409
     {
410
-        if (file_exists($this->getPrivateKeyPath())) {
410
+        if(file_exists($this->getPrivateKeyPath())) {
411 411
             // Key-pair is available, use it.
412 412
             $processEnv = array(
413 413
                 'IDENT_KEY' => $this->getPrivateKeyPath(),
@@ -456,7 +456,7 @@  discard block
 block discarded – undo
456 456
      */
457 457
     public function DNBranchList()
458 458
     {
459
-        if ($this->CVSPath && !$this->repoExists()) {
459
+        if($this->CVSPath && !$this->repoExists()) {
460 460
             $this->cloneRepo();
461 461
         }
462 462
         return DNBranchList::create($this, $this->DNData());
@@ -469,7 +469,7 @@  discard block
 block discarded – undo
469 469
      */
470 470
     public function DNTagList()
471 471
     {
472
-        if ($this->CVSPath && !$this->repoExists()) {
472
+        if($this->CVSPath && !$this->repoExists()) {
473 473
             $this->cloneRepo();
474 474
         }
475 475
         return DNReferenceList::create($this, $this->DNData(), null, null, true);
@@ -480,7 +480,7 @@  discard block
 block discarded – undo
480 480
      */
481 481
     public function getRepository()
482 482
     {
483
-        if (!$this->repoExists()) {
483
+        if(!$this->repoExists()) {
484 484
             return false;
485 485
         }
486 486
 
@@ -495,17 +495,17 @@  discard block
 block discarded – undo
495 495
      */
496 496
     public function DNEnvironmentList()
497 497
     {
498
-        if (!self::$_current_member_cache) {
498
+        if(!self::$_current_member_cache) {
499 499
             self::$_current_member_cache = Member::currentUser();
500 500
         }
501 501
 
502
-        if (self::$_current_member_cache === false) {
502
+        if(self::$_current_member_cache === false) {
503 503
             return new ArrayList();
504 504
         }
505 505
 
506 506
         $currentMember = self::$_current_member_cache;
507 507
         return $this->Environments()
508
-            ->filterByCallBack(function ($item) use ($currentMember) {
508
+            ->filterByCallBack(function($item) use ($currentMember) {
509 509
                 return $item->canView($currentMember);
510 510
             });
511 511
     }
@@ -526,14 +526,14 @@  discard block
 block discarded – undo
526 526
      */
527 527
     public function currentBuilds()
528 528
     {
529
-        if (!isset(self::$relation_cache['currentBuilds.'.$this->ID])) {
529
+        if(!isset(self::$relation_cache['currentBuilds.' . $this->ID])) {
530 530
             $currentBuilds = array();
531
-            foreach ($this->Environments() as $env) {
531
+            foreach($this->Environments() as $env) {
532 532
                 $currentBuilds[$env->Name] = $env->CurrentBuild();
533 533
             }
534
-            self::$relation_cache['currentBuilds.'.$this->ID] = $currentBuilds;
534
+            self::$relation_cache['currentBuilds.' . $this->ID] = $currentBuilds;
535 535
         }
536
-        return self::$relation_cache['currentBuilds.'.$this->ID];
536
+        return self::$relation_cache['currentBuilds.' . $this->ID];
537 537
     }
538 538
 
539 539
     /**
@@ -550,7 +550,7 @@  discard block
 block discarded – undo
550 550
      */
551 551
     public function CreateEnvironmentLink()
552 552
     {
553
-        if ($this->canCreateEnvironments()) {
553
+        if($this->canCreateEnvironments()) {
554 554
             return $this->Link('createenv');
555 555
         }
556 556
         return null;
@@ -570,11 +570,11 @@  discard block
 block discarded – undo
570 570
     public function IsStarred()
571 571
     {
572 572
         $member = Member::currentUser();
573
-        if ($member === null) {
573
+        if($member === null) {
574 574
             return false;
575 575
         }
576 576
         $favourited = $this->StarredBy()->filter('MemberID', $member->ID);
577
-        if ($favourited->count() == 0) {
577
+        if($favourited->count() == 0) {
578 578
             return false;
579 579
         }
580 580
         return true;
@@ -631,7 +631,7 @@  discard block
 block discarded – undo
631 631
 
632 632
         $environmentTypes = ClassInfo::implementorsOf('EnvironmentCreateBackend');
633 633
         $types = array();
634
-        foreach ($environmentTypes as $type) {
634
+        foreach($environmentTypes as $type) {
635 635
             $types[$type] = $type;
636 636
         }
637 637
 
@@ -656,11 +656,11 @@  discard block
 block discarded – undo
656 656
     public function setCreateProjectFolderField(&$fields)
657 657
     {
658 658
         // Check if the capistrano project folder exists
659
-        if (!$this->Name) {
659
+        if(!$this->Name) {
660 660
             return;
661 661
         }
662 662
 
663
-        if ($this->projectFolderExists()) {
663
+        if($this->projectFolderExists()) {
664 664
             return;
665 665
         }
666 666
 
@@ -695,7 +695,7 @@  discard block
 block discarded – undo
695 695
     public function cloneRepo()
696 696
     {
697 697
         // Avoid this being called multiple times in the same request
698
-        if (!isset(self::$has_cloned_cache[$this->ID])) {
698
+        if(!isset(self::$has_cloned_cache[$this->ID])) {
699 699
             $fetch = DNGitFetch::create();
700 700
             $fetch->ProjectID = $this->ID;
701 701
             $fetch->write();
@@ -721,7 +721,7 @@  discard block
 block discarded – undo
721 721
     {
722 722
         parent::onBeforeWrite();
723 723
 
724
-        if ($this->CreateEnvFolder && !file_exists($this->getProjectFolderPath())) {
724
+        if($this->CreateEnvFolder && !file_exists($this->getProjectFolderPath())) {
725 725
             mkdir($this->getProjectFolderPath());
726 726
         }
727 727
     }
@@ -730,12 +730,12 @@  discard block
 block discarded – undo
730 730
     {
731 731
         parent::onAfterWrite();
732 732
 
733
-        if (!$this->CVSPath) {
733
+        if(!$this->CVSPath) {
734 734
             return;
735 735
         }
736 736
 
737 737
         $changedFields = $this->getChangedFields(true, 2);
738
-        if (isset($changedFields['CVSPath']) || isset($changedFields['Name'])) {
738
+        if(isset($changedFields['CVSPath']) || isset($changedFields['Name'])) {
739 739
             $this->cloneRepo();
740 740
         }
741 741
     }
@@ -748,22 +748,22 @@  discard block
 block discarded – undo
748 748
         parent::onAfterDelete();
749 749
 
750 750
         // Delete related environments
751
-        foreach ($this->Environments() as $env) {
751
+        foreach($this->Environments() as $env) {
752 752
             $env->delete();
753 753
         }
754 754
 
755 755
         // Delete local repository
756
-        if (file_exists($this->getLocalCVSPath())) {
756
+        if(file_exists($this->getLocalCVSPath())) {
757 757
             Filesystem::removeFolder($this->getLocalCVSPath());
758 758
         }
759 759
 
760 760
         // Delete project template
761
-        if (file_exists($this->getProjectFolderPath()) && Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
761
+        if(file_exists($this->getProjectFolderPath()) && Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
762 762
             Filesystem::removeFolder($this->getProjectFolderPath());
763 763
         }
764 764
 
765 765
         // Delete the deploy key
766
-        if (file_exists($this->getKeyDir())) {
766
+        if(file_exists($this->getKeyDir())) {
767 767
             Filesystem::removeFolder($this->getKeyDir());
768 768
         }
769 769
     }
@@ -777,7 +777,7 @@  discard block
 block discarded – undo
777 777
     {
778 778
         $key = $this->getPublicKeyPath();
779 779
 
780
-        if (file_exists($key)) {
780
+        if(file_exists($key)) {
781 781
             return trim(file_get_contents($key));
782 782
         }
783 783
     }
@@ -789,7 +789,7 @@  discard block
 block discarded – undo
789 789
      */
790 790
     public function getPublicKeyPath()
791 791
     {
792
-        if ($privateKey = $this->getPrivateKeyPath()) {
792
+        if($privateKey = $this->getPrivateKeyPath()) {
793 793
             return $privateKey . '.pub';
794 794
         }
795 795
         return null;
@@ -803,7 +803,7 @@  discard block
 block discarded – undo
803 803
     public function getPrivateKeyPath()
804 804
     {
805 805
         $keyDir = $this->getKeyDir();
806
-        if (!empty($keyDir)) {
806
+        if(!empty($keyDir)) {
807 807
             $filter = FileNameFilter::create();
808 808
             $name = $filter->filter($this->Name);
809 809
             return $keyDir . '/' . $name;
@@ -819,7 +819,7 @@  discard block
 block discarded – undo
819 819
     public function getKeyDir()
820 820
     {
821 821
         $keyDir = $this->DNData()->getKeyDir();
822
-        if (!$keyDir) {
822
+        if(!$keyDir) {
823 823
             return null;
824 824
         }
825 825
 
@@ -837,7 +837,7 @@  discard block
 block discarded – undo
837 837
      */
838 838
     protected function setEnvironmentFields(&$fields, $environments)
839 839
     {
840
-        if (!$environments) {
840
+        if(!$environments) {
841 841
             return;
842 842
         }
843 843
 
@@ -846,7 +846,7 @@  discard block
 block discarded – undo
846 846
         $environments->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
847 847
         $environments->getConfig()->removeComponentsByType('GridFieldDeleteAction');
848 848
         $environments->getConfig()->removeComponentsByType('GridFieldPageCount');
849
-        if (Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
849
+        if(Config::inst()->get('DNEnvironment', 'allow_web_editing')) {
850 850
             $addNewRelease = new GridFieldAddNewButton('toolbar-header-right');
851 851
             $addNewRelease->setButtonName('Add');
852 852
             $environments->getConfig()->addComponent($addNewRelease);
@@ -863,7 +863,7 @@  discard block
 block discarded – undo
863 863
     public function getRepositoryURL()
864 864
     {
865 865
         $showUrl = Config::inst()->get($this->class, 'show_repository_url');
866
-        if ($showUrl) {
866
+        if($showUrl) {
867 867
             return $this->CVSPath;
868 868
         }
869 869
     }
@@ -916,9 +916,9 @@  discard block
 block discarded – undo
916 916
         $interfaces = $this->config()->repository_interfaces;
917 917
 
918 918
         /* Look for each whitelisted hostname */
919
-        foreach ($interfaces as $host => $interface) {
919
+        foreach($interfaces as $host => $interface) {
920 920
             /* See if the CVS Path is for this hostname, followed by some junk (maybe a port), then the path */
921
-            if (preg_match('{^[^.]*' . $host . '(.*?)([/a-zA-Z].+)}', $this->CVSPath, $match)) {
921
+            if(preg_match('{^[^.]*' . $host . '(.*?)([/a-zA-Z].+)}', $this->CVSPath, $match)) {
922 922
                 $path = $match[2];
923 923
 
924 924
                 $scheme = isset($interface['scheme']) ? $interface['scheme'] : 'https';
@@ -927,13 +927,13 @@  discard block
 block discarded – undo
927 927
 
928 928
                 $components = explode('.', $host);
929 929
 
930
-                foreach ($regex as $pattern => $replacement) {
930
+                foreach($regex as $pattern => $replacement) {
931 931
                     $path = preg_replace('/' . $pattern . '/', $replacement, $path);
932 932
                 }
933 933
 
934 934
                 $uxurl = Controller::join_links($scheme . '://', $host, $path);
935 935
 
936
-                if (array_key_exists('commit', $interface) && $interface['commit'] == false) {
936
+                if(array_key_exists('commit', $interface) && $interface['commit'] == false) {
937 937
                     $commiturl = false;
938 938
                 } else {
939 939
                     $commiturl = Controller::join_links(
@@ -980,7 +980,7 @@  discard block
 block discarded – undo
980 980
      */
981 981
     public function whoIsAllowedAny($codes)
982 982
     {
983
-        if (!is_array($codes)) {
983
+        if(!is_array($codes)) {
984 984
             $codes = array($codes);
985 985
         }
986 986
 
@@ -1023,11 +1023,11 @@  discard block
 block discarded – undo
1023 1023
     public function groupAllowed($permissionCode, Group $group)
1024 1024
     {
1025 1025
         $viewers = $this->Viewers();
1026
-        if (!$viewers->find('ID', $group->ID)) {
1026
+        if(!$viewers->find('ID', $group->ID)) {
1027 1027
             return false;
1028 1028
         }
1029 1029
         $groups = Permission::get_groups_by_permission($permissionCode);
1030
-        if (!$groups->find('ID', $group->ID)) {
1030
+        if(!$groups->find('ID', $group->ID)) {
1031 1031
             return false;
1032 1032
         }
1033 1033
         return true;
@@ -1043,16 +1043,16 @@  discard block
 block discarded – undo
1043 1043
      */
1044 1044
     public function allowedAny($codes, $member = null)
1045 1045
     {
1046
-        if (!$member) {
1046
+        if(!$member) {
1047 1047
             $member = Member::currentUser();
1048 1048
         }
1049 1049
 
1050
-        if (Permission::checkMember($member, 'ADMIN')) {
1050
+        if(Permission::checkMember($member, 'ADMIN')) {
1051 1051
             return true;
1052 1052
         }
1053 1053
 
1054 1054
         $hits = $this->whoIsAllowedAny($codes)->filter('Member.ID', $member->ID)->count();
1055
-        return ($hits>0 ? true : false);
1055
+        return ($hits > 0 ? true : false);
1056 1056
     }
1057 1057
 
1058 1058
     /**
@@ -1062,7 +1062,7 @@  discard block
 block discarded – undo
1062 1062
      */
1063 1063
     public function isProjectReady()
1064 1064
     {
1065
-        if ($this->getRunningInitialEnvironmentCreations()->count() > 0) {
1065
+        if($this->getRunningInitialEnvironmentCreations()->count() > 0) {
1066 1066
             // We're still creating the initial environments for this project so we're
1067 1067
             // not quite done
1068 1068
             return false;
@@ -1071,13 +1071,13 @@  discard block
 block discarded – undo
1071 1071
         // Provide a hook for further checks. Logic stolen from
1072 1072
         // {@see DataObject::extendedCan()}
1073 1073
         $isDone = $this->extend('isProjectReady');
1074
-        if ($isDone && is_array($isDone)) {
1075
-            $isDone = array_filter($isDone, function ($val) {
1074
+        if($isDone && is_array($isDone)) {
1075
+            $isDone = array_filter($isDone, function($val) {
1076 1076
                 return !is_null($val);
1077 1077
             });
1078 1078
 
1079 1079
             // If anything returns false then we're not ready.
1080
-            if ($isDone) {
1080
+            if($isDone) {
1081 1081
                 return min($isDone);
1082 1082
             }
1083 1083
         }
@@ -1134,25 +1134,25 @@  discard block
 block discarded – undo
1134 1134
     protected function validate()
1135 1135
     {
1136 1136
         $validation = parent::validate();
1137
-        if ($validation->valid()) {
1138
-            if (empty($this->Name)) {
1137
+        if($validation->valid()) {
1138
+            if(empty($this->Name)) {
1139 1139
                 return $validation->error('The stack must have a name.');
1140 1140
             }
1141 1141
 
1142 1142
             // The name is used to build filepaths so should be restricted
1143
-            if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\_]+$/', $this->Name)) {
1143
+            if(!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\-\_]+$/', $this->Name)) {
1144 1144
                 return $validation->error('Project name can only contain alphanumeric, hyphens and underscores.');
1145 1145
             }
1146 1146
 
1147
-            if (empty($this->CVSPath)) {
1147
+            if(empty($this->CVSPath)) {
1148 1148
                 return $validation->error('You must provide a repository URL.');
1149 1149
             }
1150 1150
 
1151 1151
             $existing = DNProject::get()->filter('Name', $this->Name);
1152
-            if ($this->ID) {
1152
+            if($this->ID) {
1153 1153
                 $existing = $existing->exclude('ID', $this->ID);
1154 1154
             }
1155
-            if ($existing->count() > 0) {
1155
+            if($existing->count() > 0) {
1156 1156
                 return $validation->error('A stack already exists with that name.');
1157 1157
             }
1158 1158
         }
@@ -1166,14 +1166,14 @@  discard block
 block discarded – undo
1166 1166
      */
1167 1167
     public function canCreate($member = null)
1168 1168
     {
1169
-        if (!$member) {
1169
+        if(!$member) {
1170 1170
             $member = Member::currentUser();
1171 1171
         }
1172
-        if (!$member) {
1172
+        if(!$member) {
1173 1173
             return false;
1174 1174
         }
1175 1175
 
1176
-        if (Permission::checkMember($member, 'ADMIN')) {
1176
+        if(Permission::checkMember($member, 'ADMIN')) {
1177 1177
             return true;
1178 1178
         }
1179 1179
 
Please login to merge, or discard this patch.
Braces   +63 added lines, -126 removed lines patch added patch discarded remove patch
@@ -12,8 +12,7 @@  discard block
 block discarded – undo
12 12
  * @method ManyManyList Viewers()
13 13
  * @method ManyManyList StarredBy()
14 14
  */
15
-class DNProject extends DataObject
16
-{
15
+class DNProject extends DataObject {
17 16
 
18 17
     /**
19 18
      * @var array
@@ -103,8 +102,7 @@  discard block
 block discarded – undo
103 102
      * @param string $path
104 103
      * @return \DNProject
105 104
      */
106
-    public static function create_from_path($path)
107
-    {
105
+    public static function create_from_path($path) {
108 106
         $project = DNProject::create();
109 107
         $project->Name = $path;
110 108
         $project->write();
@@ -123,8 +121,7 @@  discard block
 block discarded – undo
123 121
      * @param int $round Number of decimal places to round to
124 122
      * @return double The used quota size in MB
125 123
      */
126
-    public function getUsedQuotaMB($round = 2)
127
-    {
124
+    public function getUsedQuotaMB($round = 2) {
128 125
         $size = 0;
129 126
 
130 127
         foreach ($this->Environments() as $environment) {
@@ -144,8 +141,7 @@  discard block
 block discarded – undo
144 141
      *
145 142
      * @return string|int The quota size in MB
146 143
      */
147
-    public function getDiskQuotaMB()
148
-    {
144
+    public function getDiskQuotaMB() {
149 145
         $size = $this->getField('DiskQuotaMB');
150 146
 
151 147
         if (empty($size)) {
@@ -161,8 +157,7 @@  discard block
 block discarded – undo
161 157
      *
162 158
      * @return boolean
163 159
      */
164
-    public function HasExceededDiskQuota()
165
-    {
160
+    public function HasExceededDiskQuota() {
166 161
         return $this->getUsedQuotaMB(0) >= $this->getDiskQuotaMB();
167 162
     }
168 163
 
@@ -171,8 +166,7 @@  discard block
 block discarded – undo
171 166
      *
172 167
      * @return boolean
173 168
      */
174
-    public function HasDiskQuota()
175
-    {
169
+    public function HasDiskQuota() {
176 170
         return $this->getDiskQuotaMB() > 0;
177 171
     }
178 172
 
@@ -181,8 +175,7 @@  discard block
 block discarded – undo
181 175
      *
182 176
      * @return int
183 177
      */
184
-    public function DiskQuotaUsagePercent()
185
-    {
178
+    public function DiskQuotaUsagePercent() {
186 179
         $quota = $this->getDiskQuotaMB();
187 180
         if ($quota > 0) {
188 181
             return $this->getUsedQuotaMB() * 100 / $quota;
@@ -195,8 +188,7 @@  discard block
 block discarded – undo
195 188
      *
196 189
      * @return ArrayList
197 190
      */
198
-    public function Menu()
199
-    {
191
+    public function Menu() {
200 192
         $list = new ArrayList();
201 193
 
202 194
         $controller = Controller::curr();
@@ -221,8 +213,7 @@  discard block
 block discarded – undo
221 213
      *
222 214
      * @return bool
223 215
      */
224
-    public function isCurrent()
225
-    {
216
+    public function isCurrent() {
226 217
         return $this->isSection() && Controller::curr()->getAction() == 'project';
227 218
     }
228 219
 
@@ -232,8 +223,7 @@  discard block
 block discarded – undo
232 223
      *
233 224
      * @return DataObject
234 225
      */
235
-    public function CurrentMenu()
236
-    {
226
+    public function CurrentMenu() {
237 227
         return $this->Menu()->filter('IsSection', true)->First();
238 228
     }
239 229
 
@@ -242,8 +232,7 @@  discard block
 block discarded – undo
242 232
      *
243 233
      * @return bool
244 234
      */
245
-    public function isSection()
246
-    {
235
+    public function isSection() {
247 236
         $controller = Controller::curr();
248 237
         $project = $controller->getField('CurrentProject');
249 238
         return $project && $this->ID == $project->ID;
@@ -255,8 +244,7 @@  discard block
 block discarded – undo
255 244
      * @param Member|null $member
256 245
      * @return boolean
257 246
      */
258
-    public function canView($member = null)
259
-    {
247
+    public function canView($member = null) {
260 248
         if (!$member) {
261 249
             $member = Member::currentUser();
262 250
         }
@@ -273,8 +261,7 @@  discard block
 block discarded – undo
273 261
      *
274 262
      * @return bool
275 263
      */
276
-    public function canRestore($member = null)
277
-    {
264
+    public function canRestore($member = null) {
278 265
         if ($this->allowedAny(
279 266
             array(
280 267
                 DNRoot::ALLOW_PROD_SNAPSHOT,
@@ -294,8 +281,7 @@  discard block
 block discarded – undo
294 281
      * @param Member|null $member
295 282
      * @return bool
296 283
      */
297
-    public function canBackup($member = null)
298
-    {
284
+    public function canBackup($member = null) {
299 285
         if ($this->allowedAny(
300 286
             array(
301 287
                 DNRoot::ALLOW_PROD_SNAPSHOT,
@@ -315,8 +301,7 @@  discard block
 block discarded – undo
315 301
      * @param Member|null $member
316 302
      * @return bool
317 303
      */
318
-    public function canUploadArchive($member = null)
319
-    {
304
+    public function canUploadArchive($member = null) {
320 305
         if ($this->allowedAny(
321 306
             array(
322 307
                 DNRoot::ALLOW_PROD_SNAPSHOT,
@@ -336,8 +321,7 @@  discard block
 block discarded – undo
336 321
      * @param Member|null $member
337 322
      * @return bool
338 323
      */
339
-    public function canDownloadArchive($member = null)
340
-    {
324
+    public function canDownloadArchive($member = null) {
341 325
         if ($this->allowedAny(
342 326
             array(
343 327
                 DNRoot::ALLOW_PROD_SNAPSHOT,
@@ -363,8 +347,7 @@  discard block
 block discarded – undo
363 347
      *
364 348
      * @return bool
365 349
      */
366
-    public function canCreateEnvironments($member = null)
367
-    {
350
+    public function canCreateEnvironments($member = null) {
368 351
         $envType = $this->AllowedEnvironmentType;
369 352
         if ($envType) {
370 353
             $env = Injector::inst()->get($envType);
@@ -378,8 +361,7 @@  discard block
 block discarded – undo
378 361
     /**
379 362
      * @return DataList
380 363
      */
381
-    public function DataArchives()
382
-    {
364
+    public function DataArchives() {
383 365
         $envIds = $this->Environments()->column('ID');
384 366
         return DNDataArchive::get()->filter('EnvironmentID', $envIds);
385 367
     }
@@ -390,8 +372,7 @@  discard block
 block discarded – undo
390 372
      *
391 373
      * @return DataList
392 374
      */
393
-    public function PendingManualUploadDataArchives()
394
-    {
375
+    public function PendingManualUploadDataArchives() {
395 376
         return $this->DataArchives()->filter('ArchiveFileID', null);
396 377
     }
397 378
 
@@ -405,8 +386,7 @@  discard block
 block discarded – undo
405 386
      *
406 387
      * @return array
407 388
      */
408
-    public function getProcessEnv()
409
-    {
389
+    public function getProcessEnv() {
410 390
         if (file_exists($this->getPrivateKeyPath())) {
411 391
             // Key-pair is available, use it.
412 392
             $processEnv = array(
@@ -426,16 +406,14 @@  discard block
 block discarded – undo
426 406
      *
427 407
      * @return string
428 408
      */
429
-    public function getViewersList()
430
-    {
409
+    public function getViewersList() {
431 410
         return implode(", ", $this->Viewers()->column("Title"));
432 411
     }
433 412
 
434 413
     /**
435 414
      * @return DNData
436 415
      */
437
-    public function DNData()
438
-    {
416
+    public function DNData() {
439 417
         return DNData::inst();
440 418
     }
441 419
 
@@ -444,8 +422,7 @@  discard block
 block discarded – undo
444 422
      *
445 423
      * @return DNReferenceList
446 424
      */
447
-    public function DNBuildList()
448
-    {
425
+    public function DNBuildList() {
449 426
         return DNReferenceList::create($this, $this->DNData());
450 427
     }
451 428
 
@@ -454,8 +431,7 @@  discard block
 block discarded – undo
454 431
      *
455 432
      * @return DNBranchList
456 433
      */
457
-    public function DNBranchList()
458
-    {
434
+    public function DNBranchList() {
459 435
         if ($this->CVSPath && !$this->repoExists()) {
460 436
             $this->cloneRepo();
461 437
         }
@@ -467,8 +443,7 @@  discard block
 block discarded – undo
467 443
      *
468 444
      * @return DNReferenceList
469 445
      */
470
-    public function DNTagList()
471
-    {
446
+    public function DNTagList() {
472 447
         if ($this->CVSPath && !$this->repoExists()) {
473 448
             $this->cloneRepo();
474 449
         }
@@ -478,8 +453,7 @@  discard block
 block discarded – undo
478 453
     /**
479 454
      * @return false|Gitonomy\Git\Repository
480 455
      */
481
-    public function getRepository()
482
-    {
456
+    public function getRepository() {
483 457
         if (!$this->repoExists()) {
484 458
             return false;
485 459
         }
@@ -493,8 +467,7 @@  discard block
 block discarded – undo
493 467
      *
494 468
      * @return ArrayList
495 469
      */
496
-    public function DNEnvironmentList()
497
-    {
470
+    public function DNEnvironmentList() {
498 471
         if (!self::$_current_member_cache) {
499 472
             self::$_current_member_cache = Member::currentUser();
500 473
         }
@@ -514,8 +487,7 @@  discard block
 block discarded – undo
514 487
      * @param string $usage
515 488
      * @return ArrayList
516 489
      */
517
-    public function EnvironmentsByUsage($usage)
518
-    {
490
+    public function EnvironmentsByUsage($usage) {
519 491
         return $this->DNEnvironmentList()->filter('Usage', $usage);
520 492
     }
521 493
 
@@ -524,8 +496,7 @@  discard block
 block discarded – undo
524 496
      *
525 497
      * @return false|DNDeployment
526 498
      */
527
-    public function currentBuilds()
528
-    {
499
+    public function currentBuilds() {
529 500
         if (!isset(self::$relation_cache['currentBuilds.'.$this->ID])) {
530 501
             $currentBuilds = array();
531 502
             foreach ($this->Environments() as $env) {
@@ -540,16 +511,14 @@  discard block
 block discarded – undo
540 511
      * @param string
541 512
      * @return string
542 513
      */
543
-    public function Link($action = '')
544
-    {
514
+    public function Link($action = '') {
545 515
         return Controller::join_links("naut", "project", $this->Name, $action);
546 516
     }
547 517
 
548 518
     /**
549 519
      * @return string|null
550 520
      */
551
-    public function CreateEnvironmentLink()
552
-    {
521
+    public function CreateEnvironmentLink() {
553 522
         if ($this->canCreateEnvironments()) {
554 523
             return $this->Link('createenv');
555 524
         }
@@ -559,16 +528,14 @@  discard block
 block discarded – undo
559 528
     /**
560 529
      * @return string
561 530
      */
562
-    public function ToggleStarLink()
563
-    {
531
+    public function ToggleStarLink() {
564 532
         return $this->Link('/star');
565 533
     }
566 534
 
567 535
     /**
568 536
      * @return bool
569 537
      */
570
-    public function IsStarred()
571
-    {
538
+    public function IsStarred() {
572 539
         $member = Member::currentUser();
573 540
         if ($member === null) {
574 541
             return false;
@@ -584,16 +551,14 @@  discard block
 block discarded – undo
584 551
      * @param string $action
585 552
      * @return string
586 553
      */
587
-    public function APILink($action)
588
-    {
554
+    public function APILink($action) {
589 555
         return Controller::join_links("naut", "api", $this->Name, $action);
590 556
     }
591 557
 
592 558
     /**
593 559
      * @return FieldList
594 560
      */
595
-    public function getCMSFields()
596
-    {
561
+    public function getCMSFields() {
597 562
         $fields = parent::getCMSFields();
598 563
 
599 564
         /** @var GridField $environments */
@@ -653,8 +618,7 @@  discard block
 block discarded – undo
653 618
      *
654 619
      * @param FieldList $fields
655 620
      */
656
-    public function setCreateProjectFolderField(&$fields)
657
-    {
621
+    public function setCreateProjectFolderField(&$fields) {
658 622
         // Check if the capistrano project folder exists
659 623
         if (!$this->Name) {
660 624
             return;
@@ -675,16 +639,14 @@  discard block
 block discarded – undo
675 639
     /**
676 640
      * @return boolean
677 641
      */
678
-    public function projectFolderExists()
679
-    {
642
+    public function projectFolderExists() {
680 643
         return file_exists($this->getProjectFolderPath());
681 644
     }
682 645
 
683 646
     /**
684 647
      * @return bool
685 648
      */
686
-    public function repoExists()
687
-    {
649
+    public function repoExists() {
688 650
         return file_exists(sprintf('%s/HEAD', $this->getLocalCVSPath()));
689 651
     }
690 652
 
@@ -692,8 +654,7 @@  discard block
 block discarded – undo
692 654
      * Setup a job to clone a git repository.
693 655
      * @return string resque token
694 656
      */
695
-    public function cloneRepo()
696
-    {
657
+    public function cloneRepo() {
697 658
         // Avoid this being called multiple times in the same request
698 659
         if (!isset(self::$has_cloned_cache[$this->ID])) {
699 660
             $fetch = DNGitFetch::create();
@@ -712,13 +673,11 @@  discard block
 block discarded – undo
712 673
     /**
713 674
      * @return string
714 675
      */
715
-    public function getLocalCVSPath()
716
-    {
676
+    public function getLocalCVSPath() {
717 677
         return sprintf('%s/%s', DEPLOYNAUT_LOCAL_VCS_PATH, $this->Name);
718 678
     }
719 679
 
720
-    public function onBeforeWrite()
721
-    {
680
+    public function onBeforeWrite() {
722 681
         parent::onBeforeWrite();
723 682
 
724 683
         if ($this->CreateEnvFolder && !file_exists($this->getProjectFolderPath())) {
@@ -726,8 +685,7 @@  discard block
 block discarded – undo
726 685
         }
727 686
     }
728 687
 
729
-    public function onAfterWrite()
730
-    {
688
+    public function onAfterWrite() {
731 689
         parent::onAfterWrite();
732 690
 
733 691
         if (!$this->CVSPath) {
@@ -743,8 +701,7 @@  discard block
 block discarded – undo
743 701
     /**
744 702
      * Delete related environments and folders
745 703
      */
746
-    public function onAfterDelete()
747
-    {
704
+    public function onAfterDelete() {
748 705
         parent::onAfterDelete();
749 706
 
750 707
         // Delete related environments
@@ -773,8 +730,7 @@  discard block
 block discarded – undo
773 730
      *
774 731
      * @return string|void
775 732
      */
776
-    public function getPublicKey()
777
-    {
733
+    public function getPublicKey() {
778 734
         $key = $this->getPublicKeyPath();
779 735
 
780 736
         if (file_exists($key)) {
@@ -787,8 +743,7 @@  discard block
 block discarded – undo
787 743
      *
788 744
      * @return string|null
789 745
      */
790
-    public function getPublicKeyPath()
791
-    {
746
+    public function getPublicKeyPath() {
792 747
         if ($privateKey = $this->getPrivateKeyPath()) {
793 748
             return $privateKey . '.pub';
794 749
         }
@@ -800,8 +755,7 @@  discard block
 block discarded – undo
800 755
      *
801 756
      * @return string|null
802 757
      */
803
-    public function getPrivateKeyPath()
804
-    {
758
+    public function getPrivateKeyPath() {
805 759
         $keyDir = $this->getKeyDir();
806 760
         if (!empty($keyDir)) {
807 761
             $filter = FileNameFilter::create();
@@ -816,8 +770,7 @@  discard block
 block discarded – undo
816 770
      *
817 771
      * @return string|null
818 772
      */
819
-    public function getKeyDir()
820
-    {
773
+    public function getKeyDir() {
821 774
         $keyDir = $this->DNData()->getKeyDir();
822 775
         if (!$keyDir) {
823 776
             return null;
@@ -835,8 +788,7 @@  discard block
 block discarded – undo
835 788
      * @param FieldList $fields
836 789
      * @param GridField $environments
837 790
      */
838
-    protected function setEnvironmentFields(&$fields, $environments)
839
-    {
791
+    protected function setEnvironmentFields(&$fields, $environments) {
840 792
         if (!$environments) {
841 793
             return;
842 794
         }
@@ -860,8 +812,7 @@  discard block
 block discarded – undo
860 812
      *
861 813
      * @return void|string
862 814
      */
863
-    public function getRepositoryURL()
864
-    {
815
+    public function getRepositoryURL() {
865 816
         $showUrl = Config::inst()->get($this->class, 'show_repository_url');
866 817
         if ($showUrl) {
867 818
             return $this->CVSPath;
@@ -911,8 +862,7 @@  discard block
 block discarded – undo
911 862
      *
912 863
      * @return ArrayData
913 864
      */
914
-    public function getRepositoryInterface()
915
-    {
865
+    public function getRepositoryInterface() {
916 866
         $interfaces = $this->config()->repository_interfaces;
917 867
 
918 868
         /* Look for each whitelisted hostname */
@@ -955,8 +905,7 @@  discard block
 block discarded – undo
955 905
     /**
956 906
      * @return string
957 907
      */
958
-    protected function getProjectFolderPath()
959
-    {
908
+    protected function getProjectFolderPath() {
960 909
         return sprintf('%s/%s', $this->DNData()->getEnvironmentDir(), $this->Name);
961 910
     }
962 911
 
@@ -966,8 +915,7 @@  discard block
 block discarded – undo
966 915
      * @param string $code
967 916
      * @return SS_List
968 917
      */
969
-    public function whoIsAllowed($code)
970
-    {
918
+    public function whoIsAllowed($code) {
971 919
         return $this->whoIsAllowedAny(array($code));
972 920
     }
973 921
 
@@ -978,8 +926,7 @@  discard block
 block discarded – undo
978 926
      * @param array|string $codes
979 927
      * @return SS_List
980 928
      */
981
-    public function whoIsAllowedAny($codes)
982
-    {
929
+    public function whoIsAllowedAny($codes) {
983 930
         if (!is_array($codes)) {
984 931
             $codes = array($codes);
985 932
         }
@@ -1007,8 +954,7 @@  discard block
 block discarded – undo
1007 954
      *
1008 955
      * @return bool
1009 956
      */
1010
-    public function allowed($code, $member = null)
1011
-    {
957
+    public function allowed($code, $member = null) {
1012 958
         return $this->allowedAny(array($code), $member);
1013 959
     }
1014 960
 
@@ -1020,8 +966,7 @@  discard block
 block discarded – undo
1020 966
      *
1021 967
      * @return bool
1022 968
      */
1023
-    public function groupAllowed($permissionCode, Group $group)
1024
-    {
969
+    public function groupAllowed($permissionCode, Group $group) {
1025 970
         $viewers = $this->Viewers();
1026 971
         if (!$viewers->find('ID', $group->ID)) {
1027 972
             return false;
@@ -1041,8 +986,7 @@  discard block
 block discarded – undo
1041 986
      *
1042 987
      * @return bool
1043 988
      */
1044
-    public function allowedAny($codes, $member = null)
1045
-    {
989
+    public function allowedAny($codes, $member = null) {
1046 990
         if (!$member) {
1047 991
             $member = Member::currentUser();
1048 992
         }
@@ -1060,8 +1004,7 @@  discard block
 block discarded – undo
1060 1004
      *
1061 1005
      * @return bool
1062 1006
      */
1063
-    public function isProjectReady()
1064
-    {
1007
+    public function isProjectReady() {
1065 1008
         if ($this->getRunningInitialEnvironmentCreations()->count() > 0) {
1066 1009
             // We're still creating the initial environments for this project so we're
1067 1010
             // not quite done
@@ -1090,8 +1033,7 @@  discard block
 block discarded – undo
1090 1033
      *
1091 1034
      * @return SS_List
1092 1035
      */
1093
-    public function getRunningEnvironmentCreations()
1094
-    {
1036
+    public function getRunningEnvironmentCreations() {
1095 1037
         return $this->CreateEnvironments()
1096 1038
             ->filter('Status', ['Queued', 'Started']);
1097 1039
     }
@@ -1101,8 +1043,7 @@  discard block
 block discarded – undo
1101 1043
      *
1102 1044
      * @return DataList
1103 1045
      */
1104
-    public function getInitialEnvironmentCreations()
1105
-    {
1046
+    public function getInitialEnvironmentCreations() {
1106 1047
         return $this->CreateEnvironments()->filter('IsInitialEnvironment', true);
1107 1048
     }
1108 1049
 
@@ -1111,8 +1052,7 @@  discard block
 block discarded – undo
1111 1052
      *
1112 1053
      * @return DataList
1113 1054
      */
1114
-    public function getRunningInitialEnvironmentCreations()
1115
-    {
1055
+    public function getRunningInitialEnvironmentCreations() {
1116 1056
         return $this->getInitialEnvironmentCreations()
1117 1057
             ->filter('Status', ['Queued', 'Started']);
1118 1058
     }
@@ -1122,8 +1062,7 @@  discard block
 block discarded – undo
1122 1062
      *
1123 1063
      * @return DataList
1124 1064
      */
1125
-    public function getCompleteInitialEnvironmentCreations()
1126
-    {
1065
+    public function getCompleteInitialEnvironmentCreations() {
1127 1066
         return $this->getInitialEnvironmentCreations()
1128 1067
             ->exclude('Status', ['Queued', 'Started']);
1129 1068
     }
@@ -1131,8 +1070,7 @@  discard block
 block discarded – undo
1131 1070
     /**
1132 1071
      * @return ValidationResult
1133 1072
      */
1134
-    protected function validate()
1135
-    {
1073
+    protected function validate() {
1136 1074
         $validation = parent::validate();
1137 1075
         if ($validation->valid()) {
1138 1076
             if (empty($this->Name)) {
@@ -1164,8 +1102,7 @@  discard block
 block discarded – undo
1164 1102
      *
1165 1103
      * @return bool
1166 1104
      */
1167
-    public function canCreate($member = null)
1168
-    {
1105
+    public function canCreate($member = null) {
1169 1106
         if (!$member) {
1170 1107
             $member = Member::currentUser();
1171 1108
         }
Please login to merge, or discard this patch.
code/model/DNQueuedCommits.php 2 patches
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -9,11 +9,11 @@
 block discarded – undo
9 9
 class DNQueuedCommits extends DNFilteredCommits
10 10
 {
11 11
 
12
-    private $description = 'Commits that are currently queued';
12
+	private $description = 'Commits that are currently queued';
13 13
 
14
-    public function __construct()
15
-    {
16
-        parent::__construct();
17
-        $this->setFilter('Queued');
18
-    }
14
+	public function __construct()
15
+	{
16
+		parent::__construct();
17
+		$this->setFilter('Queued');
18
+	}
19 19
 }
Please login to merge, or discard this patch.
Braces   +2 added lines, -4 removed lines patch added patch discarded remove patch
@@ -6,13 +6,11 @@
 block discarded – undo
6 6
  *  Class to represent queued commits
7 7
  *
8 8
  */
9
-class DNQueuedCommits extends DNFilteredCommits
10
-{
9
+class DNQueuedCommits extends DNFilteredCommits {
11 10
 
12 11
     private $description = 'Commits that are currently queued';
13 12
 
14
-    public function __construct()
15
-    {
13
+    public function __construct() {
16 14
         parent::__construct();
17 15
         $this->setFilter('Queued');
18 16
     }
Please login to merge, or discard this patch.
code/model/DNReference.php 2 patches
Indentation   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -3,34 +3,34 @@
 block discarded – undo
3 3
 class DNReference extends ViewableData
4 4
 {
5 5
 
6
-    /**
7
-     * @var Gitonomy\Git\Reference
8
-     */
9
-    protected $reference;
6
+	/**
7
+	 * @var Gitonomy\Git\Reference
8
+	 */
9
+	protected $reference;
10 10
 
11
-    private static $casting = array(
12
-        'Name' => 'Text',
13
-        'FullName' => 'Text',
14
-        'Filename' => 'Text'
15
-    );
11
+	private static $casting = array(
12
+		'Name' => 'Text',
13
+		'FullName' => 'Text',
14
+		'Filename' => 'Text'
15
+	);
16 16
 
17
-    public function __construct(Gitonomy\Git\Reference $reference)
18
-    {
19
-        $this->reference = $reference;
20
-    }
17
+	public function __construct(Gitonomy\Git\Reference $reference)
18
+	{
19
+		$this->reference = $reference;
20
+	}
21 21
 
22
-    public function Name()
23
-    {
24
-        return $this->reference->getName();
25
-    }
22
+	public function Name()
23
+	{
24
+		return $this->reference->getName();
25
+	}
26 26
 
27
-    public function FullName()
28
-    {
29
-        return $this->reference->getCommitHash();
30
-    }
27
+	public function FullName()
28
+	{
29
+		return $this->reference->getCommitHash();
30
+	}
31 31
 
32
-    public function Filename()
33
-    {
34
-        return $this->reference->getFullname();
35
-    }
32
+	public function Filename()
33
+	{
34
+		return $this->reference->getFullname();
35
+	}
36 36
 }
Please login to merge, or discard this patch.
Braces   +5 added lines, -10 removed lines patch added patch discarded remove patch
@@ -1,7 +1,6 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 
3
-class DNReference extends ViewableData
4
-{
3
+class DNReference extends ViewableData {
5 4
 
6 5
     /**
7 6
      * @var Gitonomy\Git\Reference
@@ -14,23 +13,19 @@  discard block
 block discarded – undo
14 13
         'Filename' => 'Text'
15 14
     );
16 15
 
17
-    public function __construct(Gitonomy\Git\Reference $reference)
18
-    {
16
+    public function __construct(Gitonomy\Git\Reference $reference) {
19 17
         $this->reference = $reference;
20 18
     }
21 19
 
22
-    public function Name()
23
-    {
20
+    public function Name() {
24 21
         return $this->reference->getName();
25 22
     }
26 23
 
27
-    public function FullName()
28
-    {
24
+    public function FullName() {
29 25
         return $this->reference->getCommitHash();
30 26
     }
31 27
 
32
-    public function Filename()
33
-    {
28
+    public function Filename() {
34 29
         return $this->reference->getFullname();
35 30
     }
36 31
 }
Please login to merge, or discard this patch.
code/model/DNReferenceList.php 3 patches
Indentation   +206 added lines, -206 removed lines patch added patch discarded remove patch
@@ -5,210 +5,210 @@
 block discarded – undo
5 5
 class DNReferenceList extends ArrayList
6 6
 {
7 7
 
8
-    /**
9
-     * @var string
10
-     */
11
-    protected static $refs_dir = '';
12
-
13
-    /**
14
-     * @var bool
15
-     */
16
-    protected $loaded = false;
17
-
18
-    /**
19
-     * @var Reference|null
20
-     */
21
-    protected $reference = null;
22
-
23
-    /**
24
-     * @var null|string
25
-     */
26
-    protected $blockBranch;
27
-
28
-    /**
29
-     * @var array
30
-     */
31
-    protected $builds = array();
32
-
33
-    /**
34
-     * @var int
35
-     */
36
-    protected $limit = 10;
37
-
38
-    /**
39
-     * @var bool
40
-     */
41
-    protected $getTags = false;
42
-
43
-    /**
44
-     * @var DNProject
45
-     */
46
-    protected $project;
47
-
48
-    /**
49
-     * @var DNData
50
-     */
51
-    protected $data;
52
-
53
-    /**
54
-     * @param $refsDir
55
-     */
56
-    public static function set_refs_dir($refsDir)
57
-    {
58
-        self::$refs_dir = $refsDir;
59
-    }
60
-
61
-    /**
62
-     * @return string
63
-     */
64
-    public static function get_refs_dir()
65
-    {
66
-        return self::$refs_dir;
67
-    }
68
-
69
-    /**
70
-     * @param DNProject $project
71
-     * @param DNData $data
72
-     * @param Reference|null $reference
73
-     * @param string|null $blockBranch
74
-     * @param bool $getTags
75
-     */
76
-    public function __construct(
77
-        DNProject $project,
78
-        DNData $data,
79
-        Reference $reference = null,
80
-        $blockBranch = null,
81
-        $getTags = false
82
-    ) {
83
-        $this->project = $project;
84
-        $this->data = $data;
85
-        $this->reference = $reference;
86
-        $this->blockBranch = $blockBranch;
87
-        $this->getTags = $getTags;
88
-        parent::__construct(array());
89
-    }
90
-
91
-    /**
92
-     * @param int $limit
93
-     * @return DNReferenceList
94
-     */
95
-    public function setLimit($limit)
96
-    {
97
-        $this->limit = $limit;
98
-        return $this;
99
-    }
100
-
101
-    /**
102
-     * @return array
103
-     */
104
-    protected function getReferences()
105
-    {
106
-        try {
107
-            $repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
108
-        } catch (Exception $e) {
109
-            return array();
110
-        }
111
-
112
-        if ($this->getTags) {
113
-            if ($this->reference) {
114
-                throw new LogicException("Can't have \$reference and \$getTags both set");
115
-            }
116
-            $log = $repository->getReferences()->getTags();
117
-        } elseif ($this->reference) {
118
-            $log = $this->reference->getLog();
119
-        } else {
120
-            $log = $repository->getLog();
121
-        }
122
-
123
-        if ($this->limit) {
124
-            if (is_array($log)) {
125
-                $limitedLog = array_slice($log, 0, $this->limit);
126
-            } else {
127
-                $limitedLog = $log->setLimit($this->limit);
128
-            }
129
-        } else {
130
-            $limitedLog = $log;
131
-        }
132
-
133
-        // cache them for look up in byName
134
-        $builds = array();
135
-        foreach ($limitedLog as $reference) {
136
-            if (!empty($this->blockBranch)) {
137
-                $branchesIncluding = GitonomyCache::getIncludingBranches($reference);
138
-                foreach ($branchesIncluding as $candidate) {
139
-                    if ($candidate->getName() == $this->blockBranch) {
140
-                        // Break out of the function
141
-                        return $builds;
142
-                    }
143
-                }
144
-            }
145
-
146
-            if ($this->getTags) {
147
-                $builds[] = DNTag::create($reference, $this->project, $this->data);
148
-            } else {
149
-                $name = $this->reference ? $this->reference->getName() : '';
150
-                $builds[] = DNCommit::create($reference, $this->project, $this->data, $name);
151
-            }
152
-        }
153
-
154
-        return $builds;
155
-    }
156
-
157
-    /**
158
-     * Find a build in this set by hash.
159
-     *
160
-     * @param string $hash
161
-     * @return DNCommit
162
-     */
163
-    public function byName($hash)
164
-    {
165
-        if ($this->loaded === false) {
166
-            $this->items = $this->getReferences();
167
-            $this->loaded = true;
168
-        }
169
-
170
-        // The item might not be in the list because of the limit, try to find
171
-        // in an older version and add it to the list.
172
-        $found = null;
173
-        foreach ($this->items as $item) {
174
-            if ($item->SHA() == $hash) {
175
-                $found = $item;
176
-                break;
177
-            }
178
-        }
179
-
180
-        if ($found === null) {
181
-            $repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
182
-            $commit = new Gitonomy\Git\Commit($repository, $hash);
183
-            $found = DNCommit::create($commit, $this->project, $this->data);
184
-        }
185
-
186
-        return $found;
187
-    }
188
-
189
-    public function Count()
190
-    {
191
-        $this->getIterator();
192
-        return parent::Count();
193
-    }
194
-
195
-    /**
196
-     * Returns an Iterator for this ArrayList.
197
-     * This function allows you to use ArrayList in foreach loops
198
-     *
199
-     * @return ArrayIterator
200
-     */
201
-    public function getIterator()
202
-    {
203
-        if ($this->loaded === false) {
204
-            $this->items = $this->getReferences();
205
-            $this->loaded = true;
206
-        }
207
-        foreach ($this->items as $item) {
208
-            if (is_array($item)) {
209
-                $this->items[] = new ArrayData($item);
210
-            }
211
-        }
212
-        return new ArrayIterator($this->items);
213
-    }
8
+	/**
9
+	 * @var string
10
+	 */
11
+	protected static $refs_dir = '';
12
+
13
+	/**
14
+	 * @var bool
15
+	 */
16
+	protected $loaded = false;
17
+
18
+	/**
19
+	 * @var Reference|null
20
+	 */
21
+	protected $reference = null;
22
+
23
+	/**
24
+	 * @var null|string
25
+	 */
26
+	protected $blockBranch;
27
+
28
+	/**
29
+	 * @var array
30
+	 */
31
+	protected $builds = array();
32
+
33
+	/**
34
+	 * @var int
35
+	 */
36
+	protected $limit = 10;
37
+
38
+	/**
39
+	 * @var bool
40
+	 */
41
+	protected $getTags = false;
42
+
43
+	/**
44
+	 * @var DNProject
45
+	 */
46
+	protected $project;
47
+
48
+	/**
49
+	 * @var DNData
50
+	 */
51
+	protected $data;
52
+
53
+	/**
54
+	 * @param $refsDir
55
+	 */
56
+	public static function set_refs_dir($refsDir)
57
+	{
58
+		self::$refs_dir = $refsDir;
59
+	}
60
+
61
+	/**
62
+	 * @return string
63
+	 */
64
+	public static function get_refs_dir()
65
+	{
66
+		return self::$refs_dir;
67
+	}
68
+
69
+	/**
70
+	 * @param DNProject $project
71
+	 * @param DNData $data
72
+	 * @param Reference|null $reference
73
+	 * @param string|null $blockBranch
74
+	 * @param bool $getTags
75
+	 */
76
+	public function __construct(
77
+		DNProject $project,
78
+		DNData $data,
79
+		Reference $reference = null,
80
+		$blockBranch = null,
81
+		$getTags = false
82
+	) {
83
+		$this->project = $project;
84
+		$this->data = $data;
85
+		$this->reference = $reference;
86
+		$this->blockBranch = $blockBranch;
87
+		$this->getTags = $getTags;
88
+		parent::__construct(array());
89
+	}
90
+
91
+	/**
92
+	 * @param int $limit
93
+	 * @return DNReferenceList
94
+	 */
95
+	public function setLimit($limit)
96
+	{
97
+		$this->limit = $limit;
98
+		return $this;
99
+	}
100
+
101
+	/**
102
+	 * @return array
103
+	 */
104
+	protected function getReferences()
105
+	{
106
+		try {
107
+			$repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
108
+		} catch (Exception $e) {
109
+			return array();
110
+		}
111
+
112
+		if ($this->getTags) {
113
+			if ($this->reference) {
114
+				throw new LogicException("Can't have \$reference and \$getTags both set");
115
+			}
116
+			$log = $repository->getReferences()->getTags();
117
+		} elseif ($this->reference) {
118
+			$log = $this->reference->getLog();
119
+		} else {
120
+			$log = $repository->getLog();
121
+		}
122
+
123
+		if ($this->limit) {
124
+			if (is_array($log)) {
125
+				$limitedLog = array_slice($log, 0, $this->limit);
126
+			} else {
127
+				$limitedLog = $log->setLimit($this->limit);
128
+			}
129
+		} else {
130
+			$limitedLog = $log;
131
+		}
132
+
133
+		// cache them for look up in byName
134
+		$builds = array();
135
+		foreach ($limitedLog as $reference) {
136
+			if (!empty($this->blockBranch)) {
137
+				$branchesIncluding = GitonomyCache::getIncludingBranches($reference);
138
+				foreach ($branchesIncluding as $candidate) {
139
+					if ($candidate->getName() == $this->blockBranch) {
140
+						// Break out of the function
141
+						return $builds;
142
+					}
143
+				}
144
+			}
145
+
146
+			if ($this->getTags) {
147
+				$builds[] = DNTag::create($reference, $this->project, $this->data);
148
+			} else {
149
+				$name = $this->reference ? $this->reference->getName() : '';
150
+				$builds[] = DNCommit::create($reference, $this->project, $this->data, $name);
151
+			}
152
+		}
153
+
154
+		return $builds;
155
+	}
156
+
157
+	/**
158
+	 * Find a build in this set by hash.
159
+	 *
160
+	 * @param string $hash
161
+	 * @return DNCommit
162
+	 */
163
+	public function byName($hash)
164
+	{
165
+		if ($this->loaded === false) {
166
+			$this->items = $this->getReferences();
167
+			$this->loaded = true;
168
+		}
169
+
170
+		// The item might not be in the list because of the limit, try to find
171
+		// in an older version and add it to the list.
172
+		$found = null;
173
+		foreach ($this->items as $item) {
174
+			if ($item->SHA() == $hash) {
175
+				$found = $item;
176
+				break;
177
+			}
178
+		}
179
+
180
+		if ($found === null) {
181
+			$repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
182
+			$commit = new Gitonomy\Git\Commit($repository, $hash);
183
+			$found = DNCommit::create($commit, $this->project, $this->data);
184
+		}
185
+
186
+		return $found;
187
+	}
188
+
189
+	public function Count()
190
+	{
191
+		$this->getIterator();
192
+		return parent::Count();
193
+	}
194
+
195
+	/**
196
+	 * Returns an Iterator for this ArrayList.
197
+	 * This function allows you to use ArrayList in foreach loops
198
+	 *
199
+	 * @return ArrayIterator
200
+	 */
201
+	public function getIterator()
202
+	{
203
+		if ($this->loaded === false) {
204
+			$this->items = $this->getReferences();
205
+			$this->loaded = true;
206
+		}
207
+		foreach ($this->items as $item) {
208
+			if (is_array($item)) {
209
+				$this->items[] = new ArrayData($item);
210
+			}
211
+		}
212
+		return new ArrayIterator($this->items);
213
+	}
214 214
 }
Please login to merge, or discard this patch.
Spacing   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -105,23 +105,23 @@  discard block
 block discarded – undo
105 105
     {
106 106
         try {
107 107
             $repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
108
-        } catch (Exception $e) {
108
+        } catch(Exception $e) {
109 109
             return array();
110 110
         }
111 111
 
112
-        if ($this->getTags) {
113
-            if ($this->reference) {
112
+        if($this->getTags) {
113
+            if($this->reference) {
114 114
                 throw new LogicException("Can't have \$reference and \$getTags both set");
115 115
             }
116 116
             $log = $repository->getReferences()->getTags();
117
-        } elseif ($this->reference) {
117
+        } elseif($this->reference) {
118 118
             $log = $this->reference->getLog();
119 119
         } else {
120 120
             $log = $repository->getLog();
121 121
         }
122 122
 
123
-        if ($this->limit) {
124
-            if (is_array($log)) {
123
+        if($this->limit) {
124
+            if(is_array($log)) {
125 125
                 $limitedLog = array_slice($log, 0, $this->limit);
126 126
             } else {
127 127
                 $limitedLog = $log->setLimit($this->limit);
@@ -132,18 +132,18 @@  discard block
 block discarded – undo
132 132
 
133 133
         // cache them for look up in byName
134 134
         $builds = array();
135
-        foreach ($limitedLog as $reference) {
136
-            if (!empty($this->blockBranch)) {
135
+        foreach($limitedLog as $reference) {
136
+            if(!empty($this->blockBranch)) {
137 137
                 $branchesIncluding = GitonomyCache::getIncludingBranches($reference);
138
-                foreach ($branchesIncluding as $candidate) {
139
-                    if ($candidate->getName() == $this->blockBranch) {
138
+                foreach($branchesIncluding as $candidate) {
139
+                    if($candidate->getName() == $this->blockBranch) {
140 140
                         // Break out of the function
141 141
                         return $builds;
142 142
                     }
143 143
                 }
144 144
             }
145 145
 
146
-            if ($this->getTags) {
146
+            if($this->getTags) {
147 147
                 $builds[] = DNTag::create($reference, $this->project, $this->data);
148 148
             } else {
149 149
                 $name = $this->reference ? $this->reference->getName() : '';
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
      */
163 163
     public function byName($hash)
164 164
     {
165
-        if ($this->loaded === false) {
165
+        if($this->loaded === false) {
166 166
             $this->items = $this->getReferences();
167 167
             $this->loaded = true;
168 168
         }
@@ -170,14 +170,14 @@  discard block
 block discarded – undo
170 170
         // The item might not be in the list because of the limit, try to find
171 171
         // in an older version and add it to the list.
172 172
         $found = null;
173
-        foreach ($this->items as $item) {
174
-            if ($item->SHA() == $hash) {
173
+        foreach($this->items as $item) {
174
+            if($item->SHA() == $hash) {
175 175
                 $found = $item;
176 176
                 break;
177 177
             }
178 178
         }
179 179
 
180
-        if ($found === null) {
180
+        if($found === null) {
181 181
             $repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
182 182
             $commit = new Gitonomy\Git\Commit($repository, $hash);
183 183
             $found = DNCommit::create($commit, $this->project, $this->data);
@@ -200,12 +200,12 @@  discard block
 block discarded – undo
200 200
      */
201 201
     public function getIterator()
202 202
     {
203
-        if ($this->loaded === false) {
203
+        if($this->loaded === false) {
204 204
             $this->items = $this->getReferences();
205 205
             $this->loaded = true;
206 206
         }
207
-        foreach ($this->items as $item) {
208
-            if (is_array($item)) {
207
+        foreach($this->items as $item) {
208
+            if(is_array($item)) {
209 209
                 $this->items[] = new ArrayData($item);
210 210
             }
211 211
         }
Please login to merge, or discard this patch.
Braces   +8 added lines, -16 removed lines patch added patch discarded remove patch
@@ -2,8 +2,7 @@  discard block
 block discarded – undo
2 2
 
3 3
 use Gitonomy\Git\Reference;
4 4
 
5
-class DNReferenceList extends ArrayList
6
-{
5
+class DNReferenceList extends ArrayList {
7 6
 
8 7
     /**
9 8
      * @var string
@@ -53,16 +52,14 @@  discard block
 block discarded – undo
53 52
     /**
54 53
      * @param $refsDir
55 54
      */
56
-    public static function set_refs_dir($refsDir)
57
-    {
55
+    public static function set_refs_dir($refsDir) {
58 56
         self::$refs_dir = $refsDir;
59 57
     }
60 58
 
61 59
     /**
62 60
      * @return string
63 61
      */
64
-    public static function get_refs_dir()
65
-    {
62
+    public static function get_refs_dir() {
66 63
         return self::$refs_dir;
67 64
     }
68 65
 
@@ -92,8 +89,7 @@  discard block
 block discarded – undo
92 89
      * @param int $limit
93 90
      * @return DNReferenceList
94 91
      */
95
-    public function setLimit($limit)
96
-    {
92
+    public function setLimit($limit) {
97 93
         $this->limit = $limit;
98 94
         return $this;
99 95
     }
@@ -101,8 +97,7 @@  discard block
 block discarded – undo
101 97
     /**
102 98
      * @return array
103 99
      */
104
-    protected function getReferences()
105
-    {
100
+    protected function getReferences() {
106 101
         try {
107 102
             $repository = new Gitonomy\Git\Repository($this->project->getLocalCVSPath());
108 103
         } catch (Exception $e) {
@@ -160,8 +155,7 @@  discard block
 block discarded – undo
160 155
      * @param string $hash
161 156
      * @return DNCommit
162 157
      */
163
-    public function byName($hash)
164
-    {
158
+    public function byName($hash) {
165 159
         if ($this->loaded === false) {
166 160
             $this->items = $this->getReferences();
167 161
             $this->loaded = true;
@@ -186,8 +180,7 @@  discard block
 block discarded – undo
186 180
         return $found;
187 181
     }
188 182
 
189
-    public function Count()
190
-    {
183
+    public function Count() {
191 184
         $this->getIterator();
192 185
         return parent::Count();
193 186
     }
@@ -198,8 +191,7 @@  discard block
 block discarded – undo
198 191
      *
199 192
      * @return ArrayIterator
200 193
      */
201
-    public function getIterator()
202
-    {
194
+    public function getIterator() {
203 195
         if ($this->loaded === false) {
204 196
             $this->items = $this->getReferences();
205 197
             $this->loaded = true;
Please login to merge, or discard this patch.