1<?php
2
3namespace ComboStrap;
4
5
6use ComboStrap\Meta\Field\PageTemplateName;
7use ComboStrap\Web\Url;
8
9/**
10 * Bundle page from the same namespace
11 * with {@link FetcherPageBundler::getBundledOutline() corrected outline}
12 *
13 * From a wiki app, just add: `?do=combo_pagebundler`
14 *
15 */
16class FetcherPageBundler extends IFetcherAbs implements IFetcherString
17{
18
19    use FetcherTraitWikiPath;
20
21    const CANONICAL = self::NAME;
22    const NAME = "pagebundler";
23    private Outline $bundledOutline;
24
25    public static function createPageBundler(): FetcherPageBundler
26    {
27        return new FetcherPageBundler();
28    }
29
30    public function buildFromUrl(Url $url): FetcherPageBundler
31    {
32        /**
33         * Just to return the good type
34         */
35        parent::buildFromUrl($url);
36        return $this;
37    }
38
39    /**
40     * @throws ExceptionBadArgument
41     * @throws ExceptionBadSyntax
42     * @throws ExceptionNotExists
43     * @throws ExceptionNotFound
44     */
45    public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherPageBundler
46    {
47        parent::buildFromTagAttributes($tagAttributes);
48        $this->buildOriginalPathFromTagAttributes($tagAttributes);
49        return $this;
50    }
51
52
53    function getBuster(): string
54    {
55        return "";
56    }
57
58    /**
59     * @return Mime
60     */
61    public function getMime(): Mime
62    {
63        return Mime::getHtml();
64    }
65
66    public function getFetcherName(): string
67    {
68        return self::NAME;
69    }
70
71    public function getFetchString(): string
72    {
73
74        $outline = $this->getBundledOutline();
75        $instructionsCalls = $outline->toHtmlSectionOutlineCalls();
76        $mainContent = MarkupRenderer::createFromInstructions($instructionsCalls)
77            ->setRequestedExecutingPath($this->getStartPath())
78            ->setRequestedContextPath($this->getRequestedContextPath())
79            ->setRequestedMime($this->getMime())
80            ->getOutput();
81
82
83        $startMarkup = $this->getStartPath();
84        $title = PageTitle::createForMarkup($startMarkup)->getValueOrDefault();
85        $lang = Lang::createForMarkup($startMarkup);
86        try {
87            $startMarkupWikiPath = WikiPath::createFromPathObject($startMarkup->getPathObject());
88        } catch (ExceptionBadArgument $e) {
89            /**
90             * should not happen as this class accepts only wiki path as {@link FetcherPageBundler::setContextPath() context path}
91             */
92            throw new ExceptionRuntimeInternal("We were unable to get the start markup wiki path. Error:{$e->getMessage()}", self::CANONICAL);
93        }
94
95        $layoutName = PageTemplateName::BLANK_TEMPLATE_VALUE;
96        try {
97            $toc = Toc::createEmpty()
98                ->setValue($this->getBundledOutline()->toTocDokuwikiFormat());
99        } catch (ExceptionBadArgument $e) {
100            // this is an array
101            throw new ExceptionRuntimeInternal("The toc could not be created. Error:{$e->getMessage()}", self::CANONICAL, 1, $e);
102        }
103        try {
104            return TemplateForWebPage::create()
105                ->setRequestedTemplateName($layoutName)
106                ->setRequestedContextPath($startMarkupWikiPath)
107                ->setRequestedTitle($title)
108                ->setRequestedLang($lang)
109                ->setToc($toc)
110                ->setIsSocial(false)
111                ->setRequestedEnableTaskRunner(false)
112                ->setMainContent($mainContent)
113                ->render();
114        } catch (ExceptionBadSyntax|ExceptionNotFound|ExceptionBadArgument $e) {
115            // layout should be good
116            throw new ExceptionRuntimeInternal("The $layoutName template returns an error", self::CANONICAL, 1, $e);
117        }
118
119
120    }
121
122    public function getBundledOutline(): Outline
123    {
124
125        if (isset($this->bundledOutline)) {
126            return $this->bundledOutline;
127        }
128
129        $startPath = $this->getStartPath();
130        if (FileSystems::exists($startPath)) {
131            $indexOutline = $this->addFirstSectionIfMissing($startPath->getOutline());
132        } else {
133            $title = PageTitle::createForMarkup($startPath)->getValueOrDefault();
134            $content = <<<EOF
135====== $title ======
136EOF;
137            $indexOutline = Outline::createFromMarkup($content, $this->getStartPath(), $this->getRequestedContextPath());
138        }
139
140        $childrenPages = MarkupFileSystem::getOrCreate()->getChildren($startPath, FileSystems::LEAF);
141        foreach ($childrenPages as $child) {
142            $outer = $this->addFirstSectionIfMissing($child->getOutline());
143            Outline::merge($indexOutline, $outer);
144        }
145        $this->bundledOutline = $indexOutline;
146
147        return $this->bundledOutline;
148
149    }
150
151    /**
152     * The path from where the bundle should start
153     * If this is not an index markup, the index markup will be chosen {@link FetcherPageBundler::getStartPath()}
154     *
155     * @throws ExceptionBadArgument - if the path is not a {@link WikiPath web path}
156     */
157    public function setContextPath(Path $requestedPath): FetcherPageBundler
158    {
159        $this->setSourcePath(WikiPath::createFromPathObject($requestedPath));
160        return $this;
161    }
162
163    private function getRequestedContextPath(): WikiPath
164    {
165        return $this->getSourcePath();
166    }
167
168
169    /**
170     *
171     * @return MarkupPath The index path or the request path is none
172     *
173     */
174    private function getStartPath(): MarkupPath
175    {
176        $requestedPath = MarkupPath::createPageFromPathObject($this->getRequestedContextPath());
177        if ($requestedPath->isIndexPage()) {
178            return $requestedPath;
179        }
180        try {
181            /**
182             * Parent is an index path in the {@link MarkupFileSystem}
183             */
184            return $requestedPath->getParent();
185        } catch (ExceptionNotFound $e) {
186            // home markup case (should not happen - home page is a index page)
187            return $requestedPath;
188        }
189
190    }
191
192    /**
193     * If a page does not have any h1
194     * (Case of index page for instance)
195     *
196     * If this is the case, the outline is broken.
197     * @param Outline $outline
198     * @return Outline
199     */
200    private function addFirstSectionIfMissing(Outline $outline): Outline
201    {
202        $rootOutlineSection = $outline->getRootOutlineSection();
203        $addFirstSection = false;
204        try {
205            $firstChild = $rootOutlineSection->getFirstChild();
206            if ($firstChild->getLevel() >= 2) {
207                $addFirstSection = true;
208            }
209        } catch (ExceptionNotFound $e) {
210            $addFirstSection = true;
211        }
212        if ($addFirstSection) {
213            $enterHeading = Call::createComboCall(
214                HeadingTag::HEADING_TAG,
215                DOKU_LEXER_ENTER,
216                array(HeadingTag::LEVEL => 1),
217                HeadingTag::TYPE_OUTLINE,
218                null,
219                null,
220                null,
221                \syntax_plugin_combo_xmlblocktag::TAG
222            );
223            $title = PageTitle::createForMarkup($outline->getMarkupPath())->getValueOrDefault();
224            $unmatchedHeading = Call::createComboCall(
225                HeadingTag::HEADING_TAG,
226                DOKU_LEXER_UNMATCHED,
227                [],
228                null,
229                $title,
230                $title,
231                null,
232                \syntax_plugin_combo_xmlblocktag::TAG
233            );
234            $exitHeading = Call::createComboCall(
235                HeadingTag::HEADING_TAG,
236                DOKU_LEXER_EXIT,
237                array(HeadingTag::LEVEL => 1),
238                null,
239                null,
240                null,
241                null,
242                \syntax_plugin_combo_xmlblocktag::TAG
243            );
244            $h1Section = OutlineSection::createFromEnterHeadingCall($enterHeading)
245                ->addHeaderCall($unmatchedHeading)
246                ->addHeaderCall($exitHeading);
247            $children = $rootOutlineSection->getChildren();
248            foreach ($children as $child) {
249                $child->detachBeforeAppend();
250                try {
251                    $h1Section->appendChild($child);
252                } catch (ExceptionBadState $e) {
253                    LogUtility::error("An error occurs when trying to move the h2 children below the recreated heading title ($title)", self::CANONICAL);
254                }
255            }
256            /**
257             * Without h1
258             * The content is in the root heading
259             */
260            foreach ($rootOutlineSection->getContentCalls() as $rootHeadingCall) {
261                $h1Section->addContentCall($rootHeadingCall);
262            }
263            $rootOutlineSection->deleteContentCalls();
264            try {
265                $rootOutlineSection->appendChild($h1Section);
266            } catch (ExceptionBadState $e) {
267                LogUtility::error("An error occurs when trying to add the recreated title heading ($title) to the root", self::CANONICAL);
268            }
269        }
270        return $outline;
271    }
272
273    public function getLabel(): string
274    {
275        return self::CANONICAL;
276    }
277}
278