Passed
Push — master ( f5d59a...3a9472 )
by Nicolaas
06:47 queued 04:44
created

SearchAndReplace::setRunInRootDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Sunnysideup\UpgradeToSilverstripe4\Tasks\IndividualTasks;
4
5
use Sunnysideup\UpgradeToSilverstripe4\Api\LoadReplacementData;
6
use Sunnysideup\UpgradeToSilverstripe4\Api\SearchAndReplaceAPI;
7
use Sunnysideup\UpgradeToSilverstripe4\Tasks\Task;
8
9
/**
10
 * Replaces a bunch of code snippets in preparation of the upgrade.
11
 * Controversial replacements will be replaced with a comment
12
 * next to it so you can review replacements easily.
13
 */
14
class SearchAndReplace extends Task
15
{
16
    protected $taskStep = 's30';
17
18
    /**
19
     * for debugging purposes
20
     * @var bool
21
     */
22
    protected $debug = false;
23
24
    /**
25
     * check if there are double-ups in replacement.
26
     * @var bool
27
     */
28
    protected $checkReplacementIssues = false;
29
30
    /**
31
     * string used to show issues in replacement in the actual code being replaced.
32
     *
33
     * @var string
34
     */
35
    protected $replacementHeader = 'automated upgrade';
36
37
    /**
38
     * folder containing the replacement file
39
     *
40
     * @var string
41
     */
42
    protected $folderContainingReplacementData = '';
43
44
    /**
45
     * list of folders to ignore in search and replace
46
     * @var array
47
     */
48
    protected $ignoreFolderArray = [
49
        '.git',
50
        '.svn',
51
    ];
52
53
    protected $runInRootDir = false;
54
55
    /**
56
     * the names of the folder that contains the data we need
57
     * e.g. SS4 / SS37
58
     * IMPORTANT!
59
     *
60
     * @var array
61
     */
62
    protected $sourceFolders = [
63
        'SS4',
64
    ];
65
66
    protected $commitAndPush = true;
67
68
    public function getTitle()
69
    {
70
        return 'Search and Replace';
71
    }
72
73
    public function getDescription()
74
    {
75
        return '
76
            Replaces a bunch of code snippets in preparation of the upgrade.
77
            Controversial replacements will be replaced with a comment
78
            next to it so you can review replacements easily.';
79
    }
80
81
    public function setCheckReplacementIssues(bool $b)
82
    {
83
        $this->checkReplacementIssues = $b;
84
85
        return $this;
86
    }
87
88
    public function setIgnoreFolderArray(array $a)
89
    {
90
        $this->ignoreFolderArray = $a;
91
92
        return $this;
93
    }
94
95
    public function setRunInRootDir(array $a)
96
    {
97
        $this->ignoreFolderArray = $a;
98
99
        return $this;
100
    }
101
102
    public function setCommitAndPush(bool $b)
103
    {
104
        $this->commitAndPush = $b;
105
106
        return $this;
107
    }
108
109
    public function setFolderContainingReplacementData(string $s)
110
    {
111
        $this->folderContainingReplacementData = $s;
112
113
        return $this;
114
    }
115
116
117
    public function setSourceFolders(array $a)
118
    {
119
        $this->sourceFolders = $a;
120
121
        return $this;
122
    }
123
124
    public function runActualTask($params = []): ?string
125
    {
126
        //replacement data
127
        $replacementDataObjects = $this->getReplacementDataObjects();
128
        foreach ($replacementDataObjects as $replacementDataObject) {
129
            if ($this->checkReplacementIssues) {
130
                $this->checkReplacementDataIssues($replacementDataObject);
131
            }
132
133
            $replacementArray = $replacementDataObject->getReplacementArrays();
134
135
            if ($this->debug) {
136
                $this->mu()->colourPrint($replacementArray);
0 ignored issues
show
Bug introduced by
It seems like colourPrint() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

136
                $this->mu()->/** @scrutinizer ignore-call */ colourPrint($replacementArray);
Loading history...
137
            }
138
139
            //replace API
140
            if($this->runInRootDir) {
141
                $list = [$this->mu()->getWebRootDirLocation()];
0 ignored issues
show
Bug introduced by
The method getWebRootDirLocation() does not exist on Sunnysideup\UpgradeToSilverstripe4\ModuleUpgrader. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

141
                $list = [$this->mu()->/** @scrutinizer ignore-call */ getWebRootDirLocation()];
Loading history...
Bug introduced by
It seems like getWebRootDirLocation() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

141
                $list = [$this->mu()->/** @scrutinizer ignore-call */ getWebRootDirLocation()];
Loading history...
142
            } else {
143
                $list = $this->mu()->getExistingModuleDirLocationsWithThemeFolders();
0 ignored issues
show
Bug introduced by
It seems like getExistingModuleDirLocationsWithThemeFolders() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

143
                $list = $this->mu()->/** @scrutinizer ignore-call */ getExistingModuleDirLocationsWithThemeFolders();
Loading history...
144
            }
145
            foreach ($list as $moduleOrThemeDir) {
146
                $textSearchMachine = new SearchAndReplaceAPI($moduleOrThemeDir);
147
                $textSearchMachine->setIsReplacingEnabled(true);
148
                $textSearchMachine->addToIgnoreFolderArray($this->ignoreFolderArray);
149
150
                foreach ($replacementArray as $path => $pathArray) {
151
                    $path = $moduleOrThemeDir . '/' . $path ?: '';
152
                    $path = $this->mu()->checkIfPathExistsAndCleanItUp($path);
0 ignored issues
show
Bug introduced by
It seems like checkIfPathExistsAndCleanItUp() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

152
                    $path = $this->mu()->/** @scrutinizer ignore-call */ checkIfPathExistsAndCleanItUp($path);
Loading history...
153
                    if (! file_exists($path)) {
154
                        $this->mu()->colourPrint("SKIPPING ".$path);
155
                    } else {
156
                        $textSearchMachine->setSearchPath($path);
157
                        foreach ($pathArray as $extension => $extensionArray) {
158
                            //setting extensions to search files within
159
                            $textSearchMachine->setExtensions(explode('|', $extension));
160
                            $this->mu()->colourPrint(
161
                                "++++++++++++++++++++++++++++++++++++\n" .
162
                                "CHECKING\n" .
163
                                "IN ".$path."\n" .
164
                                "FOR ".$extension." FILES\n" .
165
                                'BASE ' . $moduleOrThemeDir . "\n" .
166
                                "++++++++++++++++++++++++++++++++++++\n"
167
                            );
168
                            foreach ($extensionArray as $find => $findDetails) {
169
                                $replace = isset($findDetails['R']) ? $findDetails['R'] : $find;
170
                                $comment = isset($findDetails['C']) ? $findDetails['C'] : '';
171
                                $ignoreCase = isset($findDetails['I']) ? $findDetails['I'] : false;
172
                                $caseSensitive = ! $ignoreCase;
173
                                $isRegex = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $isRegex is dead and can be removed.
Loading history...
174
                                //$replace = $replaceArray[1]; unset($replaceArray[1]);
175
                                //$fullReplacement =
176
                                //   (isset($replaceArray[2]) ? "/* ".$replaceArray[2]." */\n" : "").$replaceArray[1];
177
                                $fullReplacement = $replace;
178
                                $isStraightReplace = true;
179
                                if ($comment) {
180
                                    $isStraightReplace = false;
181
                                }
182
                                if (! $find) {
183
                                    user_error("no find is specified, replace is: ${replace}");
184
                                }
185
                                if (! $fullReplacement) {
186
                                    user_error('
187
                                        No replace is specified, find is: ${find}.
188
                                        We suggest setting your final replace to a single space if
189
                                        you would like to replace with NOTHING.
190
                                    ');
191
                                }
192
                                $replaceKey = $isStraightReplace ? 'BASIC' : 'COMPLEX';
193
194
                                $textSearchMachine->setSearchKey($find, $caseSensitive, $replaceKey);
195
                                $textSearchMachine->setReplacementKey($fullReplacement);
196
                                if ($comment) {
197
                                    $textSearchMachine->setComment($comment);
198
                                }
199
                                $textSearchMachine->setReplacementHeader($this->replacementHeader);
200
                                $textSearchMachine->startSearchAndReplace();
201
                            }
202
                            $replacements = $textSearchMachine->showFormattedSearchTotals();
203
                            if ($replacements) {
204
                            } else {
205
                                //flush output anyway!
206
                                $this->mu()->colourPrint("No replacements for  ${extension}");
207
                            }
208
                            $this->mu()->colourPrint($textSearchMachine->getOutput());
209
                        }
210
                    }
211
                }
212
            }
213
        }
214
        return null;
215
    }
216
217
    protected function hasCommitAndPush()
218
    {
219
        return $this->commitAndPush;
220
    }
221
222
    protected function getReplacementDataObjects(): array
223
    {
224
        $array = [];
225
        foreach ($this->sourceFolders as $sourceFolder) {
226
            $array[] = new LoadReplacementData(
227
                $this->mu(),
228
                $this->folderContainingReplacementData,
229
                $sourceFolder
230
            );
231
        }
232
233
        return $array;
234
    }
235
236
    /**
237
     * 1. check that one find is not used twice:
238
     * find can be found 2x
239
     * @param mixed $replacementDataObject
240
     */
241
    private function checkReplacementDataIssues($replacementDataObject)
242
    {
243
        $replacementDataObject->getReplacementArrays(null);
244
        $arrLanguages = $replacementDataObject->getLanguages();
245
        $fullFindArray = $replacementDataObject->getFlatFindArray();
246
        $fullReplaceArray = $replacementDataObject->getFlatReplacedArray();
247
248
        //1. check that one find may not stop another replacement.
249
        foreach ($arrLanguages as $language) {
250
            if (! isset($fullFindArray[$language])) {
251
                continue;
252
            }
253
            unset($keyOuterDoneSoFar);
254
            $keyOuterDoneSoFar = [];
255
            foreach ($fullFindArray[$language] as $keyOuter => $findStringOuter) {
256
                $keyOuterDoneSoFar[$keyOuter] = true;
257
                foreach ($fullFindArray[$language] as $keyInner => $findStringInner) {
258
                    if (! isset($keyOuterDoneSoFar[$keyInner])) {
259
                        if ($keyOuter !== $keyInner) {
260
                            $findStringOuterReplaced = str_replace($findStringInner, '...', $findStringOuter);
261
                            if ($findStringOuter === $findStringInner || $findStringOuterReplaced !== $findStringOuter) {
262
                                $this->mu()->colourPrint("
263
ERROR in ".$language.": \t\t we are trying to find the same thing twice (A and B)
264
---- A: (".$keyOuter."): \t\t ".$findStringOuter."
265
---- B: (".$keyInner."): \t\t ".$findStringInner);
266
                            }
267
                        }
268
                    }
269
                }
270
            }
271
        }
272
273
        //2. check that a replacement is not mentioned before the it is being replaced
274
        foreach ($arrLanguages as $language) {
275
            if (! isset($fullReplaceArray[$language])) {
276
                continue;
277
            }
278
            unset($keyOuterDoneSoFar);
279
            $keyOuterDoneSoFar = [];
280
            foreach ($fullReplaceArray[$language] as $keyOuter => $findStringOuter) {
281
                $keyOuterDoneSoFar[$keyOuter] = true;
282
                foreach ($fullFindArray[$language] as $keyInner => $findStringInner) {
283
                    if (isset($keyOuterDoneSoFar[$keyInner])) {
284
                        if ($keyOuter !== $keyInner) {
285
                            $findStringOuterReplaced = str_replace($findStringInner, '...', $findStringOuter);
286
                            if ($findStringOuter === $findStringInner || $findStringOuterReplaced !== $findStringOuter) {
287
                                $this->mu()->colourPrint("
288
ERROR in ".$language.": \t\t there is a replacement (A) that was earlier tried to be found (B).
289
---- A: (".$keyOuter."): \t\t ".$findStringOuter."
290
---- B: (".$keyInner."): \t\t ".$findStringInner);
291
                            }
292
                        }
293
                    }
294
                }
295
            }
296
        }
297
        $this->mu()->colourPrint('');
298
    }
299
}
300