Completed
Push — master ( 9804c4...f16ecd )
by Nicolaas
01:55
created

UpdateModules::writeLog()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 43
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 21
nc 4
nop 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 46 and the first side effect is on line 8.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/**
4
 * main class running all the updates
5
 *
6
 *
7
 */
8
class UpdateModules extends BuildTask
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
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();
26
27
    /**
28
     * e.g.
29
     * - ClassNameForUpdatingFileA
30
     * - ClassNameForUpdatingFileB
31
     *
32
     * @var array
33
     */
34
    private static $files_to_update = [];
35
    /**
36
     * e.g.
37
     * - ClassNameForUpdatingFileA
38
     * - ClassNameForUpdatingFileB
39
     *
40
     * @var array
41
     */
42
    private static $commands_to_run = array();
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
        $tempDirFiles = scandir($tempFolder);
53
        if (count($tempDirFiles) > 2) {
54
            die('<h2>' . $tempFolder . ' is not empty, please delete or move files </h2>');
55
        }
56
57
        //Get list of all modules from GitHub
58
        $gitUserName = $this->Config()->get('github_user_name');
59
60
        $modules = GitRepoFinder::get_all_repos();
61
62
63
        /*
64
         * Get files to add to modules
65
         * */
66
        $files = ClassInfo::subclassesFor('AddFileToModule');
67
        array_shift($files);
68
        $limitedFileClasses = $this->Config()->get('files_to_update');
69
        if ($limitedFileClasses === []) {
70
            //do nothing
71
        elseif ($limitedFileClasses === 'none') {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_ELSEIF
Loading history...
72
            $files = [];
73
        } elseif (is_array($limitedFileClasses) && count($limitedFileClasses)) {
74
            $files = array_intersect($files, $limitedFileClasses);
75
        }
76
77
        /*
78
         * Get commands to run on modules
79
         * */
80
81
        $commands = ClassInfo::subclassesFor('RunCommandLineMethodOnModule');
82
        array_shift($commands);
83
        $limitedCommands = $this->Config()->get('commands_to_run');
84
        if ($limitedCommands === 'none') {
85
            $commands = [];
86
        } elseif (is_array($limitedCommands) && count($limitedCommands)) {
87
            $commands = array_intersect($commands, $limitedCommands);
88
        }
89
90
91
        set_error_handler('errorHandler', E_ALL);
92
        foreach ($modules as $count => $module) {
93
            $this->currentModule = $module;
94
            try {
95
                $this->processOneModule($module, $count, $files, $commands);
96
            } catch (Exception $e) {
97
                GeneralMethods::output_to_screen("<li> Could not complete processing $module: " .  $e->getMessage() . " </li>");
98
            }
99
        }
100
101
        restore_error_handler();
102
103
        $this->writeLog();
104
        //to do ..
105
    }
106
107
    protected function errorHandler(int $errno, string $errstr)
108
    {
109
        GeneralMethods::output_to_screen("<li> Could not complete processing module: " .  $errstr . " </li>");
110
111
        UpdateModules::addUnsolvedProblem($this->currentModule, "Could not complete processing module: " . $errstr);
112
113
        return true;
114
    }
115
116
    protected function processOneModule($module, $count, $files, $commands)
117
    {
118
        if (stripos($module, 'silverstripe-')  === false) {
119
            $module = "silverstripe-" . $module;
120
        }
121
        echo "<h2>" . ($count+1) . ". ".$module."</h2>";
122
123
124
        $moduleObject = GitHubModule::get_or_create_github_module($module);
125
126
        $this->checkUpdateTag($moduleObject);
127
128
        $updateComposerJson = $this->Config()->get('update_composer_json');
129
130
        // Check if all necessary files are perfect on GitHub repo already,
131
        // if so we can skip that module. But! ... if there are commands to run
132
        // over the files in the repo, then we need to clone the repo anyhow,
133
        // so skip the check
134
        if (count($commands) == 0 && ! $updateComposerJson) {
135
            $moduleFilesOK = true;
136
137
            foreach ($files as $file) {
138
                $fileObj = $file::create($moduleObject);
139
                $checkFileName = $fileObj->getFileLocation();
140
                $GitHubFileText = $moduleObject -> getRawFileFromGithub($checkFileName);
141
                if ($GitHubFileText) {
142
                    $fileCheck = $fileObj->compareWithText($GitHubFileText);
143
                    if (! $fileCheck) {
144
                        $moduleFilesOK = false;
145
                    }
146
                } else {
147
                    $moduleFilesOK = false;
148
                }
149
            }
150
        }
151
152
        $repository = $moduleObject->checkOrSetGitCommsWrapper($forceNew = true);
153
154
155
        $this->moveOldReadMe($moduleObject);
156
157
158
        $checkConfigYML = $this->Config()->get('check_config_yml');
159
        if ($checkConfigYML) {
160
            $this->checkConfigYML($moduleObject);
161
        }
162
163
        if ($updateComposerJson) {
164
            $composerJsonObj = new ComposerJson($moduleObject);
165
            $composerJsonObj->updateJsonFile();
166
            $moduleObject->setDescription($composerJsonObj->getDescription());
167
        }
168
169
        $excludedWords = $this->Config()->get('excluded_words');
170
171
172
        if (count($excludedWords) > 0) {
173
            $folder = GitHubModule::Config()->get('absolute_temp_folder') . '/' . $moduleObject->moduleName . '/';
174
175
            $results = $this->checkDirExcludedWords($folder.'/'.$moduleObject->modulename, $excludedWords);
176
177
178
            if ($results && count($results > 0)) {
179
                $msg = "<h4>The following excluded words were found: </h4><ul>";
180
                foreach ($results as $file => $words) {
181
                    foreach ($words as $word) {
182
                        $msg .= "<li>$word in $file</li>";
183
                    }
184
                }
185
                $msg .= '</ul>';
186
187
                //trigger_error ("excluded words found in files(s)");
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
188
                GeneralMethods::output_to_screen($msg);
189
                UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
190
            }
191
        }
192
193
194
        foreach ($files as $file) {
195
            //run file update
196
197
            $obj = $file::create($moduleObject);
198
            $obj->run();
199
        }
200
201
        $moduleDir = $moduleObject->Directory();
202
203
        foreach ($commands as $command) {
204
            //run file update
205
206
207
            $obj = $command::create($moduleDir);
208
            $obj->run();
209
210
211
            //run command
212
        }
213
214
        //Update Repository description
215
        //$moduleObject->updateGitHubInfo(array());
0 ignored issues
show
Unused Code Comprehensibility introduced by
89% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
216
217
        if (! $moduleObject->add()) {
218
            $msg = "Could not add files module to Repo";
219
            GeneralMethods::output_to_screen($msg);
220
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
221
            return;
222
        }
223
        if (! $moduleObject->commit()) {
224
            $msg = "Could not commit files to Repo";
225
            GeneralMethods::output_to_screen($msg);
226
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
227
            return;
228
        }
229
230
        if (! $moduleObject->push()) {
231
            $msg = "Could not push files to Repo";
232
            GeneralMethods::output_to_screen($msg);
233
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
234
            return;
235
        }
236
        if (! $moduleObject->removeClone()) {
237
            $msg = "Could not remove local copy of repo";
238
            GeneralMethods::output_to_screen($msg);
239
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
240
        }
241
242
        $addRepoToScrutinzer = $this->Config()->get('add_to_scrutinizer');
243
        if ($addRepoToScrutinzer) {
244
            $moduleObject->addRepoToScrutinzer();
245
        }
246
    }
247
248
249
250
    protected function renameTest($moduleObject)
251
    {
252
        $oldName = $moduleObject->Directory() . "/tests/ModuleTest.php";
253
254
        if (! file_exists($oldName)) {
255
            print_r($oldName);
256
            return false;
257
        }
258
259
260
261
        $newName = $moduleObject->Directory() . "tests/" . $moduleObject->ModuleName . "Test.php";
262
263
        GeneralMethods::output_to_screen("Renaming $oldName to $newName");
264
265
        unlink($newName);
266
267
        rename($oldName, $newName);
268
    }
269
270
    public static function addUnsolvedProblem($moduleName, $problemString)
271
    {
272
        if (!isset(UpdateModules::$unsolvedItems[$moduleName])) {
273
            UpdateModules::$unsolvedItems[$moduleName] = array();
274
        }
275
        array_push(UpdateModules::$unsolvedItems[$moduleName], $problemString);
276
    }
277
278
    protected function writeLog()
279
    {
280
        $debug = $this->Config()->get('debug');
281
282
        $dateStr =  date("Y/m/d H:i:s");
283
284
        $html = '<h1> Modules checker report at ' .$dateStr . '</h1>';
285
286
        if (count(UpdateModules::$unsolvedItems) == 0) {
287
            $html .= ' <h2> No unresolved problems in modules</h2>';
288
        } else {
289
            $html .= '
290
                <h2> Unresolved problems in modules</h2>
291
292
            <table border = 1>
293
                    <tr><th>Module</th><th>Problem</th></tr>';
294
295
            foreach (UpdateModules::$unsolvedItems as $moduleName => $problems) {
296
                if (is_array($problems)) {
297
                    foreach ($problems as $problem) {
298
                        $html .= '<tr><td>'.$moduleName.'</td><td>'. $problem .'</td></tr>';
299
                    }
300
                } elseif (is_string($problems)) {
301
                    $html .= '<tr><td>'.$moduleName.'</td><td>'. $problems.'</td></tr>';
302
                }
303
            }
304
            $html .= '</table>';
305
        }
306
307
308
309
        $logFolder = $this->Config()->get('logfolder');
310
311
        $filename = $logFolder . date('U') . '.html';
312
313
        GeneralMethods::output_to_screen("Writing to $filename");
314
315
        $result = file_put_contents($filename, $html);
316
317
        if (! $result) {
318
            GeneralMethods::output_to_screen("Could not write log file");
319
        }
320
    }
321
322
    protected function checkConfigYML($module)
323
    {
324
        $configYml = ConfigYML::create($module)->reWrite();
325
    }
326
327
    private function checkFile($module, $filename)
328
    {
329
        $folder = GitHubModule::Config()->get('absolute_temp_folder');
330
        return file_exists($folder.'/'.$module.'/'.$filename);
331
    }
332
333
    private function checkReadMe($module)
334
    {
335
        return $this->checkFile($module, "README.MD");
336
    }
337
338
    private function checkDirExcludedWords($directory, $wordArray)
339
    {
340
        $filesAndFolders = scandir($directory);
341
342
        $problem_files = array();
343
        foreach ($filesAndFolders as $fileOrFolder) {
344
            if ($fileOrFolder == '.' || $fileOrFolder == '..' || $fileOrFolder == '.git') {
345
                continue;
346
            }
347
348
            $fileOrFolderFullPath = $directory . '/' . $fileOrFolder;
349
            if (is_dir($fileOrFolderFullPath)) {
350
                $dir = $fileOrFolderFullPath;
351
                $problem_files = array_merge($this->checkDirExcludedWords($dir, $wordArray), $problem_files);
352
            }
353
            if (is_file($fileOrFolderFullPath)) {
354
                $file = $fileOrFolderFullPath;
355
                $matchedWords = $this->checkFileExcludedWords($file, $wordArray);
356
357
                if ($matchedWords) {
358
                    $problem_files[$file] = $matchedWords;
359
                }
360
            }
361
        }
362
363
        return $problem_files;
364
    }
365
366
    private function checkFileExcludedWords($fileName, $wordArray)
367
    {
368
        $matchedWords = array();
369
370
        $fileName = str_replace('////', '/', $fileName);
371
        if (filesize($fileName) == 0) {
372
            return $matchedWords;
373
        }
374
375
376
        $fileContent = file_get_contents($fileName);
377
        if (!$fileContent) {
378
            $msg = "Could not open $fileName to check for excluded words";
379
380
            GeneralMethods::output_to_screen($msg);
381
            UpdateModules::$unsolvedItems[$moduleObject->ModuleName] = $msg;
382
        }
383
384
        foreach ($wordArray as $word) {
385
            $matches = array();
386
            $matchCount = preg_match_all('/' . $word . '/i', $fileContent);
387
388
389
390
391
392
            if ($matchCount > 0) {
393
                array_push($matchedWords, $word);
394
            }
395
        }
396
397
        return $matchedWords;
398
    }
399
400
    private function checkUpdateTag($moduleObject)
401
    {
402
        $tagDelayString = $this->Config()->get('tag_delay');
403
        $nextTag = null;
404
405
        if (!$tagDelayString) {
406
            $tagDelayString = "-3 weeks";
407
        }
408
409
410
        $tagDelay = strtotime($tagDelayString);
411
        if (!$tagDelay) {
412
            $tagDelay = strtotime("-3 weeks");
413
        }
414
415
        $tag = $moduleObject->getLatestTag();
416
417
        $commitTime = $moduleObject->getLatestCommitTime();
418
419
        if (! $commitTime) { // if no commits, cannot create a tag
420
            return false;
421
        }
422
423
        $createTag = false;
424
425
426
        $newTagString  = '';
427
428
        if (! $tag) {
429
            $createTag = true;
430
            $newTagString = '1.0.0';
431
        } elseif ($tag && $commitTime > $tag['timestamp'] && $commitTime < $tagDelay) {
432
            $changeType = $moduleObject->getChangeTypeSinceLastTag();
433
434
            $newTagString = $this->findNextTag($tag, $changeType);
435
        }
436
437
        if ($newTagString) {
438
            GeneralMethods::output_to_screen('<li> Creating new tag  '.$newTagString.' ... </li>');
439
440
            //git tag -a 0.0.1 -m "testing tag"
441
            $options = array(
442
                'a' => $newTagString,
443
                'm' => $this->Config()->get('tag_create_message')
444
            );
445
446
            $moduleObject->createTag($options);
447
        }
448
449
        return true;
450
    }
451
452
    protected function findNextTag($tag, $changeType)
453
    {
454
        switch ($changeType) {
455
456
            case 'MAJOR':
457
            $tag['tagparts'][0] = intval($tag['tagparts'][0]) + 1;
458
            $tag['tagparts'][1] = 0;
459
            $tag['tagparts'][2] = 0;
460
            break;
461
462
            case 'MINOR':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
463
464
            $tag['tagparts'][1] = intval($tag['tagparts'][1]) + 1;
465
            $tag['tagparts'][2] = 0;
466
            break;
467
468
            default:
469
            case 'PATCH':
470
            $tag['tagparts'][2] = intval($tag['tagparts'][2]) + 1;
471
            break;
472
        }
473
474
        $newTagString = trim(implode('.', $tag['tagparts']));
475
        return $newTagString;
476
    }
477
478
    protected function moveOldReadMe($moduleObject)
479
    {
480
        $tempDir = GitHubModule::Config()->get('absolute_temp_folder');
481
        $oldReadMe = $tempDir . '/' .  $moduleObject->ModuleName . '/' .'README.md';
482
483
        if (! file_exists($oldReadMe)) {
484
            return false;
485
        }
486
487
488
        $oldreadmeDestinationFiles = array(
489
            'docs/en/INDEX.md',
490
            'docs/en/README.old.md',
491
        );
492
493
494
        $copied = false;
495
        foreach ($oldreadmeDestinationFiles as $file) {
496
            $filePath = $tempDir . '/' .  $moduleObject->ModuleName . '/' . $file;
497
            FileSystem::makeFolder(dirname($filePath));
498
499
            if (!file_exists($filePath)) {
500
                $copied = true;
501
                GeneralMethods::output_to_screen('Copying '.$oldReadMe.' to '.$filePath);
502
                copy($oldReadMe, $filePath);
503
            }
504
        }
505
        if ($copied) {
506
            unlink($oldReadMe);
507
        }
508
    }
509
}
510