Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

getLastLineNumberThatContains()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 11
rs 10
1
<?php
2
3
namespace Backpack\CRUD\app\Console\Commands;
4
5
use Illuminate\Console\Command;
6
7
class AddCustomRouteContent extends Command
8
{
9
    use \Backpack\CRUD\app\Console\Commands\Traits\PrettyCommandOutput;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Consol...its\PrettyCommandOutput requires some properties which are not provided by Backpack\CRUD\app\Consol...s\AddCustomRouteContent: $progressBar, $statusColor, $status
Loading history...
10
11
    /**
12
     * The name and signature of the console command.
13
     *
14
     * @var string
15
     */
16
    protected $signature = 'backpack:add-custom-route
17
                                {code : HTML/PHP code that registers a route. Use either single quotes or double quotes. Never both. }
18
                                {--route-file=routes/backpack/custom.php : The file where the code should be added relative to the root of the project. }';
19
20
    /**
21
     * The console command description.
22
     *
23
     * @var string
24
     */
25
    protected $description = 'Add HTML/PHP code to the routes/backpack/custom.php file';
26
27
    /**
28
     * Create a new command instance.
29
     *
30
     * @return void
31
     */
32
    public function __construct()
33
    {
34
        parent::__construct();
35
    }
36
37
    /**
38
     * Execute the console command.
39
     *
40
     * @return mixed
41
     */
42
    public function handle()
43
    {
44
        $routeFilePath = base_path($this->option('route-file'));
45
46
        if (! file_exists($routeFilePath)) {
47
            if ($routeFilePath !== base_path($this->backpackCustomRouteFile)) {
0 ignored issues
show
Bug Best Practice introduced by
The property backpackCustomRouteFile does not exist on Backpack\CRUD\app\Consol...s\AddCustomRouteContent. Did you maybe forget to declare it?
Loading history...
48
                $this->info('The route file <fg=blue>'.$routeFilePath.'</> does not exist. Please create it first.');
49
50
                return 1;
51
            }
52
53
            $createRouteFile = $this->confirm('The route file <fg=blue>'.$routeFilePath.'</> does not exist. Should we create it?', 'yes');
0 ignored issues
show
Bug introduced by
'yes' of type string is incompatible with the type boolean expected by parameter $default of Illuminate\Console\Command::confirm(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
            $createRouteFile = $this->confirm('The route file <fg=blue>'.$routeFilePath.'</> does not exist. Should we create it?', /** @scrutinizer ignore-type */ 'yes');
Loading history...
54
            if ($createRouteFile === 'yes') {
0 ignored issues
show
introduced by
The condition $createRouteFile === 'yes' is always false.
Loading history...
55
                $this->call('vendor:publish', ['--provider' => \Backpack\CRUD\BackpackServiceProvider::class, '--tag' => 'custom_routes']);
56
            } else {
57
                $this->info('The route file <fg=blue>'.$routeFilePath.'</> does not exist. Please create it first.');
58
59
                return 1;
60
            }
61
        }
62
63
        $code = $this->argument('code');
64
65
        $this->progressBlock("Adding route to <fg=blue>$routeFilePath</>");
66
67
        $originalContent = file($routeFilePath);
68
69
        // clean the content from comments etc
70
        $cleanContent = $this->cleanContentArray($originalContent);
71
72
        // if the content contains code, don't add it again.
73
        if (array_search($code, $cleanContent, true) !== false) {
74
            $this->closeProgressBlock('Already existed', 'yellow');
75
76
            return;
77
        }
78
79
        // get the last element of the array contains '}'
80
        $lastLine = $this->getLastLineNumberThatContains('}', $cleanContent);
81
82
        if ($lastLine === false) {
0 ignored issues
show
introduced by
The condition $lastLine === false is always false.
Loading history...
83
            $this->closeProgressBlock('Could not find the last line, file '.$routeFilePath.' may be corrupted.', 'red');
84
85
            return;
86
        }
87
88
        // in case the last line contains the last } but also the last {, we need to split them
89
        // so that we can create a space between them and add the new code
90
        if (strpos($cleanContent[$lastLine], '{') !== false) {
91
            $lastLineContent = explode('{', $originalContent[$lastLine]);
92
            $originalContent[$lastLine] = $lastLineContent[0].'{'.PHP_EOL;
93
            // push all other elements one line down creating space for the new code
94
            for ($i = count($originalContent) - 1; $i > $lastLine; $i--) {
95
                $originalContent[$i + 1] = $originalContent[$i];
96
            }
97
            $originalContent[$lastLine + 1] = $lastLineContent[1];
98
            $lastLine++;
99
        }
100
101
        // in case the last line contains more than one ";" it means that line closes more than one group
102
        // we need to split the line and create space for the new code
103
        if (substr_count($cleanContent[$lastLine], ';') > 1) {
104
            $lastLineContent = explode(';', $originalContent[$lastLine]);
105
106
            // find in lastLineContent array the last element that contains the }
107
            $lastElement = $this->getLastLineNumberThatContains('}', $lastLineContent);
108
109
            // merge the first part of the lastLineContent up to the lastElement
110
            $originalContent[$lastLine] = implode(';', array_slice($lastLineContent, 0, $lastElement)).';'.PHP_EOL;
111
112
            // push all other elements one line down creating space for the new code
113
            for ($i = count($originalContent) - 1; $i > $lastLine; $i--) {
114
                $originalContent[$i + 1] = $originalContent[$i];
115
            }
116
117
            // merge the second part of the lastLineContent starting from the lastElement
118
            $originalContent[$lastLine + 1] = implode(';', array_slice($lastLineContent, $lastElement));
119
            $lastLine++;
120
        }
121
122
        $sliceLength = 0;
123
124
        // in case there is already an empty line at the end of the route file, we don't need to add another one
125
        if (trim($originalContent[$lastLine - 1]) === '') {
126
            $lastLine--;
127
            $sliceLength = 1;
128
        }
129
130
        // add the code to the line before the last line
131
        array_splice($originalContent, $lastLine, $sliceLength, '    '.$code.PHP_EOL);
132
133
        // write the new content to the file
134
        if (file_put_contents($routeFilePath, implode('', $originalContent)) === false) {
135
            $this->closeProgressBlock('Failed to add route. Failed writing the modified route file. Maybe check file permissions?', 'red');
136
137
            return;
138
        }
139
140
        $this->closeProgressBlock('done', 'green');
141
    }
142
143
    private function cleanContentArray(array $content)
144
    {
145
        return array_filter(array_map(function ($line) {
146
            $lineText = trim($line);
147
            if ($lineText === '' ||
148
                $lineText === '\n' ||
149
                $lineText === '\r' ||
150
                $lineText === '\r\n' ||
151
                $lineText === PHP_EOL ||
152
                str_starts_with($lineText, '<?php') ||
153
                str_starts_with($lineText, '?>') ||
154
                str_starts_with($lineText, '//') ||
155
                str_starts_with($lineText, '/*') ||
156
                str_starts_with($lineText, '*/') ||
157
                str_ends_with($lineText, '*/') ||
158
                str_starts_with($lineText, '*') ||
159
                str_starts_with($lineText, 'use ') ||
160
                str_starts_with($lineText, 'return ') ||
161
                str_starts_with($lineText, 'namespace ')) {
162
                return null;
163
            }
164
165
            return $lineText;
166
        }, $content));
167
    }
168
169
    /**
170
     * Parse the given file stream and return the line number where a string is found.
171
     *
172
     * @param  string  $needle  The string that's being searched for.
173
     * @param  array  $haystack  The file where the search is being performed.
174
     * @return bool|int The last line number where the string was found. Or false.
175
     */
176
    private function getLastLineNumberThatContains($needle, $haystack)
177
    {
178
        $matchingLines = array_filter($haystack, function ($k) use ($needle) {
179
            return strpos($k, $needle) !== false;
180
        });
181
182
        if ($matchingLines) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matchingLines of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
183
            return array_key_last($matchingLines);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_key_last($matchingLines) also could return the type string which is incompatible with the documented return type boolean|integer.
Loading history...
184
        }
185
186
        return false;
187
    }
188
}
189