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 (#76)
by joseph
04:09
created

LinksChecker::main()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 5
nop 1
dl 0
loc 19
ccs 14
cts 14
cp 1
crap 4
rs 9.8333
c 0
b 0
f 0
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
            $start           .= '/' . \rtrim($relativeSubdirs, '/');
128
        }
129 2
        $realpath = \realpath($start . '/' . $path);
130 2
        if (false === $realpath) {
131 1
            $errors[] = \sprintf("\nBad link for \"%s\" to \"%s\"\n", $link[1], $link[2]);
132 1
            $return   = 1;
133
        }
134 2
    }
135
136
137
    /**
138
     * @param string|null $projectRootDirectory
139
     *
140
     * @return int
141
     * @throws \Exception
142
     * @SuppressWarnings(PHPMD.StaticAccess)
143
     */
144 4
    public static function main(string $projectRootDirectory = null): int
145
    {
146 4
        $return               = 0;
147 4
        $projectRootDirectory = $projectRootDirectory ?? Helper::getProjectRootDirectory();
148 4
        $files                = static::getFiles($projectRootDirectory);
149 3
        foreach ($files as $file) {
150 3
            $relativeFile = str_replace($projectRootDirectory, '', $file);
151 3
            $title        = "\n$relativeFile\n" . str_repeat('-', strlen($relativeFile)) . "\n";
152 3
            $errors       = [];
153 3
            $links        = static::getLinks($file);
154 3
            foreach ($links as $link) {
155 3
                static::checkLink($projectRootDirectory, $link, $file, $errors, $return);
156
            }
157 3
            if ([] !== $errors) {
158 3
                echo $title . implode('', $errors);
159
            }
160
        }
161
162 3
        return $return;
163
    }
164
165 1
    private static function validateHttpLink(array $link, array &$errors, int &$return): void
166
    {
167 1
        list(, $anchor, $href) = $link;
168 1
        static $checked = [];
169 1
        $hashPos = (int)strpos($href, '#');
170 1
        if ($hashPos > 0) {
171
            $href = substr($href, 0, $hashPos);
172
        }
173 1
        if (isset($checked[$href])) {
174
            return;
175
        }
176 1
        $checked[$href] = true;
177
        #$start          = microtime(true);
178
        #fwrite(STDERR, "\n".'Validating link: '.$href);
179 1
        $context = stream_context_create([
180 1
                                             'http' => [
181
                                                 'method'           => 'HEAD',
182
                                                 'protocol_version' => 1.1,
183
                                                 'header'           => [
184
                                                     'Connection: close',
185
                                                 ],
186
                                             ],
187
                                         ]);
188 1
        $result  = null;
189
        try {
190 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

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