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 (#86)
by joseph
03:14 queued 01:25
created

LinksChecker   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Test Coverage

Coverage 95.79%

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 207
ccs 91
cts 95
cp 0.9579
rs 10
c 0
b 0
f 0
wmc 27

7 Methods

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

192
            $headers = get_headers($href, 0, /** @scrutinizer ignore-type */ $context);
Loading history...
193 1
            if (false === $headers) {
194
                throw new \RuntimeException('Failed getting headers for href ' . $href);
195
            }
196 1
            foreach ($headers as $header) {
197 1
                if (false !== strpos($header, ' 200 ')) {
198
                    #$time = round(microtime(true) - $start, 2);
199
                    #fwrite(STDERR, "\n".'OK ('.$time.' seconds): '.$href);
200
201 1
                    return;
202
                }
203
            }
204
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
205
        }
206
207 1
        $errors[] = \sprintf(
208 1
            "\nBad link for \"%s\" to \"%s\"\nresult: %s\n",
209 1
            $anchor,
210 1
            $href,
211 1
            var_export($result, true)
212
        );
213 1
        $return   = 1;
214
        #$time     = round(microtime(true) - $start, 2);
215
        #fwrite(STDERR, "\n".'Failed ('.$time.' seconds): '.$href);
216 1
    }
217
}
218