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.

AbstractContentParser   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 399
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 160
c 5
b 0
f 0
dl 0
loc 399
ccs 0
cts 99
cp 0
rs 9.44
wmc 37

16 Methods

Rating   Name   Duplication   Size   Complexity  
B processHTMLContent() 0 42 7
B tabContent() 0 62 6
A getLocalizedFileDir() 0 3 1
A getDefaultDocsLanguage() 0 3 1
A getContent() 0 36 5
A getDefaultFileURL() 0 4 1
A getDefaultFileDir() 0 3 1
A getFileDir() 0 3 1
A isAbsoluteURL() 0 3 2
A embedVideos() 0 19 1
A appendPathURLToElement() 0 27 3
A isMailto() 0 3 1
A appendPathURLToImages() 0 3 1
A appendPathURLToAnchors() 0 3 1
A addClasses() 0 9 1
A convertMarkdownLinks() 0 51 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLAPI\GraphQLAPI\ContentProcessors;
6
7
use InvalidArgumentException;
8
use PoP\ComponentModel\Misc\RequestUtils;
9
use GraphQLAPI\GraphQLAPI\General\LocaleUtils;
10
use GraphQLAPI\GraphQLAPI\General\RequestParams;
11
use GraphQLAPI\GraphQLAPI\PluginConstants;
12
13
abstract class AbstractContentParser implements ContentParserInterface
14
{
15
    public const PATH_URL_TO_DOCS = 'pathURLToDocs';
16
17
    /**
18
     * Parse the file's Markdown into HTML Content
19
     *
20
     * @param string $relativePathDir Dir relative to the /docs/${lang}/ folder
21
     * @throws InvalidArgumentException When the file is not found
22
     */
23
    public function getContent(
24
        string $filename,
25
        string $relativePathDir = '',
26
        array $options = []
27
    ): string {
28
        // Make sure the relative path ends with "/"
29
        if ($relativePathDir) {
30
            $relativePathDir = \trailingslashit($relativePathDir);
31
        }
32
        $localizeFile = \trailingslashit($this->getLocalizedFileDir()) . $filename;
33
        if (file_exists($localizeFile)) {
34
            // First check if the localized version exists
35
            $file = $localizeFile;
36
        } else {
37
            // Otherwise, use the default language version
38
            $file = \trailingslashit($this->getDefaultFileDir()) . $filename;
39
            // Make sure this file exists
40
            if (!file_exists($file)) {
41
                throw new InvalidArgumentException(sprintf(
42
                    \__('File \'%s\' does not exist', 'graphql-api'),
43
                    $file
44
                ));
45
            }
46
        }
47
        $fileContent = file_get_contents($file);
48
        if ($fileContent === false) {
49
            throw new InvalidArgumentException(sprintf(
50
                \__('File \'%s\' is corrupted', 'graphql-api'),
51
                $file
52
            ));
53
        }
54
        $htmlContent = $this->getHTMLContent($fileContent);
55
        $pathURL = \trailingslashit($this->getDefaultFileURL()) . $relativePathDir;
56
        // Include the images from the GitHub repo
57
        $options[self::PATH_URL_TO_DOCS] = PluginConstants::GITHUB_REPO_DOCS_PATH_URL . $relativePathDir;
58
        return $this->processHTMLContent($htmlContent, $pathURL, $options);
59
    }
60
61
    /**
62
     * Where the markdown file localized to the user's language is stored
63
     */
64
    public function getLocalizedFileDir(): string
65
    {
66
        return $this->getFileDir(LocaleUtils::getLocaleLanguage());
67
    }
68
69
    /**
70
     * Where the default markdown file (for if the localized language is not available) is stored
71
     * Default language for documentation: English
72
     */
73
    public function getDefaultFileDir(): string
74
    {
75
        return $this->getFileDir($this->getDefaultDocsLanguage());
76
    }
77
78
    /**
79
     * Default language for documentation: English
80
     */
81
    public function getDefaultDocsLanguage(): string
82
    {
83
        return 'en';
84
    }
85
86
    /**
87
     * Path where to find the local images
88
     */
89
    protected function getFileDir(string $lang): string
90
    {
91
        return constant('GRAPHQL_API_DIR') . "/docs/${lang}";
92
    }
93
94
    /**
95
     * Path URL to append to the local images referenced in the markdown file
96
     */
97
    protected function getDefaultFileURL(): string
98
    {
99
        $lang = $this->getDefaultDocsLanguage();
100
        return constant('GRAPHQL_API_URL') . "docs/${lang}";
101
    }
102
103
    /**
104
     * Process the HTML content:
105
     *
106
     * - Add the path to the images and anchors
107
     * - Add classes to HTML elements
108
     * - Append video embeds
109
     */
110
    abstract protected function getHTMLContent(string $fileContent): string;
111
112
    /**
113
     * Process the HTML content:
114
     *
115
     * - Add the path to the images and anchors
116
     * - Add classes to HTML elements
117
     * - Append video embeds
118
     *
119
     * @param array<string, mixed> $options
120
     */
121
    protected function processHTMLContent(string $htmlContent, string $pathURL, array $options = []): string
122
    {
123
        // Add default values for the options
124
        $options = array_merge(
125
            [
126
                ContentParserOptions::APPEND_PATH_URL_TO_IMAGES => true,
127
                ContentParserOptions::APPEND_PATH_URL_TO_ANCHORS => true,
128
                ContentParserOptions::SUPPORT_MARKDOWN_LINKS => true,
129
                ContentParserOptions::ADD_CLASSES => true,
130
                ContentParserOptions::EMBED_VIDEOS => true,
131
                ContentParserOptions::TAB_CONTENT => false,
132
            ],
133
            $options
134
        );
135
        // Add the path to the images
136
        if ($options[ContentParserOptions::APPEND_PATH_URL_TO_IMAGES] ?? null) {
137
            // Enable to override the path for images, to read them from
138
            // the GitHub repo and avoid including them in the plugin
139
            $imagePathURL = $options[self::PATH_URL_TO_DOCS] ?? $pathURL;
140
            $htmlContent = $this->appendPathURLToImages($imagePathURL, $htmlContent);
141
        }
142
        // Convert Markdown links: execute before appending path to anchors
143
        if ($options[ContentParserOptions::SUPPORT_MARKDOWN_LINKS] ?? null) {
144
            $htmlContent = $this->convertMarkdownLinks($htmlContent);
145
        }
146
        // Add the path to the anchors
147
        if ($options[ContentParserOptions::APPEND_PATH_URL_TO_ANCHORS] ?? null) {
148
            $htmlContent = $this->appendPathURLToAnchors($pathURL, $htmlContent);
149
        }
150
        // Add classes to HTML elements
151
        if ($options[ContentParserOptions::ADD_CLASSES] ?? null) {
152
            $htmlContent = $this->addClasses($htmlContent);
153
        }
154
        // Append video embeds
155
        if ($options[ContentParserOptions::EMBED_VIDEOS] ?? null) {
156
            $htmlContent = $this->embedVideos($htmlContent);
157
        }
158
        // Convert the <h2> into tabs
159
        if ($options[ContentParserOptions::TAB_CONTENT] ?? null) {
160
            $htmlContent = $this->tabContent($htmlContent);
161
        }
162
        return $htmlContent;
163
    }
164
165
    /**
166
     * Add tabs to the content wherever there is an <h2>
167
     */
168
    protected function tabContent(string $htmlContent): string
169
    {
170
        $tag = 'h2';
171
        $firstTagPos = strpos($htmlContent, '<' . $tag . '>');
172
        // Check if there is any <h2>
173
        if ($firstTagPos !== false) {
174
            // Content before the first <h2> does not go within any tab
175
            $contentStarter = substr(
176
                $htmlContent,
177
                0,
178
                $firstTagPos
179
            );
180
            // Add the markup for the tabs around every <h2>
181
            $regex = sprintf(
182
                '/<%1$s>(.*?)<\/%1$s>/',
183
                $tag
184
            );
185
            $headers = [];
186
            $panelContent = preg_replace_callback(
187
                $regex,
188
                function ($matches) use (&$headers) {
189
                    $isFirstTab = empty($headers);
190
                    if (!$isFirstTab) {
191
                        $tabbedPanel = '</div>';
192
                    } else {
193
                        $tabbedPanel = '';
194
                    }
195
                    $headers[] = $matches[1];
196
                    return $tabbedPanel . sprintf(
197
                        '<div id="doc-panel-%s" class="tab-content" style="display: %s;">',
198
                        count($headers),
199
                        $isFirstTab ? 'block' : 'none'
200
                    );// . $matches[0];
201
                },
202
                substr(
203
                    $htmlContent,
204
                    $firstTagPos
205
                )
206
            ) . '</div>';
207
208
            // Create the tabs
209
            $panelTabs = '<h2 class="nav-tab-wrapper">';
210
            $headersCount = count($headers);
211
            for ($i = 0; $i < $headersCount; $i++) {
212
                $isFirstTab = $i == 0;
213
                $panelTabs .= sprintf(
214
                    '<a href="#doc-panel-%s" class="nav-tab %s">%s</a>',
215
                    $i + 1,
216
                    $isFirstTab ? 'nav-tab-active' : '',
217
                    $headers[$i]
218
                );
219
            }
220
            $panelTabs .= '</h2>';
221
222
            return
223
                $contentStarter
224
                . '<div class="graphql-api-tabpanel">'
225
                . $panelTabs
226
                . $panelContent
227
                . '</div>';
228
        }
229
        return $htmlContent;
230
    }
231
232
    /**
233
     * Is the anchor pointing to an URL?
234
     */
235
    protected function isAbsoluteURL(string $href): bool
236
    {
237
        return \str_starts_with($href, 'http://') || \str_starts_with($href, 'https://');
238
    }
239
240
    /**
241
     * Is the anchor pointing to an email?
242
     */
243
    protected function isMailto(string $href): bool
244
    {
245
        return \str_starts_with($href, 'mailto:');
246
    }
247
248
    /**
249
     * Whenever a link points to a .md file, convert it
250
     * so it works also within the plugin
251
     */
252
    protected function convertMarkdownLinks(string $htmlContent): string
253
    {
254
        return (string)preg_replace_callback(
255
            '/<a.*href="(.*?)\.md".*?>/',
256
            function (array $matches): string {
257
                // If the element has an absolute route, then no need
258
                if ($this->isAbsoluteURL($matches[1]) || $this->isMailto($matches[1])) {
259
                    return $matches[0];
260
                }
261
                // The URL is the current one, plus attr to open the .md file
262
                // in a modal window
263
                $elementURL = \add_query_arg(
264
                    [
265
                        RequestParams::TAB => RequestParams::TAB_DOCS,
266
                        RequestParams::DOC => $matches[1],
267
                        'TB_iframe' => 'true',
268
                    ],
269
                    RequestUtils::getRequestedFullURL()
270
                );
271
                /** @var string */
272
                $link = str_replace(
273
                    "href=\"{$matches[1]}.md\"",
274
                    "href=\"{$elementURL}\"",
275
                    $matches[0]
276
                );
277
                // Must also add some classnames
278
                $classnames = 'thickbox open-plugin-details-modal';
279
                // 1. If there are classes already
280
                /** @var string */
281
                $replacedLink = preg_replace_callback(
282
                    '/ class="(.*?)"/',
283
                    function (array $matches) use ($classnames): string {
284
                        return str_replace(
285
                            " class=\"{$matches[1]}\"",
286
                            " class=\"{$matches[1]} {$classnames}\"",
287
                            $matches[0]
288
                        );
289
                    },
290
                    $link
291
                );
292
                // 2. If there were no classes
293
                if ($replacedLink == $link) {
294
                    $replacedLink = str_replace(
295
                        "<a ",
296
                        "<a class=\"{$classnames}\" ",
297
                        $link
298
                    );
299
                }
300
                return $replacedLink;
301
            },
302
            $htmlContent
303
        );
304
    }
305
306
    /**
307
     * Append video embeds. These are not already in the markdown file
308
     * because GitHub can't add `<iframe>`. Then, the source only contains
309
     * a link to the video. This must be identified, and transformed into
310
     * the embed.
311
     *
312
     * The match is produced when a link is pointing to a video in
313
     * Vimeo or Youtube by the end of the paragraph, with/out a final dot.
314
     */
315
    protected function embedVideos(string $htmlContent): string
316
    {
317
        // Identify videos from Vimeo/Youtube
318
        return (string)preg_replace_callback(
319
            '/<p>(.*?)<a href="https:\/\/(vimeo.com|youtube.com|youtu.be)\/(.*?)">(.*?)<\/a>\.?<\/p>/',
320
            function ($matches) {
321
                global $wp_embed;
322
                // Keep the link, and append the embed immediately after
323
                return
324
                    $matches[0]
325
                    . '<div class="video-responsive-container">' .
326
                        $wp_embed->autoembed(sprintf(
327
                            'https://%s/%s',
328
                            $matches[2],
329
                            $matches[3]
330
                        ))
331
                    . '</div>';
332
            },
333
            $htmlContent
334
        );
335
    }
336
337
    /**
338
     * Add classes to the HTML elements
339
     */
340
    protected function addClasses(string $htmlContent): string
341
    {
342
        /**
343
         * Add class "wp-list-table widefat" to all tables
344
         */
345
        return str_replace(
346
            '<table>',
347
            '<table class="wp-list-table widefat striped">',
348
            $htmlContent
349
        );
350
    }
351
352
    /**
353
     * Convert relative paths to absolute paths for image URLs
354
     *
355
     * @param string $pathURL
356
     * @param string $htmlContent
357
     * @return string
358
     */
359
    protected function appendPathURLToImages(string $pathURL, string $htmlContent): string
360
    {
361
        return $this->appendPathURLToElement('img', 'src', $pathURL, $htmlContent);
362
    }
363
364
    /**
365
     * Convert relative paths to absolute paths for image URLs
366
     *
367
     * @param string $pathURL
368
     * @param string $htmlContent
369
     * @return string
370
     */
371
    protected function appendPathURLToAnchors(string $pathURL, string $htmlContent): string
372
    {
373
        return $this->appendPathURLToElement('a', 'href', $pathURL, $htmlContent);
374
    }
375
376
    /**
377
     * Convert relative paths to absolute paths for elements
378
     *
379
     * @param string $tag
380
     * @param string $attr
381
     * @param string $pathURL
382
     * @param string $htmlContent
383
     * @return string
384
     */
385
    protected function appendPathURLToElement(string $tag, string $attr, string $pathURL, string $htmlContent): string
386
    {
387
        /**
388
         * $regex will become:
389
         * - /<img.*src="(.*?)".*?>/
390
         * - /<a.*href="(.*?)".*?>/
391
         */
392
        $regex = sprintf(
393
            '/<%s.*%s="(.*?)".*?>/',
394
            $tag,
395
            $attr
396
        );
397
        return (string)preg_replace_callback(
398
            $regex,
399
            function ($matches) use ($pathURL, $attr) {
400
                // If the element has an absolute route, then no need
401
                if ($this->isAbsoluteURL($matches[1]) || $this->isMailto($matches[1])) {
402
                    return $matches[0];
403
                }
404
                $elementURL = \trailingslashit($pathURL) . $matches[1];
405
                return str_replace(
406
                    "{$attr}=\"{$matches[1]}\"",
407
                    "{$attr}=\"{$elementURL}\"",
408
                    $matches[0]
409
                );
410
            },
411
            $htmlContent
412
        );
413
    }
414
}
415