Build::getTotalErrorsCount()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8657
c 0
b 0
f 0
cc 6
nc 5
nop 3
1
<?php
2
3
namespace Fabrica\Models\Infra\Ci;
4
5
use Fabrica\Tools\Builder;
6
use Fabrica\Helper\Lang;
7
use Fabrica\Tools\Plugin\PhpParallelLint;
8
use Fabrica\Tools\Store\Factory;
9
use Fabrica\Tools\Store\ProjectStore;
10
use Fabrica\Tools\Store\BuildErrorStore;
11
use Symfony\Component\Filesystem\Filesystem;
12
use Symfony\Component\Yaml\Parser as YamlParser;
13
use Fabrica\Models\Infra\Ci\Base\Build as BaseBuild;
14
use Pedreiro\Exceptions\InvalidArgumentException;
15
16
/**
17
 * @author Ricardo Sierra <[email protected]>
18
 */
19
class Build extends BaseBuild
20
{
21
    const STAGE_SETUP    = 'setup';
22
    const STAGE_TEST     = 'test';
23
    const STAGE_DEPLOY   = 'deploy';
24
    const STAGE_COMPLETE = 'complete';
25
    const STAGE_SUCCESS  = 'success';
26
    const STAGE_FAILURE  = 'failure';
27
    const STAGE_FIXED    = 'fixed';
28
    const STAGE_BROKEN   = 'broken';
29
30
    /**
31
     * @var array
32
     */
33
    public static $pullRequestSources = [
34
        self::SOURCE_WEBHOOK_PULL_REQUEST_CREATED,
35
        self::SOURCE_WEBHOOK_PULL_REQUEST_UPDATED,
36
        self::SOURCE_WEBHOOK_PULL_REQUEST_APPROVED,
37
        self::SOURCE_WEBHOOK_PULL_REQUEST_MERGED,
38
    ];
39
40
    /**
41
     * @var array
42
     */
43
    public static $webhookSources = [
44
        self::SOURCE_WEBHOOK_PUSH,
45
        self::SOURCE_WEBHOOK_PULL_REQUEST_CREATED,
46
        self::SOURCE_WEBHOOK_PULL_REQUEST_UPDATED,
47
        self::SOURCE_WEBHOOK_PULL_REQUEST_APPROVED,
48
        self::SOURCE_WEBHOOK_PULL_REQUEST_MERGED,
49
    ];
50
51
    /**
52
     * @var array
53
     */
54
    protected $totalErrorsCount = [];
55
56
    /**
57
     * @var string
58
     */
59
    protected $buildDirectory;
60
61
    /**
62
     * @var string
63
     */
64
    protected $buildBranchDirectory;
65
66
    /**
67
     * @return null|Project
68
     *
69
     * @throws \PHPCensor\Exception\HttpException
70
     */
71
    public function getProject()
72
    {
73
        $projectId = $this->getProjectId();
74
        if (!$projectId) {
75
            return null;
76
        }
77
78
        /**
79
 * @var ProjectStore $projectStore 
80
*/
81
        $projectStore = Factory::getStore('Project');
82
83
        return $projectStore->getById($projectId);
84
    }
85
86
    /**
87
     * @param string $name
88
     * @param mixed  $value
89
     */
90
    public function addExtraValue($name, $value)
91
    {
92
        $extra = json_decode($this->data['extra'], true);
93
        if ($extra === false) {
94
            $extra = [];
95
        }
96
        $extra[$name] = $value;
97
98
        $this->setExtra($extra);
99
    }
100
101
    /**
102
     * @param string $name
103
     */
104
    public function removeExtraValue($name)
105
    {
106
        $extra = $this->getExtra();
107
        if (!empty($extra[$name])) {
108
            unset($extra[$name]);
109
        }
110
111
        $this->setExtra($extra);
112
    }
113
114
    /**
115
     * Get BuildError models by BuildId for this Build.
116
     *
117
     * @return \PHPCensor\Model\BuildError[]
118
     */
119
    public function getBuildBuildErrors()
120
    {
121
        return Factory::getStore('BuildError')->getByBuildId($this->getId());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fabrica\Tools\Store as the method getByBuildId() does only exist in the following sub-classes of Fabrica\Tools\Store: Fabrica\Tools\Store\BuildErrorStore, Fabrica\Tools\Store\BuildMetaStore. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
122
    }
123
124
    /**
125
     * Get BuildMeta models by BuildId for this Build.
126
     *
127
     * @return \PHPCensor\Model\BuildMeta[]
128
     */
129
    public function getBuildBuildMetas()
130
    {
131
        return Factory::getStore('BuildMeta')->getByBuildId($this->getId());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fabrica\Tools\Store as the method getByBuildId() does only exist in the following sub-classes of Fabrica\Tools\Store: Fabrica\Tools\Store\BuildErrorStore, Fabrica\Tools\Store\BuildMetaStore. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
132
    }
133
134
    /**
135
     * Get link to commit from another source (i.e. Github)
136
     */
137
    public function getCommitLink()
138
    {
139
        return '#';
140
    }
141
142
    /**
143
     * Get link to branch from another source (i.e. Github)
144
     */
145
    public function getBranchLink()
146
    {
147
        return '#';
148
    }
149
150
    /**
151
     * Get remote branch (from pull request) from another source (i.e. Github)
152
     */
153
    public function getRemoteBranch()
154
    {
155
        return $this->getExtra('remote_branch');
156
    }
157
158
    /**
159
     * Get link to remote branch (from pull request) from another source (i.e. Github)
160
     */
161
    public function getRemoteBranchLink()
162
    {
163
        return '#';
164
    }
165
166
    /**
167
     * Get link to tag from another source (i.e. Github)
168
     */
169
    public function getTagLink()
170
    {
171
        return '#';
172
    }
173
174
    /**
175
     * Return a template to use to generate a link to a specific file.
176
     *
177
     * @return string|null
178
     */
179
    public function getFileLinkTemplate()
180
    {
181
        return null;
182
    }
183
184
    /**
185
     * Send status updates to any relevant third parties (i.e. Github)
186
     */
187
    public function sendStatusPostback()
188
    {
189
        return false;
190
    }
191
192
    /**
193
     * @return string
194
     *
195
     * @throws \PHPCensor\Exception\HttpException
196
     */
197
    public function getProjectTitle()
198
    {
199
        $project = $this->getProject();
200
        return $project ? $project->getTitle() : "";
201
    }
202
203
    /**
204
     * Store build metadata
205
     *
206
     * @param string $key
207
     * @param mixed  $value
208
     */
209
    public function storeMeta($key, $value)
210
    {
211
        $value = json_encode($value);
212
213
        Factory::getStore('Build')->setMeta($this->getId(), $key, $value);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fabrica\Tools\Store as the method setMeta() does only exist in the following sub-classes of Fabrica\Tools\Store: Fabrica\Tools\Store\BuildStore. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
214
    }
215
216
    /**
217
     * Is this build successful?
218
     */
219
    public function isSuccessful()
220
    {
221
        return ($this->getStatus() === self::STATUS_SUCCESS);
222
    }
223
224
    /**
225
     * @param Builder $builder
226
     *
227
     * @return bool
228
     *
229
     * @throws \Exception
230
     */
231
    public function handleConfigBeforeClone(Builder $builder)
232
    {
233
        $buildConfig = $this->getProject()->getBuildConfig();
234
235
        if ($buildConfig) {
236
            $yamlParser  = new YamlParser();
237
            $buildConfig = $yamlParser->parse($buildConfig);
238
239 View Code Duplication
            if ($buildConfig && is_array($buildConfig)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
240
                $builder->logDebug('Config before repository clone (DB): ' . json_encode($buildConfig));
241
242
                $builder->setConfig($buildConfig);
243
            }
244
        }
245
246
        return true;
247
    }
248
249
    /**
250
     * @param Builder $builder
251
     * @param string  $buildPath
252
     *
253
     * @return bool
254
     *
255
     * @throws \Exception
256
     */
257
    protected function handleConfig(Builder $builder, $buildPath)
258
    {
259
        $yamlParser           = new YamlParser();
260
        $overwriteBuildConfig = $this->getProject()->getOverwriteBuildConfig();
261
        $buildConfig          = $builder->getConfig();
262
263
        $repositoryConfig     = $this->getZeroConfigPlugins($builder);
264
        $repositoryConfigFrom = '<empty config>';
265
        if (file_exists($buildPath . '/.php-censor.yml')) {
266
            $repositoryConfigFrom = '.php-censor.yml';
267
            $repositoryConfig = $yamlParser->parse(
268
                file_get_contents($buildPath . '/.php-censor.yml')
269
            );
270
        } elseif (file_exists($buildPath . '/.phpci.yml')) {
271
            $builder->logWarning(
272
                '[DEPRECATED] Config file name ".phpci.yml" is deprecated and will be deleted in version 2.0. Use a config file name ".php-censor.yml" instead.'
273
            );
274
275
            $repositoryConfigFrom = '.phpci.yml';
276
            $repositoryConfig = $yamlParser->parse(
277
                file_get_contents($buildPath . '/.phpci.yml')
278
            );
279
        } elseif (file_exists($buildPath . '/phpci.yml')) {
280
            $builder->logWarning(
281
                '[DEPRECATED] Config file name "phpci.yml" is deprecated and will be deleted in version 2.0. Use a config file name ".php-censor.yml" instead.'
282
            );
283
284
            $repositoryConfigFrom = 'phpci.yml';
285
            $repositoryConfig = $yamlParser->parse(
286
                file_get_contents($buildPath . '/phpci.yml')
287
            );
288
        }
289
290
        if (isset($repositoryConfig['build_settings']['clone_depth'])) {
291
            $builder->logWarning(
292
                'Option "build_settings.clone_depth" supported only in additional DB project config.' .
293
                ' Please move this option to DB config from your in-repository config file (".php-censor.yml").'
294
            );
295
        }
296
297
        if (!$buildConfig) {
298
            $builder->logDebug(
299
                sprintf('Build config from repository (%s)', $repositoryConfigFrom)
300
            );
301
302
            $buildConfig = $repositoryConfig;
303
        } elseif ($buildConfig && !$overwriteBuildConfig) {
304
            $builder->logDebug(
305
                sprintf('Build config from project (DB) + config from repository (%s)', $repositoryConfigFrom)
306
            );
307
308
            $buildConfig = array_replace_recursive($repositoryConfig, $buildConfig);
309
        } elseif ($buildConfig) {
310
            $builder->logDebug('Build config from project (DB)');
311
        }
312
313 View Code Duplication
        if ($buildConfig && is_array($buildConfig)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
314
            $builder->logDebug('Final config: ' . json_encode($buildConfig));
315
316
            $builder->setConfig($buildConfig);
317
        }
318
319
        return true;
320
    }
321
322
    /**
323
     * Get an array of plugins to run if there's no .php-censor.yml file.
324
     *
325
     * @param Builder $builder
326
     *
327
     * @return array
328
     *
329
     * @throws \ReflectionException
330
     */
331
    protected function getZeroConfigPlugins(Builder $builder)
332
    {
333
        $pluginDir = SRC_DIR . 'Plugin/';
334
        $dir = new \DirectoryIterator($pluginDir);
335
336
        $config = [
337
            'build_settings' => [
338
                'ignore' => [
339
                    'vendor',
340
                ]
341
            ]
342
        ];
343
344
        foreach ($dir as $item) {
345
            if ($item->isDot()) {
346
                continue;
347
            }
348
349
            if (!$item->isFile()) {
350
                continue;
351
            }
352
353
            if ($item->getExtension() != 'php') {
354
                continue;
355
            }
356
357
            $className = '\PHPCensor\Plugin\\' . $item->getBasename('.php');
358
359
            $reflectedPlugin = new \ReflectionClass($className);
360
361
            if (!$reflectedPlugin->implementsInterface('\PHPCensor\ZeroConfigPluginInterface')) {
362
                continue;
363
            }
364
365
            foreach ([Build::STAGE_SETUP, Build::STAGE_TEST] as $stage) {
366
                if ($className::canExecuteOnStage($stage, $this)) {
367
                    $pluginConfig = [
368
                        'zero_config' => true,
369
                    ];
370
371
                    if (PhpParallelLint::pluginName() === $className::pluginName()) {
372
                        $pluginConfig['allow_failures'] = true;
373
                    }
374
375
                    $config[$stage][$className::pluginName()] = $pluginConfig;
376
                }
377
            }
378
        }
379
380
        return $config;
381
    }
382
383
    /**
384
     * Allows specific build types (e.g. Github) to report violations back to their respective services.
385
     *
386
     * @param Builder $builder
387
     * @param string  $plugin
388
     * @param string  $message
389
     * @param int     $severity
390
     * @param string  $file
391
     * @param int     $lineStart
392
     * @param int     $lineEnd
393
     */
394
    public function reportError(
395
        Builder $builder,
396
        $plugin,
397
        $message,
398
        $severity = BuildError::SEVERITY_NORMAL,
399
        $file = null,
400
        $lineStart = null,
401
        $lineEnd = null
402
    ) {
403
        $writer = $builder->getBuildErrorWriter();
404
        $writer->write(
405
            $plugin,
406
            $message,
407
            $severity,
408
            $file,
409
            $lineStart,
410
            $lineEnd
411
        );
412
    }
413
414
    /**
415
     * @return string|null
416
     */
417 View Code Duplication
    public function getBuildDirectory()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
418
    {
419
        if (!$this->getId()) {
420
            return null;
421
        }
422
423
        $createDate = $this->getCreateDate();
424
        if (empty($this->buildDirectory)) {
425
            $this->buildDirectory = $this->getProjectId() . '/' . $this->getId() . '_' . substr(
426
                md5(
427
                    ($this->getId() . '_' . ($createDate ? $createDate->format('Y-m-d H:i:s') : null))
428
                ),
429
                0,
430
                8
431
            );
432
        }
433
434
        return $this->buildDirectory;
435
    }
436
437
    /**
438
     * @return string|null
439
     */
440 View Code Duplication
    public function getBuildBranchDirectory()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
441
    {
442
        if (!$this->getId()) {
443
            return null;
444
        }
445
446
        $createDate = $this->getCreateDate();
447
        if (empty($this->buildBranchDirectory)) {
448
            $this->buildBranchDirectory = $this->getProjectId() . '/' . $this->getBranch() . '_' . substr(
449
                md5(
450
                    ($this->getBranch() . '_' . ($createDate ? $createDate->format('Y-m-d H:i:s') : null))
451
                ),
452
                0,
453
                8
454
            );
455
        }
456
457
        return $this->buildBranchDirectory;
458
    }
459
460
    /**
461
     * @return string|null
462
     */
463
    public function getBuildPath()
464
    {
465
        if (!$this->getId()) {
466
            return null;
467
        }
468
469
        return rtrim(
470
            realpath(RUNTIME_DIR . 'builds'),
471
            '/\\'
472
        ) . '/' . $this->getBuildDirectory() . '/';
473
    }
474
475
    /**
476
     * Removes the build directory.
477
     *
478
     * @param bool $withArtifacts
479
     */
480
    public function removeBuildDirectory($withArtifacts = false)
481
    {
482
        // Get the path and remove the trailing slash as this may prompt PHP
483
        // to see this as a directory even if it's a link.
484
        $buildPath = rtrim($this->getBuildPath(), '/');
485
486
        if (!$buildPath || !is_dir($buildPath)) {
487
            return;
488
        }
489
490
        try {
491
            $fileSystem = new Filesystem();
492
493
            if (is_link($buildPath)) {
494
                // Remove the symlink without using recursive.
495
                exec(sprintf('rm "%s"', $buildPath));
496
            } else {
497
                $fileSystem->remove($buildPath);
498
            }
499
500
            if ($withArtifacts) {
501
                $buildDirectory = $this->getBuildDirectory();
502
503
                $fileSystem->remove(PUBLIC_DIR . 'artifacts/pdepend/' . $buildDirectory);
504
                $fileSystem->remove(PUBLIC_DIR . 'artifacts/phpunit/' . $buildDirectory);
505
            }
506
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
507
        }
508
    }
509
510
    /**
511
     * Get the number of seconds a build has been running for.
512
     *
513
     * @return int
514
     */
515
    public function getDuration()
516
    {
517
        $start = $this->getStartDate();
518
519
        if (empty($start)) {
520
            return 0;
521
        }
522
523
        $end = $this->getFinishDate();
524
525
        if (empty($end)) {
526
            $end = new \DateTime();
527
        }
528
529
        return $end->getTimestamp() - $start->getTimestamp();
530
    }
531
532
    /**
533
     * get time a build has been running for in hour/minute/seconds format (e.g. 1h 21m 45s)
534
     *
535
     * @return string
536
     */
537
    public function getPrettyDuration()
538
    {
539
        $start = $this->getStartDate();
540
        if (!$start) {
541
            $start = new \DateTime();
542
        }
543
        $end = $this->getFinishDate();
544
        if (!$end) {
545
            $end = new \DateTime();
546
        }
547
548
        $diff  = date_diff($start, $end);
549
        $parts = [];
550
        foreach (['y', 'm', 'd', 'h', 'i', 's'] as $timePart) {
551
            if ($diff->{$timePart} != 0) {
552
                $parts[] = $diff->{$timePart} . ($timePart == 'i' ? 'm' : $timePart);
553
            }
554
        }
555
556
        return implode(" ", $parts);
557
    }
558
559
    /**
560
     * Create a working copy by cloning, copying, or similar.
561
     *
562
     * @param Builder $builder
563
     * @param string  $buildPath
564
     *
565
     * @return bool
566
     */
567
    public function createWorkingCopy(Builder $builder, $buildPath)
568
    {
569
        return false;
570
    }
571
572
    /**
573
     * Create an SSH key file on disk for this build.
574
     *
575
     * @return string
576
     *
577
     * @throws \PHPCensor\Exception\HttpException
578
     */
579
    protected function writeSshKey()
580
    {
581
        $tempKeyFile = tempnam(sys_get_temp_dir(), 'key_');
582
583
        file_put_contents($tempKeyFile, $this->getProject()->getSshPrivateKey());
584
585
        return $tempKeyFile;
586
    }
587
588
    /**
589
     * Create an SSH wrapper script for Svn to use, to disable host key checking, etc.
590
     *
591
     * @param string $keyFile
592
     *
593
     * @return string
594
     */
595
    protected function writeSshWrapper($keyFile)
596
    {
597
        $sshFlags = '-o CheckHostIP=no -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o PasswordAuthentication=no';
598
599
        // Write out the wrapper script for this build:
600
        $script = <<<OUT
601
#!/bin/sh
602
ssh {$sshFlags} -o IdentityFile={$keyFile} $*
603
604
OUT;
605
        $tempShFile = tempnam(sys_get_temp_dir(), 'sh_');
606
607
        file_put_contents($tempShFile, $script);
608
        shell_exec('chmod +x "' . $tempShFile . '"');
609
610
        return $tempShFile;
611
    }
612
613
    /**
614
     * @return string
615
     */
616
    public function getSourceHumanize()
617
    {
618
        $parentId   = $this->getParentId();
619
        $parentLink = '<a href="' . config('app.url') . 'build/view/' . $parentId . '">#' . $parentId . '</a>';
620
621
        switch ($this->getSource()) {
622
        case Build::SOURCE_WEBHOOK_PUSH:
623
            return Lang::get('source_webhook_push');
624
        case Build::SOURCE_WEBHOOK_PULL_REQUEST_CREATED:
625
            return Lang::get('source_webhook_pull_request_created');
626
        case Build::SOURCE_WEBHOOK_PULL_REQUEST_UPDATED:
627
            return Lang::get('source_webhook_pull_request_updated');
628
        case Build::SOURCE_WEBHOOK_PULL_REQUEST_APPROVED:
629
            return Lang::get('source_webhook_pull_request_approved');
630
        case Build::SOURCE_WEBHOOK_PULL_REQUEST_MERGED:
631
            return Lang::get('source_webhook_pull_request_merged');
632
        case Build::SOURCE_MANUAL_WEB:
633
            return Lang::get('source_manual_web');
634
        case Build::SOURCE_MANUAL_REBUILD_WEB:
635
            return Lang::get('source_manual_rebuild_web', $parentLink);
636
        case Build::SOURCE_MANUAL_CONSOLE:
637
            return Lang::get('source_manual_console');
638
        case Build::SOURCE_MANUAL_REBUILD_CONSOLE:
639
            return Lang::get('source_manual_rebuild_console', $parentLink);
640
        case Build::SOURCE_PERIODICAL:
641
            return Lang::get('source_periodical');
642
        case Build::SOURCE_UNKNOWN:
643
        default:
644
            return Lang::get('source_unknown');
645
        }
646
    }
647
648
    /**
649
     * Gets the total number of errors for a given build.
650
     *
651
     * @param string|null $plugin
652
     * @param int|null    $severity
653
     * @param string|null $isNew
654
     *
655
     * @return int
656
     *
657
     * @throws \Exception
658
     */
659
    public function getTotalErrorsCount($plugin = null, $severity = null, $isNew = null)
660
    {
661
        if (null === $plugin && null === $severity && null === $isNew) {
662
            return (int)$this->getErrorsTotal();
663
        }
664
665
        $key =
666
            $plugin . ':' .
667
            ((null === $severity) ? 'null' : (int)$severity) . ':' .
668
            $isNew;
669
670
        if (!isset($this->totalErrorsCount[$key])) {
671
            /**
672
 * @var BuildErrorStore $store 
673
*/
674
            $store = Factory::getStore('BuildError');
675
676
            $this->totalErrorsCount[$key] = (int)$store->getErrorTotalForBuild(
677
                $this->getId(),
678
                $plugin,
679
                $severity,
680
                $isNew
681
            );
682
        }
683
684
        return $this->totalErrorsCount[$key];
685
    }
686
687
    /**
688
     * @return int
689
     *
690
     * @throws InvalidArgumentException
691
     * @throws \PHPCensor\Exception\HttpException
692
     */
693
    public function getErrorsTrend()
694
    {
695
        $total    = (int)$this->getErrorsTotal();
696
        $previous = $this->getErrorsTotalPrevious();
697
698
        if (null === $previous) {
699
            return 0;
700
        }
701
702
        $previous = (int)$previous;
703
        if ($previous > $total) {
704
            return 1;
705
        } elseif ($previous < $total) {
706
            return -1;
707
        }
708
709
        return 0;
710
    }
711
}
712