xref: /template/strap/ComboStrap/FetcherPageBundler.php (revision 034808a595c3778878019a7d2a6c5cbe339bbabb)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeaunamespace ComboStrap;
404fd306cSNickeau
504fd306cSNickeau
604fd306cSNickeauuse ComboStrap\Meta\Field\PageTemplateName;
704fd306cSNickeauuse ComboStrap\Web\Url;
804fd306cSNickeau
904fd306cSNickeau/**
1004fd306cSNickeau * Bundle page from the same namespace
1104fd306cSNickeau * with {@link FetcherPageBundler::getBundledOutline() corrected outline}
1204fd306cSNickeau *
1304fd306cSNickeau * From a wiki app, just add: `?do=combo_pagebundler`
1404fd306cSNickeau *
1504fd306cSNickeau */
1604fd306cSNickeauclass FetcherPageBundler extends IFetcherAbs implements IFetcherString
1704fd306cSNickeau{
1804fd306cSNickeau
1904fd306cSNickeau    use FetcherTraitWikiPath;
2004fd306cSNickeau
2104fd306cSNickeau    const CANONICAL = self::NAME;
2204fd306cSNickeau    const NAME = "pagebundler";
237dbcdecdSNico    private ?Outline $bundledOutline = null;
24e453e515SNico    /**
25e453e515SNico     * @var int - the maximum number of pages to bundle
26e453e515SNico     * Security to not get DDOS by a Search engine
27e453e515SNico     */
28e453e515SNico    private int $maxPages = 5;
29e453e515SNico    /**
30e453e515SNico     * @var int the number of pages processed (ie actually added to the outline)
31e453e515SNico     */
32e453e515SNico    private int $countPageProcessed = 0;
3304fd306cSNickeau
3404fd306cSNickeau    public static function createPageBundler(): FetcherPageBundler
3504fd306cSNickeau    {
3604fd306cSNickeau        return new FetcherPageBundler();
3704fd306cSNickeau    }
3804fd306cSNickeau
3904fd306cSNickeau    public function buildFromUrl(Url $url): FetcherPageBundler
4004fd306cSNickeau    {
4104fd306cSNickeau        /**
4204fd306cSNickeau         * Just to return the good type
4304fd306cSNickeau         */
4404fd306cSNickeau        parent::buildFromUrl($url);
4504fd306cSNickeau        return $this;
4604fd306cSNickeau    }
4704fd306cSNickeau
4804fd306cSNickeau    /**
4904fd306cSNickeau     * @throws ExceptionBadArgument
5004fd306cSNickeau     * @throws ExceptionBadSyntax
5104fd306cSNickeau     * @throws ExceptionNotExists
5204fd306cSNickeau     * @throws ExceptionNotFound
5304fd306cSNickeau     */
5404fd306cSNickeau    public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherPageBundler
5504fd306cSNickeau    {
5604fd306cSNickeau        parent::buildFromTagAttributes($tagAttributes);
5704fd306cSNickeau        $this->buildOriginalPathFromTagAttributes($tagAttributes);
5804fd306cSNickeau        return $this;
5904fd306cSNickeau    }
6004fd306cSNickeau
6104fd306cSNickeau
6204fd306cSNickeau    function getBuster(): string
6304fd306cSNickeau    {
6404fd306cSNickeau        return "";
6504fd306cSNickeau    }
6604fd306cSNickeau
6704fd306cSNickeau    /**
6804fd306cSNickeau     * @return Mime
6904fd306cSNickeau     */
7004fd306cSNickeau    public function getMime(): Mime
7104fd306cSNickeau    {
7204fd306cSNickeau        return Mime::getHtml();
7304fd306cSNickeau    }
7404fd306cSNickeau
7504fd306cSNickeau    public function getFetcherName(): string
7604fd306cSNickeau    {
7704fd306cSNickeau        return self::NAME;
7804fd306cSNickeau    }
7904fd306cSNickeau
8004fd306cSNickeau    public function getFetchString(): string
8104fd306cSNickeau    {
8204fd306cSNickeau
8304fd306cSNickeau        $outline = $this->getBundledOutline();
8404fd306cSNickeau        $instructionsCalls = $outline->toHtmlSectionOutlineCalls();
8504fd306cSNickeau        $mainContent = MarkupRenderer::createFromInstructions($instructionsCalls)
8604fd306cSNickeau            ->setRequestedExecutingPath($this->getStartPath())
8704fd306cSNickeau            ->setRequestedContextPath($this->getRequestedContextPath())
8804fd306cSNickeau            ->setRequestedMime($this->getMime())
8904fd306cSNickeau            ->getOutput();
9004fd306cSNickeau
9104fd306cSNickeau
9204fd306cSNickeau        $startMarkup = $this->getStartPath();
9304fd306cSNickeau        $title = PageTitle::createForMarkup($startMarkup)->getValueOrDefault();
9404fd306cSNickeau        $lang = Lang::createForMarkup($startMarkup);
9504fd306cSNickeau        try {
9604fd306cSNickeau            $startMarkupWikiPath = WikiPath::createFromPathObject($startMarkup->getPathObject());
9704fd306cSNickeau        } catch (ExceptionBadArgument $e) {
9804fd306cSNickeau            /**
9904fd306cSNickeau             * should not happen as this class accepts only wiki path as {@link FetcherPageBundler::setContextPath() context path}
10004fd306cSNickeau             */
10104fd306cSNickeau            throw new ExceptionRuntimeInternal("We were unable to get the start markup wiki path. Error:{$e->getMessage()}", self::CANONICAL);
10204fd306cSNickeau        }
10304fd306cSNickeau
10404fd306cSNickeau        $layoutName = PageTemplateName::BLANK_TEMPLATE_VALUE;
10504fd306cSNickeau        try {
10604fd306cSNickeau            $toc = Toc::createEmpty()
10704fd306cSNickeau                ->setValue($this->getBundledOutline()->toTocDokuwikiFormat());
10804fd306cSNickeau        } catch (ExceptionBadArgument $e) {
10904fd306cSNickeau            // this is an array
11004fd306cSNickeau            throw new ExceptionRuntimeInternal("The toc could not be created. Error:{$e->getMessage()}", self::CANONICAL, 1, $e);
11104fd306cSNickeau        }
11204fd306cSNickeau        try {
11304fd306cSNickeau            return TemplateForWebPage::create()
11404fd306cSNickeau                ->setRequestedTemplateName($layoutName)
11504fd306cSNickeau                ->setRequestedContextPath($startMarkupWikiPath)
11604fd306cSNickeau                ->setRequestedTitle($title)
11704fd306cSNickeau                ->setRequestedLang($lang)
11804fd306cSNickeau                ->setToc($toc)
11904fd306cSNickeau                ->setIsSocial(false)
12004fd306cSNickeau                ->setRequestedEnableTaskRunner(false)
12104fd306cSNickeau                ->setMainContent($mainContent)
12204fd306cSNickeau                ->render();
12304fd306cSNickeau        } catch (ExceptionBadSyntax|ExceptionNotFound|ExceptionBadArgument $e) {
12404fd306cSNickeau            // layout should be good
12504fd306cSNickeau            throw new ExceptionRuntimeInternal("The $layoutName template returns an error", self::CANONICAL, 1, $e);
12604fd306cSNickeau        }
12704fd306cSNickeau
12804fd306cSNickeau
12904fd306cSNickeau    }
13004fd306cSNickeau
13104fd306cSNickeau    public function getBundledOutline(): Outline
13204fd306cSNickeau    {
13304fd306cSNickeau
13404fd306cSNickeau        if (isset($this->bundledOutline)) {
13504fd306cSNickeau            return $this->bundledOutline;
13604fd306cSNickeau        }
13704fd306cSNickeau
138e453e515SNico        if (!Identity::isAnonymous()) {
139e453e515SNico            $this->maxPages = 99999;
140e453e515SNico            set_time_limit(5 * 60);
141e453e515SNico        }
14204fd306cSNickeau        $startPath = $this->getStartPath();
1437dbcdecdSNico        $actualLevel = 0;
1447dbcdecdSNico        $this->buildOutlineRecursive($startPath, $actualLevel);
14504fd306cSNickeau
14604fd306cSNickeau        return $this->bundledOutline;
14704fd306cSNickeau
14804fd306cSNickeau    }
14904fd306cSNickeau
15004fd306cSNickeau    /**
15104fd306cSNickeau     * The path from where the bundle should start
15204fd306cSNickeau     * If this is not an index markup, the index markup will be chosen {@link FetcherPageBundler::getStartPath()}
15304fd306cSNickeau     *
15404fd306cSNickeau     * @throws ExceptionBadArgument - if the path is not a {@link WikiPath web path}
15504fd306cSNickeau     */
15604fd306cSNickeau    public function setContextPath(Path $requestedPath): FetcherPageBundler
15704fd306cSNickeau    {
15804fd306cSNickeau        $this->setSourcePath(WikiPath::createFromPathObject($requestedPath));
15904fd306cSNickeau        return $this;
16004fd306cSNickeau    }
16104fd306cSNickeau
16204fd306cSNickeau    private function getRequestedContextPath(): WikiPath
16304fd306cSNickeau    {
16404fd306cSNickeau        return $this->getSourcePath();
16504fd306cSNickeau    }
16604fd306cSNickeau
16704fd306cSNickeau
16804fd306cSNickeau    /**
16904fd306cSNickeau     *
17004fd306cSNickeau     * @return MarkupPath The index path or the request path is none
17104fd306cSNickeau     *
17204fd306cSNickeau     */
17304fd306cSNickeau    private function getStartPath(): MarkupPath
17404fd306cSNickeau    {
17504fd306cSNickeau        $requestedPath = MarkupPath::createPageFromPathObject($this->getRequestedContextPath());
17604fd306cSNickeau        if ($requestedPath->isIndexPage()) {
17704fd306cSNickeau            return $requestedPath;
17804fd306cSNickeau        }
17904fd306cSNickeau        try {
18004fd306cSNickeau            /**
18104fd306cSNickeau             * Parent is an index path in the {@link MarkupFileSystem}
18204fd306cSNickeau             */
18304fd306cSNickeau            return $requestedPath->getParent();
18404fd306cSNickeau        } catch (ExceptionNotFound $e) {
18504fd306cSNickeau            // home markup case (should not happen - home page is a index page)
18604fd306cSNickeau            return $requestedPath;
18704fd306cSNickeau        }
18804fd306cSNickeau
18904fd306cSNickeau    }
19004fd306cSNickeau
19104fd306cSNickeau    /**
19204fd306cSNickeau     * If a page does not have any h1
19304fd306cSNickeau     * (Case of index page for instance)
19404fd306cSNickeau     *
19504fd306cSNickeau     * If this is the case, the outline is broken.
19604fd306cSNickeau     * @param Outline $outline
19704fd306cSNickeau     * @return Outline
19804fd306cSNickeau     */
19904fd306cSNickeau    private function addFirstSectionIfMissing(Outline $outline): Outline
20004fd306cSNickeau    {
20104fd306cSNickeau        $rootOutlineSection = $outline->getRootOutlineSection();
20204fd306cSNickeau        $addFirstSection = false;
20304fd306cSNickeau        try {
20404fd306cSNickeau            $firstChild = $rootOutlineSection->getFirstChild();
20504fd306cSNickeau            if ($firstChild->getLevel() >= 2) {
20604fd306cSNickeau                $addFirstSection = true;
20704fd306cSNickeau            }
20804fd306cSNickeau        } catch (ExceptionNotFound $e) {
20904fd306cSNickeau            $addFirstSection = true;
21004fd306cSNickeau        }
21104fd306cSNickeau        if ($addFirstSection) {
21204fd306cSNickeau            $enterHeading = Call::createComboCall(
21304fd306cSNickeau                HeadingTag::HEADING_TAG,
21404fd306cSNickeau                DOKU_LEXER_ENTER,
21504fd306cSNickeau                array(HeadingTag::LEVEL => 1),
21604fd306cSNickeau                HeadingTag::TYPE_OUTLINE,
21704fd306cSNickeau                null,
21804fd306cSNickeau                null,
21904fd306cSNickeau                null,
22004fd306cSNickeau                \syntax_plugin_combo_xmlblocktag::TAG
22104fd306cSNickeau            );
22204fd306cSNickeau            $title = PageTitle::createForMarkup($outline->getMarkupPath())->getValueOrDefault();
22304fd306cSNickeau            $unmatchedHeading = Call::createComboCall(
22404fd306cSNickeau                HeadingTag::HEADING_TAG,
22504fd306cSNickeau                DOKU_LEXER_UNMATCHED,
22604fd306cSNickeau                [],
22704fd306cSNickeau                null,
22804fd306cSNickeau                $title,
22904fd306cSNickeau                $title,
23004fd306cSNickeau                null,
23104fd306cSNickeau                \syntax_plugin_combo_xmlblocktag::TAG
23204fd306cSNickeau            );
23304fd306cSNickeau            $exitHeading = Call::createComboCall(
23404fd306cSNickeau                HeadingTag::HEADING_TAG,
23504fd306cSNickeau                DOKU_LEXER_EXIT,
23604fd306cSNickeau                array(HeadingTag::LEVEL => 1),
23704fd306cSNickeau                null,
23804fd306cSNickeau                null,
23904fd306cSNickeau                null,
24004fd306cSNickeau                null,
24104fd306cSNickeau                \syntax_plugin_combo_xmlblocktag::TAG
24204fd306cSNickeau            );
24304fd306cSNickeau            $h1Section = OutlineSection::createFromEnterHeadingCall($enterHeading)
24404fd306cSNickeau                ->addHeaderCall($unmatchedHeading)
24504fd306cSNickeau                ->addHeaderCall($exitHeading);
24604fd306cSNickeau            $children = $rootOutlineSection->getChildren();
24704fd306cSNickeau            foreach ($children as $child) {
24804fd306cSNickeau                $child->detachBeforeAppend();
24904fd306cSNickeau                try {
25004fd306cSNickeau                    $h1Section->appendChild($child);
25104fd306cSNickeau                } catch (ExceptionBadState $e) {
25204fd306cSNickeau                    LogUtility::error("An error occurs when trying to move the h2 children below the recreated heading title ($title)", self::CANONICAL);
25304fd306cSNickeau                }
25404fd306cSNickeau            }
25504fd306cSNickeau            /**
25604fd306cSNickeau             * Without h1
25704fd306cSNickeau             * The content is in the root heading
25804fd306cSNickeau             */
25904fd306cSNickeau            foreach ($rootOutlineSection->getContentCalls() as $rootHeadingCall) {
26004fd306cSNickeau                $h1Section->addContentCall($rootHeadingCall);
26104fd306cSNickeau            }
26204fd306cSNickeau            $rootOutlineSection->deleteContentCalls();
26304fd306cSNickeau            try {
26404fd306cSNickeau                $rootOutlineSection->appendChild($h1Section);
26504fd306cSNickeau            } catch (ExceptionBadState $e) {
26604fd306cSNickeau                LogUtility::error("An error occurs when trying to add the recreated title heading ($title) to the root", self::CANONICAL);
26704fd306cSNickeau            }
26804fd306cSNickeau        }
26904fd306cSNickeau        return $outline;
27004fd306cSNickeau    }
27104fd306cSNickeau
27204fd306cSNickeau    public function getLabel(): string
27304fd306cSNickeau    {
27404fd306cSNickeau        return self::CANONICAL;
27504fd306cSNickeau    }
2767dbcdecdSNico
2777dbcdecdSNico    private function buildOutlineRecursive(MarkupPath $indexPath, int $actualLevel)
2787dbcdecdSNico    {
2797dbcdecdSNico        /**
2807dbcdecdSNico         * Index Page
2817dbcdecdSNico         */
2827dbcdecdSNico        if (FileSystems::exists($indexPath)) {
2834fbd4ae2SNico            $outline = FetcherMarkup::confRoot()
2844fbd4ae2SNico                ->setRequestedExecutingPath($indexPath)
2854fbd4ae2SNico                ->setRequestedContextPath($indexPath->toWikiPath())
2864fbd4ae2SNico                ->setRequestedMimeToInstructions()
2874fbd4ae2SNico                ->build()
2884fbd4ae2SNico                ->getOutline();
2894fbd4ae2SNico            $indexOutline = $this->addFirstSectionIfMissing($outline);
290*034808a5SNico            foreach ($indexOutline->getRootOutlineSection()->getChildren() as $childOuterSection) {
291*034808a5SNico                $childOuterSection->updatePageLinkToInternal($indexPath);
292*034808a5SNico            }
2937dbcdecdSNico        } else {
2947dbcdecdSNico            $title = PageTitle::createForMarkup($indexPath)->getValueOrDefault();
2957dbcdecdSNico            $content = <<<EOF
2967dbcdecdSNico====== $title ======
2977dbcdecdSNicoEOF;
298dff3a8c8SNico            $indexOutline = Outline::createFromMarkup($content, $indexPath, $this->getRequestedContextPath());
299dff3a8c8SNico            $indexOutline = $this->addFirstSectionIfMissing($indexOutline);
3007dbcdecdSNico        }
3017dbcdecdSNico
302e453e515SNico        /**
303e453e515SNico         * Start of bundled outline or not
304e453e515SNico         */
305e453e515SNico        if ($this->bundledOutline === null) {
3067dbcdecdSNico            $this->bundledOutline = $indexOutline;
3077dbcdecdSNico        } else {
3087dbcdecdSNico            Outline::merge($this->bundledOutline, $indexOutline, $actualLevel);
3097dbcdecdSNico        }
310e453e515SNico        $this->countPageProcessed = +1;
311e453e515SNico        if ($this->countPageProcessed > $this->maxPages) {
312e453e515SNico            return;
313e453e515SNico        }
3147dbcdecdSNico
3157dbcdecdSNico        /**
3167dbcdecdSNico         * Children Pages (Same level)
3177dbcdecdSNico         */
3187dbcdecdSNico        $childrenPages = MarkupFileSystem::getOrCreate()->getChildren($indexPath, FileSystems::LEAF);
3197dbcdecdSNico        foreach ($childrenPages as $child) {
3207dbcdecdSNico            if ($child->isSlot()) {
3217dbcdecdSNico                continue;
3227dbcdecdSNico            }
3234fbd4ae2SNico            try {
3244fbd4ae2SNico                $outline = FetcherMarkup::confRoot()
3254fbd4ae2SNico                    ->setRequestedExecutingPath($child)
3264fbd4ae2SNico                    ->setRequestedContextPath($child->toWikiPath())
3274fbd4ae2SNico                    ->setRequestedMimeToInstructions()
3284fbd4ae2SNico                    ->build()
3294fbd4ae2SNico                    ->getOutline();
3304fbd4ae2SNico            } catch (ExceptionNotExists $e) {
3314fbd4ae2SNico                // as it's in a file system loop, the page should exist
3324fbd4ae2SNico                continue;
3334fbd4ae2SNico            }
3344fbd4ae2SNico            $outer = $this->addFirstSectionIfMissing($outline);
3357dbcdecdSNico            Outline::merge($this->bundledOutline, $outer, $actualLevel);
336e453e515SNico            $this->countPageProcessed = +1;
337e453e515SNico            if ($this->countPageProcessed > $this->maxPages) {
338e453e515SNico                return;
339e453e515SNico            }
3407dbcdecdSNico        }
3417dbcdecdSNico        $containerPages = MarkupFileSystem::getOrCreate()->getChildren($indexPath, FileSystems::CONTAINER);
3427dbcdecdSNico        $nextLevel = $actualLevel + 1;
3437dbcdecdSNico        foreach ($containerPages as $child) {
3447dbcdecdSNico            $this->buildOutlineRecursive($child, $nextLevel);
3457dbcdecdSNico        }
3467dbcdecdSNico
3477dbcdecdSNico    }
34804fd306cSNickeau}
349