GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#99)
by joseph
03:46
created

LinksChecker::getFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\PHPQA\Markdown;
6
7
use EdmondsCommerce\PHPQA\Helper;
8
use Exception;
9
use RecursiveDirectoryIterator;
10
use RecursiveIteratorIterator;
11
use RecursiveRegexIterator;
12
use RegexIterator;
13
use RuntimeException;
14
use Throwable;
15
16
use function current;
17
use function dirname;
18
use function explode;
19
use function preg_match;
20
use function preg_replace;
21
use function realpath;
22
use function rtrim;
23
use function sprintf;
24
use function str_repeat;
25
use function str_replace;
26
use function strlen;
27
use function strpos;
28
use function trim;
29
30
final class LinksChecker
31
{
32
    /**
33
     * @throws Exception
34
     * @SuppressWarnings(PHPMD.StaticAccess)
35
     */
36 4
    public static function main(string $projectRootDirectory = null): int
37
    {
38 4
        $return               = 0;
39 4
        $projectRootDirectory = $projectRootDirectory ?? Helper::getProjectRootDirectory();
40 4
        $files                = static::getFiles($projectRootDirectory);
41 3
        foreach ($files as $file) {
42 3
            $relativeFile = str_replace([$projectRootDirectory], [''], $file);
43 3
            $title        = "\n{$relativeFile}\n" . str_repeat('-', strlen($relativeFile)) . "\n";
44 3
            $errors       = [];
45 3
            $links        = static::getLinks($file);
46 3
            foreach ($links as $link) {
47 3
                static::checkLink($projectRootDirectory, $link, $file, $errors, $return);
48
            }
49 3
            if ([] !== $errors) {
50 2
                echo $title . implode('', $errors);
51
            }
52
        }
53
54 3
        return $return;
55
    }
56
57
    /**
58
     * @return string[]
59
     * @SuppressWarnings(PHPMD.StaticAccess)
60
     */
61 4
    private static function getFiles(string $projectRootDirectory): array
62
    {
63 4
        $files   = self::getDocsFiles($projectRootDirectory);
64 4
        $files[] = self::getMainReadme($projectRootDirectory);
65
66 3
        return $files;
67
    }
68
69
    /**
70
     * @SuppressWarnings(PHPMD.StaticAccess)
71
     *
72
     * @return string[]
73
     */
74 4
    private static function getDocsFiles(string $projectRootDirectory): array
75
    {
76 4
        $files = [];
77 4
        $dir   = $projectRootDirectory . '/docs';
78 4
        if (!is_dir($dir)) {
79 2
            return $files;
80
        }
81 2
        $directory = new RecursiveDirectoryIterator($dir);
82 2
        $recursive = new RecursiveIteratorIterator($directory);
83 2
        $regex     = new RegexIterator(
84 2
            $recursive,
85 2
            '/^.+\.md/i',
86 2
            RecursiveRegexIterator::GET_MATCH
87
        );
88 2
        foreach ($regex as $file) {
89 2
            if ($file[0] !== '') {
90 2
                $files[] = $file[0];
91
            }
92
        }
93
94 2
        return $files;
95
    }
96
97
    /**
98
     * @SuppressWarnings(PHPMD.StaticAccess)
99
     */
100 4
    private static function getMainReadme(string $projectRootDirectory): string
101
    {
102 4
        $path = $projectRootDirectory . '/README.md';
103 4
        if (!is_file($path)) {
104 1
            throw new RuntimeException(
105
                "\n\nYou have no README.md file in your project"
106 1
                . "\n\nAs the bear minimum you need to have this file to pass QA"
107
            );
108
        }
109
110 3
        return $path;
111
    }
112
113
    /**
114
     * @SuppressWarnings(PHPMD.StaticAccess)
115
     *
116
     * @return array<array<string>>
117
     */
118 3
    private static function getLinks(string $file): array
119
    {
120 3
        $links    = [];
121 3
        $contents = (string)file_get_contents($file);
122
        if (
123 3
            preg_match_all(
124 3
                '/\[(.+?)\].*?\((.+?)\)/',
125 3
                $contents,
126 3
                $matches,
127 3
                PREG_SET_ORDER
128 3
            ) !== false
129
        ) {
130 3
            $links = array_merge($links, $matches);
131
        }
132
133 3
        return $links;
134
    }
135
136
    /**
137
     * @SuppressWarnings(PHPMD.StaticAccess)
138
     *
139
     * @param string[] $link
140
     * @param string[] $errors
141
     */
142 3
    private static function checkLink(
143
        string $projectRootDirectory,
144
        array $link,
145
        string $file,
146
        array &$errors,
147
        int &$return
148
    ): void {
149 3
        $path = trim($link[2]);
150 3
        if (strpos($path, '#') === 0) {
151 1
            return;
152
        }
153 3
        if (preg_match('%^(http|//)%', $path) === 1) {
154 1
            self::validateHttpLink($link, $errors, $return);
155
156 1
            return;
157
        }
158
159 2
        $path  = current(explode('#', $path, 2));
160 2
        $start = rtrim($projectRootDirectory, '/');
161 2
        if ($path[0] !== '/' || strpos($path, './') === 0) {
162 2
            $relativeSubdirs = preg_replace(
163 2
                '%^' . $projectRootDirectory . '%',
164 2
                '',
165 2
                dirname($file)
166
            );
167 2
            if ($relativeSubdirs !== null) {
168 2
                $start .= '/' . rtrim($relativeSubdirs, '/');
169
            }
170
        }
171 2
        $realpath = realpath($start . '/' . $path);
172 2
        if ($realpath === false) {
173 1
            $errors[] = sprintf("\nBad link for \"%s\" to \"%s\"\n", $link[1], $link[2]);
174 1
            $return   = 1;
175
        }
176 2
    }
177
178
    /**
179
     * @param string[] $link
180
     * @param string[] $errors
181
     */
182 1
    private static function validateHttpLink(array $link, array &$errors, int &$return): void
183
    {
184 1
        list(, $anchor, $href) = $link;
185 1
        static $checked        = [];
186 1
        $hashPos               = (int)strpos($href, '#');
187 1
        if ($hashPos > 0) {
188
            $href = substr($href, 0, $hashPos);
189
        }
190 1
        if (isset($checked[$href])) {
191
            return;
192
        }
193 1
        $checked[$href] = true;
194
        //$start          = microtime(true);
195
        //fwrite(STDERR, "\n".'Validating link: '.$href);
196 1
        $context = stream_context_create([
197 1
            'http' => [
198
                'method'           => 'HEAD',
199
                'protocol_version' => 1.1,
200
                'header'           => [
201
                    'Connection: close',
202
                ],
203
            ],
204
        ]);
205 1
        $result  = null;
206
        try {
207 1
            $headers = get_headers($href, 0, $context);
0 ignored issues
show
Bug introduced by
$context of type resource is incompatible with the type null|resource expected by parameter $context of get_headers(). ( Ignorable by Annotation )

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

207
            $headers = get_headers($href, 0, /** @scrutinizer ignore-type */ $context);
Loading history...
208 1
            if ($headers === false) {
209
                throw new RuntimeException('Failed getting headers for href ' . $href);
210
            }
211 1
            foreach ($headers as $header) {
212 1
                if (strpos($header, ' 200 ') !== false) {
213
                    //$time = round(microtime(true) - $start, 2);
214
                    //fwrite(STDERR, "\n".'OK ('.$time.' seconds): '.$href);
215
216 1
                    return;
217
                }
218
            }
219
        } catch (Throwable $e) {
220
            throw new RuntimeException('Unexpected error ' . $e->getMessage(), $e->getCode(), $e);
221
        }
222
223 1
        $errors[] = sprintf(
224 1
            "\nBad link for \"%s\" to \"%s\"\nresult: %s\n",
225 1
            $anchor,
226 1
            $href,
227 1
            var_export($result, true)
228
        );
229 1
        $return   = 1;
230
        //$time     = round(microtime(true) - $start, 2);
231
        //fwrite(STDERR, "\n".'Failed ('.$time.' seconds): '.$href);
232 1
    }
233
}
234