1<?php
2
3namespace ComboStrap;
4
5
6class MarkupRenderer
7{
8
9    /**
10     * Setting the mime to instructions will just not do any render processing.
11     * Just parse tree/instructions processing if needed
12     */
13    public const INSTRUCTION_EXTENSION = "i";
14
15    const CANONICAL = "markup:renderer";
16    const XHTML_RENDERER = "xhtml";
17    const DEFAULT_RENDERER = self::XHTML_RENDERER;
18    const METADATA_EXTENSION = "meta";
19    /**
20     * @var string source of the renderer is a markup (and not instructions)
21     */
22    private string $markupSource;
23    /**
24     * @var array source of the rendere is instructions
25     */
26    private array $instructionsSource;
27
28    private Mime $requestedMime;
29
30    /**
31     * @var mixed
32     */
33    private $cacheAfterRendering = true;
34    private string $renderer;
35
36    /**
37     * @var WikiPath the context path
38     * May be null (ie markup rendering without any context such as webcode)
39     */
40    private WikiPath $requestedContextPath;
41
42    /**
43     * @var ?Path the path from where the instructions/markup where created
44     * This is mandatory to add cache dependency informations
45     * and set the path that is executing
46     */
47    private ?Path $executingPath;
48
49
50    /**
51     * @param string $markup
52     * @param Path|null $executingPath - the source of the markup - may be null (case of webcode)
53     * @param WikiPath|null $contextPath - the requested markup path - may be null (case of webcode)
54     * @return MarkupRenderer
55     */
56    public static function createFromMarkup(string $markup, ?Path $executingPath, ?WikiPath $contextPath): MarkupRenderer
57    {
58        $markupRenderer = (new MarkupRenderer())
59            ->setMarkup($markup);
60        if ($executingPath != null) {
61            $markupRenderer->setRequestedExecutingPath($executingPath);
62        }
63        if ($contextPath != null) {
64            $markupRenderer->setRequestedContextPath($contextPath);
65        }
66        return $markupRenderer;
67
68    }
69
70    private function setMarkup(string $markup): MarkupRenderer
71    {
72        $this->markupSource = $markup;
73        return $this;
74    }
75
76    public static function createFromMarkupInstructions($instructions, FetcherMarkup $fetcherMarkup): MarkupRenderer
77    {
78        return (new MarkupRenderer())
79            ->setInstructions($instructions)
80            ->setRequestedContextPath($fetcherMarkup->getRequestedContextPath())
81            ->setRequestedExecutingPath($fetcherMarkup->getExecutingPathOrNull());
82    }
83
84    public static function createFromInstructions($instructions): MarkupRenderer
85    {
86        return (new MarkupRenderer())->setInstructions($instructions);
87    }
88
89
90    public function setRequestedMimeToInstruction(): MarkupRenderer
91    {
92        try {
93            $this->setRequestedMime(Mime::createFromExtension(self::INSTRUCTION_EXTENSION));
94        } catch (ExceptionNotFound $e) {
95            throw new ExceptionRuntime("Internal error: the mime is internal and should be good");
96        }
97        return $this;
98
99    }
100
101    public function setRequestedMime(Mime $requestedMime): MarkupRenderer
102    {
103        $this->requestedMime = $requestedMime;
104        return $this;
105    }
106
107    public function getOutput()
108    {
109
110        $extension = $this->requestedMime->getExtension();
111        switch ($extension) {
112            case self::INSTRUCTION_EXTENSION:
113                /**
114                 * Get the instructions adapted from {@link p_cached_instructions()}
115                 *
116                 * Note that this code may not run at first rendering
117                 *
118                 * Why ?
119                 * Because dokuwiki asks first page information
120                 * via the {@link pageinfo()} method.
121                 * This function then render the metadata (ie {@link p_render_metadata()} and therefore will trigger
122                 * the rendering with this function
123                 * ```p_cached_instructions(wikiFN($id),false,$id)```
124                 *
125                 * The best way to manipulate the instructions is not before but after
126                 * the parsing. See {@link \action_plugin_combo_instructionspostprocessing}
127                 *
128                 */
129                return p_get_instructions($this->markupSource);
130
131            default:
132                /**
133                 * The code below is adapted from {@link p_cached_output()}
134                 * $ret = p_cached_output($file, 'xhtml', $pageid);
135                 */
136                if (!isset($this->instructionsSource)) {
137                    $executingPath = null;
138                    if (isset($this->executingPath)) {
139                        $executingPath = $this->executingPath;
140                    }
141
142                    $contextPath = null;
143                    if (isset($this->requestedContextPath)) {
144                        $contextPath = $this->requestedContextPath;
145                    }
146
147                    $this->instructionsSource = MarkupRenderer::createFromMarkup($this->markupSource, $executingPath, $contextPath)
148                        ->setRequestedMimeToInstruction()
149                        ->getOutput();
150                }
151
152                /**
153                 * Render
154                 */
155                $result = p_render($this->getRendererNameOrDefault(), $this->instructionsSource, $info);
156                $this->cacheAfterRendering = $info['cache'] !== null ? $info['cache'] : false;
157                return $result;
158
159        }
160
161
162    }
163
164    private function setInstructions(array $instructions): MarkupRenderer
165    {
166        $this->instructionsSource = $instructions;
167        return $this;
168    }
169
170
171    function getRendererNameOrDefault(): string
172    {
173        if (isset($this->renderer)) {
174            return $this->renderer;
175        }
176        /**
177         * Note: This value is passed to {@link p_get_renderer} to get the renderer class
178         */
179        return self::DEFAULT_RENDERER;
180    }
181
182    public function setRendererName(string $rendererName): MarkupRenderer
183    {
184        $this->renderer = $rendererName;
185        return $this;
186    }
187
188    public function getCacheAfterRendering()
189    {
190        return $this->cacheAfterRendering;
191    }
192
193    /**
194     * The page context in which this markup was requested
195     * @param WikiPath $path
196     * @return $this
197     */
198    public function setRequestedContextPath(WikiPath $path): MarkupRenderer
199    {
200        $this->requestedContextPath = $path;
201        return $this;
202    }
203
204    public function setRequestedMimeToXhtml(): MarkupRenderer
205    {
206        try {
207            return $this->setRequestedMime(Mime::createFromExtension("xhtml"));
208        } catch (ExceptionNotFound $e) {
209            throw new ExceptionRuntime("Internal error", 0, $e);
210        }
211    }
212
213    /**
214     * @throws ExceptionNotFound
215     */
216    private function getRequestedContextPath(): WikiPath
217    {
218
219        if (!isset($this->requestedContextPath)) {
220            throw new ExceptionNotFound("No requested context path");
221        }
222        return $this->requestedContextPath;
223
224    }
225
226    public function setRequestedExecutingPath(?Path $executingPath): MarkupRenderer
227    {
228        $this->executingPath = $executingPath;
229        return $this;
230    }
231
232
233}
234