UpdateModules::moveOldReadMe()   A
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 17
c 4
b 1
f 0
dl 0
loc 29
rs 9.3888
cc 5
nc 7
nop 1
1
<?php
2
3
/**
4
 * main class running all the updates
5
 *
6
 *
7
 */
8
class UpdateModules extends BuildTask
9
{
10
    protected $enabled = true;
11
12
    protected $title = "Update Modules";
13
14
    protected $description = "Adds files necessary for publishing a module to GitHub. The list of modules is specified in standard config or else it retrieves a list of modules from GitHub.";
15
16
    /**
17
     * e.g.
18
     * - moduleA
19
     * - moduleB
20
     * - moduleC
21
     *
22
     *
23
     * @var array
24
     */
25
    private static $modules_to_update = array();
0 ignored issues
show
introduced by
The private property $modules_to_update is not used, and could be removed.
Loading history...
26
27
    /**
28
     * e.g.
29
     * - ClassNameForUpdatingFileA
30
     * - ClassNameForUpdatingFileB
31
     *
32
     * @var array
33
     */
34
    private static $files_to_update = [];
0 ignored issues
show
introduced by
The private property $files_to_update is not used, and could be removed.
Loading history...
35
    /**
36
     * e.g.
37
     * - ClassNameForUpdatingFileA
38
     * - ClassNameForUpdatingFileB
39
     *
40
     * @var array
41
     */
42
    private static $commands_to_run = array();
0 ignored issues
show
introduced by
The private property $commands_to_run is not used, and could be removed.
Loading history...
43
44
    public static $unsolvedItems = array();
45
46
    public function run($request)
47
    {
48
        increase_time_limit_to(3600);
49
50
        //Check temp module folder is empty
51
        $tempFolder = GitHubModule::Config()->get('absolute_temp_folder');
52
        if(! file_exists($tempFolder)) {
0 ignored issues
show
Bug introduced by
It seems like $tempFolder can also be of type array; however, parameter $filename of file_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

52
        if(! file_exists(/** @scrutinizer ignore-type */ $tempFolder)) {
Loading history...
53
            FileSystem::makeFolder($tempFolder);
0 ignored issues
show
Bug introduced by
It seems like $tempFolder can also be of type array; however, parameter $folder of Filesystem::makeFolder() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

53
            FileSystem::makeFolder(/** @scrutinizer ignore-type */ $tempFolder);
Loading history...
54
        }
55
        $tempDirFiles = scandir($tempFolder);
0 ignored issues
show
Bug introduced by
It seems like $tempFolder can also be of type array; however, parameter $directory of scandir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

55
        $tempDirFiles = scandir(/** @scrutinizer ignore-type */ $tempFolder);
Loading history...
56
        if (count($tempDirFiles) > 2) {
0 ignored issues
show
Bug introduced by
It seems like $tempDirFiles can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

56
        if (count(/** @scrutinizer ignore-type */ $tempDirFiles) > 2) {
Loading history...
57
            die('<h2>' . $tempFolder . ' is not empty, please delete or move files </h2>');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
58
        }
59
60
        //Get list of all modules from GitHub
61
        $gitUserName = $this->Config()->get('github_user_name');
0 ignored issues
show
Unused Code introduced by
The assignment to $gitUserName is dead and can be removed.
Loading history...
62
63
        $modules = GitRepoFinder::get_all_repos();
64
65
66
        /*
67
         * Get files to add to modules
68
         * */
69
        $files = ClassInfo::subclassesFor('AddFileToModule');
70
        array_shift($files);
71
        $limitedFileClasses = $this->Config()->get('files_to_update');
72
        if ($limitedFileClasses === []) {
73
            //do nothing
74
        } elseif ($limitedFileClasses === 'none') {
75
            $files = [];
76
        } elseif (is_array($limitedFileClasses) && count($limitedFileClasses)) {
77
            $files = array_intersect($files, $limitedFileClasses);
78
        }
79
80
        /*
81
         * Get commands to run on modules
82
         * */
83
84
        $commands = ClassInfo::subclassesFor('RunCommandLineMethodOnModule');
85
        array_shift($commands);
86
        $limitedCommands = $this->Config()->get('commands_to_run');
87
        if ($limitedCommands === 'none') {
88
            $commands = [];
89
        } elseif (is_array($limitedCommands) && count($limitedCommands)) {
90
            $commands = array_intersect($commands, $limitedCommands);
91
        }
92
93
94
        set_error_handler('errorHandler', E_ALL);
95
        foreach ($modules as $count => $module) {
0 ignored issues
show
Bug introduced by
The expression $modules of type string is not traversable.
Loading history...
96
            $this->currentModule = $module;
0 ignored issues
show
Bug Best Practice introduced by
The property currentModule does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
97
            try {
98
                $this->processOneModule($module, $count, $files, $commands);
99
            } catch (Exception $e) {
100
                GeneralMethods::output_to_screen("<li> Could not complete processing $module: " .  $e->getMessage() . " </li>");
101
            }
102
        }
103
104
        restore_error_handler();
105
106
        $this->writeLog();
107
        //to do ..
108
    }
109
110
    protected function errorHandler(int $errno, string $errstr)
111
    {
112
        GeneralMethods::output_to_screen("<li> Could not complete processing module: " .  $errstr . " </li>");
113
114
        UpdateModules::addUnsolvedProblem($this->currentModule, "Could not complete processing module: " . $errstr);
115
116
        return true;
117
    }
118
119
    protected function processOneModule($module, $count, $files, $commands)
120
    {
121
        if (stripos($module, 'silverstripe-')  === false) {
122
            $module = "silverstripe-" . $module;
123
        }
124
        echo "<h2>" . ($count+1) . ". ".$module."</h2>";
125
126
127
        $moduleObject = GitHubModule::get_or_create_github_module($module);
128
129
        $this->checkUpdateTag($moduleObject);
130
131
        $updateComposerJson = $this->Config()->get('update_composer_json');
132
133
        // Check if all necessary files are perfect on GitHub repo already,
134
        // if so we can skip that module. But! ... if there are commands to run
135
        // over the files in the repo, then we need to clone the repo anyhow,
136
        // so skip the check
137
        if (count($commands) == 0 && ! $updateComposerJson) {
138
            $moduleFilesOK = true;
139
140
            foreach ($files as $file) {
141
                $fileObj = $file::create($moduleObject);
142
                $checkFileName = $fileObj->getFileLocation();
143
                $GitHubFileText = $moduleObject -> getRawFileFromGithub($checkFileName);
144
                if ($GitHubFileText) {
145
                    $fileCheck = $fileObj->compareWithText($GitHubFileText);
146
                    if (! $fileCheck) {
147
                        $moduleFilesOK = false;
148
                    }
149
                } else {
150
                    $moduleFilesOK = false;
151
                }
152
            }
153
        }
154
155
        $repository = $moduleObject->checkOrSetGitCommsWrapper($forceNew = true);
0 ignored issues
show
Unused Code introduced by
The assignment to $repository is dead and can be removed.
Loading history...
156
157
158
        $this->moveOldReadMe($moduleObject);
159
160
161
        $checkConfigYML = $this->Config()->get('check_config_yml');
162
        if ($checkConfigYML) {
163
            $this->checkConfigYML($moduleObject);
164
        }
165
166
        if ($updateComposerJson) {
167
            $composerJsonObj = new ComposerJson($moduleObject);
168
            $composerJsonObj->updateJsonFile();
169
            $moduleObject->setDescription($composerJsonObj->getDescription());
170
        }
171
172
        $excludedWords = $this->Config()->get('excluded_words');
173
174
175
        if (count($excludedWords) > 0) {
0 ignored issues
show
Bug introduced by
It seems like $excludedWords can also be of type boolean and double and integer and string; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

175
        if (count(/** @scrutinizer ignore-type */ $excludedWords) > 0) {
Loading history...
176
            $folder = GitHubModule::Config()->get('absolute_temp_folder') . '/' . $moduleObject->moduleName . '/';
177
178
            $results = $this->checkDirExcludedWords($folder.'/'.$moduleObject->modulename, $excludedWords);
179
180
181
            if ($results && count($results > 0)) {
0 ignored issues
show
Bug introduced by
$results > 0 of type boolean is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

181
            if ($results && count(/** @scrutinizer ignore-type */ $results > 0)) {
Loading history...
182
                $msg = "<h4>The following excluded words were found: </h4><ul>";
183
                foreach ($results as $file => $words) {
184
                    foreach ($words as $word) {
185
                        $msg .= "<li>$word in $file</li>";
186
                    }
187
                }
188
                $msg .= '</ul>';
189
190
                //trigger_error ("excluded words found in files(s)");
191
                GeneralMethods::output_to_screen($msg);
192
                UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
193
            }
194
        }
195
196
197
        foreach ($files as $file) {
198
            //run file update
199
200
            $obj = $file::create($moduleObject);
201
            $obj->run();
202
        }
203
204
        $moduleDir = $moduleObject->Directory();
205
206
        foreach ($commands as $command) {
207
            //run file update
208
209
210
            $obj = $command::create($moduleDir);
211
            $obj->run();
212
213
214
            //run command
215
        }
216
217
        //Update Repository description
218
        //$moduleObject->updateGitHubInfo(array());
219
220
        if (! $moduleObject->add()) {
221
            $msg = "Could not add files module to Repo";
222
            GeneralMethods::output_to_screen($msg);
223
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
224
            return;
225
        }
226
        if (! $moduleObject->commit()) {
227
            $msg = "Could not commit files to Repo";
228
            GeneralMethods::output_to_screen($msg);
229
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
230
            return;
231
        }
232
233
        if (! $moduleObject->push()) {
234
            $msg = "Could not push files to Repo";
235
            GeneralMethods::output_to_screen($msg);
236
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
237
            return;
238
        }
239
        if (! $moduleObject->removeClone()) {
240
            $msg = "Could not remove local copy of repo";
241
            GeneralMethods::output_to_screen($msg);
242
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
243
        }
244
245
        $addRepoToScrutinzer = $this->Config()->get('add_to_scrutinizer');
246
        if ($addRepoToScrutinzer) {
247
            $moduleObject->addRepoToScrutinzer();
248
        }
249
    }
250
251
252
253
    protected function renameTest($moduleObject)
254
    {
255
        $oldName = $moduleObject->Directory() . "/tests/ModuleTest.php";
256
257
        if (! file_exists($oldName)) {
258
            print_r($oldName);
259
            return false;
260
        }
261
262
263
264
        $newName = $moduleObject->Directory() . "tests/" . $moduleObject->ModuleName . "Test.php";
265
266
        GeneralMethods::output_to_screen("Renaming $oldName to $newName");
267
268
        unlink($newName);
269
270
        rename($oldName, $newName);
271
    }
272
273
    public static function addUnsolvedProblem($moduleName, $problemString)
274
    {
275
        if (!isset(UpdateModules::$unsolvedItems[$moduleName])) {
276
            UpdateModules::$unsolvedItems[$moduleName] = array();
277
        }
278
        array_push(UpdateModules::$unsolvedItems[$moduleName], $problemString);
279
    }
280
281
    protected function writeLog()
282
    {
283
        $debug = $this->Config()->get('debug');
0 ignored issues
show
Unused Code introduced by
The assignment to $debug is dead and can be removed.
Loading history...
284
285
        $dateStr =  date("Y/m/d H:i:s");
286
287
        $html = '<h1> Modules checker report at ' .$dateStr . '</h1>';
288
289
        if (count(UpdateModules::$unsolvedItems) == 0) {
290
            $html .= ' <h2> No unresolved problems in modules</h2>';
291
        } else {
292
            $html .= '
293
                <h2> Unresolved problems in modules</h2>
294
295
            <table border = 1>
296
                    <tr><th>Module</th><th>Problem</th></tr>';
297
298
            foreach (UpdateModules::$unsolvedItems as $moduleName => $problems) {
299
                if (is_array($problems)) {
300
                    foreach ($problems as $problem) {
301
                        $html .= '<tr><td>'.$moduleName.'</td><td>'. $problem .'</td></tr>';
302
                    }
303
                } elseif (is_string($problems)) {
304
                    $html .= '<tr><td>'.$moduleName.'</td><td>'. $problems.'</td></tr>';
305
                }
306
            }
307
            $html .= '</table>';
308
        }
309
310
311
312
        $logFolder = $this->Config()->get('logfolder');
313
314
        $filename = $logFolder . date('U') . '.html';
315
316
        GeneralMethods::output_to_screen("Writing to $filename");
317
318
        $result = file_put_contents($filename, $html);
319
320
        if (! $result) {
321
            GeneralMethods::output_to_screen("Could not write log file");
322
        }
323
    }
324
325
    protected function checkConfigYML($module)
326
    {
327
        $configYml = ConfigYML::create($module)->reWrite();
0 ignored issues
show
Unused Code introduced by
The assignment to $configYml is dead and can be removed.
Loading history...
328
    }
329
330
    private function checkFile($module, $filename)
331
    {
332
        $folder = GitHubModule::Config()->get('absolute_temp_folder');
333
        return file_exists($folder.'/'.$module.'/'.$filename);
334
    }
335
336
    private function checkReadMe($module)
0 ignored issues
show
Unused Code introduced by
The method checkReadMe() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
337
    {
338
        return $this->checkFile($module, "README.MD");
339
    }
340
341
    private function checkDirExcludedWords($directory, $wordArray)
342
    {
343
        $filesAndFolders = scandir($directory);
344
345
        $problem_files = array();
346
        foreach ($filesAndFolders as $fileOrFolder) {
347
            if ($fileOrFolder == '.' || $fileOrFolder == '..' || $fileOrFolder == '.git') {
348
                continue;
349
            }
350
351
            $fileOrFolderFullPath = $directory . '/' . $fileOrFolder;
352
            if (is_dir($fileOrFolderFullPath)) {
353
                $dir = $fileOrFolderFullPath;
354
                $problem_files = array_merge($this->checkDirExcludedWords($dir, $wordArray), $problem_files);
355
            }
356
            if (is_file($fileOrFolderFullPath)) {
357
                $file = $fileOrFolderFullPath;
358
                $matchedWords = $this->checkFileExcludedWords($file, $wordArray);
359
360
                if ($matchedWords) {
361
                    $problem_files[$file] = $matchedWords;
362
                }
363
            }
364
        }
365
366
        return $problem_files;
367
    }
368
369
    private function checkFileExcludedWords($fileName, $wordArray)
370
    {
371
        $matchedWords = array();
372
373
        $fileName = str_replace('////', '/', $fileName);
374
        if (filesize($fileName) == 0) {
375
            return $matchedWords;
376
        }
377
378
379
        $fileContent = file_get_contents($fileName);
380
        if (!$fileContent) {
381
            $msg = "Could not open $fileName to check for excluded words";
382
383
            GeneralMethods::output_to_screen($msg);
384
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $moduleObject seems to be never defined.
Loading history...
385
        }
386
387
        foreach ($wordArray as $word) {
388
            $matches = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $matches is dead and can be removed.
Loading history...
389
            $matchCount = preg_match_all('/' . $word . '/i', $fileContent);
390
391
392
393
394
395
            if ($matchCount > 0) {
396
                array_push($matchedWords, $word);
397
            }
398
        }
399
400
        return $matchedWords;
401
    }
402
403
    private function checkUpdateTag($moduleObject)
404
    {
405
        $tagDelayString = $this->Config()->get('tag_delay');
406
        $nextTag = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $nextTag is dead and can be removed.
Loading history...
407
408
        if (!$tagDelayString) {
409
            $tagDelayString = "-3 weeks";
410
        }
411
412
413
        $tagDelay = strtotime($tagDelayString);
414
        if (!$tagDelay) {
415
            $tagDelay = strtotime("-3 weeks");
416
        }
417
418
        $tag = $moduleObject->getLatestTag();
419
420
        $commitTime = $moduleObject->getLatestCommitTime();
421
422
        if (! $commitTime) { // if no commits, cannot create a tag
423
            return false;
424
        }
425
426
        $createTag = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $createTag is dead and can be removed.
Loading history...
427
428
429
        $newTagString  = '';
430
431
        if (! $tag) {
432
            $createTag = true;
433
            $newTagString = '1.0.0';
434
        } elseif ($tag && $commitTime > $tag['timestamp'] && $commitTime < $tagDelay) {
435
            $changeType = $moduleObject->getChangeTypeSinceLastTag();
436
437
            $newTagString = $this->findNextTag($tag, $changeType);
438
        }
439
440
        if ($newTagString) {
441
            GeneralMethods::output_to_screen('<li> Creating new tag  '.$newTagString.' ... </li>');
442
443
            //git tag -a 0.0.1 -m "testing tag"
444
            $options = array(
445
                'a' => $newTagString,
446
                'm' => $this->Config()->get('tag_create_message')
447
            );
448
449
            $moduleObject->createTag($options);
450
        }
451
452
        return true;
453
    }
454
455
    protected function findNextTag($tag, $changeType)
456
    {
457
        switch ($changeType) {
458
459
            case 'MAJOR':
460
            $tag['tagparts'][0] = intval($tag['tagparts'][0]) + 1;
461
            $tag['tagparts'][1] = 0;
462
            $tag['tagparts'][2] = 0;
463
            break;
464
465
            case 'MINOR':
466
467
            $tag['tagparts'][1] = intval($tag['tagparts'][1]) + 1;
468
            $tag['tagparts'][2] = 0;
469
            break;
470
471
            default:
472
            case 'PATCH':
473
            $tag['tagparts'][2] = intval($tag['tagparts'][2]) + 1;
474
            break;
475
        }
476
477
        $newTagString = trim(implode('.', $tag['tagparts']));
478
        return $newTagString;
479
    }
480
481
    protected function moveOldReadMe($moduleObject)
482
    {
483
        $tempDir = GitHubModule::Config()->get('absolute_temp_folder');
484
        $oldReadMe = $tempDir . '/' .  $moduleObject->ModuleName . '/' .'README.md';
485
486
        if (! file_exists($oldReadMe)) {
487
            return false;
488
        }
489
490
491
        $oldreadmeDestinationFiles = array(
492
            'docs/en/INDEX.md',
493
            'docs/en/README.old.md',
494
        );
495
496
497
        $copied = false;
498
        foreach ($oldreadmeDestinationFiles as $file) {
499
            $filePath = $tempDir . '/' .  $moduleObject->ModuleName . '/' . $file;
500
            FileSystem::makeFolder(dirname($filePath));
501
502
            if (!file_exists($filePath)) {
503
                $copied = true;
504
                GeneralMethods::output_to_screen('Copying '.$oldReadMe.' to '.$filePath);
505
                copy($oldReadMe, $filePath);
506
            }
507
        }
508
        if ($copied) {
509
            unlink($oldReadMe);
510
        }
511
    }
512
}
513