Passed
Push — master ( 3b69a6...ebea0a )
by Caen
05:44 queued 02:49
created

MarkdownService::injectTorchlightAttribution()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
3
namespace Hyde\Framework\Services;
4
5
use Hyde\Framework\Actions\MarkdownConverter;
6
use Hyde\Framework\Concerns\Internal\SetsUpMarkdownConverter;
7
use Hyde\Framework\Contracts\MarkdownPostProcessorContract as PostProcessor;
8
use Hyde\Framework\Contracts\MarkdownPreProcessorContract as PreProcessor;
9
use Hyde\Framework\Helpers\Features;
10
use Hyde\Framework\Models\Pages\DocumentationPage;
11
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
12
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
13
14
/**
15
 * Dynamically creates a Markdown converter tailored for the target model and setup,
16
 * then converts the Markdown to HTML using both pre- and post-processors.
17
 *
18
 * @see \Hyde\Framework\Testing\Feature\MarkdownServiceTest
19
 */
20
class MarkdownService
21
{
22
    use SetsUpMarkdownConverter;
23
24
    public string $markdown;
25
    public ?string $sourceModel = null;
26
27
    protected array $config = [];
28
    protected array $extensions = [];
29
    protected MarkdownConverter $converter;
30
31
    protected string $html;
32
    protected array $features = [];
33
34
    protected array $preprocessors = [];
35
    protected array $postprocessors = [];
36
37
    public function __construct(string $markdown, ?string $sourceModel = null)
38
    {
39
        $this->sourceModel = $sourceModel;
40
        $this->markdown = $markdown;
41
    }
42
43
    public function parse(): string
44
    {
45
        $this->setupConverter();
46
47
        $this->runPreProcessing();
48
49
        $this->html = $this->converter->convert($this->markdown);
50
51
        $this->runPostProcessing();
52
53
        return $this->html;
54
    }
55
56
    protected function setupConverter(): void
57
    {
58
        $this->enableDynamicExtensions();
59
60
        $this->enableConfigDefinedExtensions();
61
62
        $this->mergeMarkdownConfiguration();
63
64
        $this->converter = new MarkdownConverter($this->config);
65
66
        foreach ($this->extensions as $extension) {
67
            $this->initializeExtension($extension);
68
        }
69
70
        $this->registerPreProcessors();
71
        $this->registerPostProcessors();
72
    }
73
74
    public function addExtension(string $extensionClassName): void
75
    {
76
        if (! in_array($extensionClassName, $this->extensions)) {
77
            $this->extensions[] = $extensionClassName;
78
        }
79
    }
80
81
    protected function runPreProcessing(): void
82
    {
83
        /** @var PreProcessor $processor */
84
        foreach ($this->preprocessors as $processor) {
85
            $this->markdown = $processor::preprocess($this->markdown);
86
        }
87
    }
88
89
    protected function runPostProcessing(): void
90
    {
91
        if ($this->determineIfTorchlightAttributionShouldBeInjected()) {
92
            $this->html .= $this->injectTorchlightAttribution();
93
        }
94
95
        /** @var PostProcessor $processor */
96
        foreach ($this->postprocessors as $processor) {
97
            $this->html = $processor::postprocess($this->html);
98
        }
99
100
        // Remove any Hyde annotations (everything between `// HYDE!` and `HYDE! //`) (must be done last)
101
        $this->html = preg_replace('/ \/\/ HYDE!.*HYDE! \/\//s', '', $this->html);
102
    }
103
104
    public function getExtensions(): array
105
    {
106
        return $this->extensions;
107
    }
108
109
    public function removeFeature(string $feature): static
110
    {
111
        if (in_array($feature, $this->features)) {
112
            $this->features = array_diff($this->features, [$feature]);
113
        }
114
115
        return $this;
116
    }
117
118
    public function addFeature(string $feature): static
119
    {
120
        if (! in_array($feature, $this->features)) {
121
            $this->features[] = $feature;
122
        }
123
124
        return $this;
125
    }
126
127
    public function withPermalinks(): static
128
    {
129
        $this->addFeature('permalinks');
130
131
        return $this;
132
    }
133
134
    public function isDocumentationPage(): bool
135
    {
136
        return isset($this->sourceModel) && $this->sourceModel === DocumentationPage::class;
137
    }
138
139
    public function withTableOfContents(): static
140
    {
141
        $this->addFeature('table-of-contents');
142
143
        return $this;
144
    }
145
146
    public function canEnableTorchlight(): bool
147
    {
148
        return $this->hasFeature('torchlight') ||
149
            Features::hasTorchlight();
150
    }
151
152
    public function canEnablePermalinks(): bool
153
    {
154
        if ($this->hasFeature('permalinks')) {
155
            return true;
156
        }
157
158
        if ($this->isDocumentationPage() && DocumentationPage::hasTableOfContents()) {
159
            return true;
160
        }
161
162
        return false;
163
    }
164
165
    public function hasFeature(string $feature): bool
166
    {
167
        return in_array($feature, $this->features);
168
    }
169
170
    protected function determineIfTorchlightAttributionShouldBeInjected(): bool
171
    {
172
        return ! $this->isDocumentationPage()
173
            && config('torchlight.attribution.enabled', true)
174
            && str_contains($this->html, 'Syntax highlighted by torchlight.dev');
175
    }
176
177
    protected function injectTorchlightAttribution(): string
178
    {
179
        return '<br>'.$this->converter->convert(config(
180
            'torchlight.attribution.markdown',
181
            'Syntax highlighted by torchlight.dev'
182
        ));
183
    }
184
185
    protected function configurePermalinksExtension(): void
186
    {
187
        $this->addExtension(HeadingPermalinkExtension::class);
188
189
        $this->config = array_merge([
190
            'heading_permalink' => [
191
                'id_prefix' => '',
192
                'fragment_prefix' => '',
193
                'symbol' => '#',
194
                'insert' => 'after',
195
                'min_heading_level' => 2,
196
            ],
197
        ], $this->config);
198
    }
199
200
    protected function enableAllHtmlElements(): void
201
    {
202
        $this->addExtension(DisallowedRawHtmlExtension::class);
203
204
        $this->config = array_merge([
205
            'disallowed_raw_html' => [
206
                'disallowed_tags' => [],
207
            ],
208
        ], $this->config);
209
    }
210
}
211