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 ); 243*04823ca8SNico $h1Section = OutlineSection::createFromEnterHeadingCall($outline, $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); 290034808a5SNico foreach ($indexOutline->getRootOutlineSection()->getChildren() as $childOuterSection) { 291034808a5SNico $childOuterSection->updatePageLinkToInternal($indexPath); 292034808a5SNico } 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