1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeaunamespace ComboStrap; 4*04fd306cSNickeau 5*04fd306cSNickeauuse ComboStrap\Meta\Field\FeaturedRasterImage; 6*04fd306cSNickeauuse ComboStrap\Meta\Field\FeaturedSvgImage; 7*04fd306cSNickeauuse ComboStrap\Tag\AdTag; 8*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 9*04fd306cSNickeau 10*04fd306cSNickeauclass OutlineVisitor 11*04fd306cSNickeau{ 12*04fd306cSNickeau 13*04fd306cSNickeau 14*04fd306cSNickeau private Outline $outline; 15*04fd306cSNickeau private ?MarkupPath $markupPath; 16*04fd306cSNickeau private int $currentLineCountSinceLastAd; 17*04fd306cSNickeau /** 18*04fd306cSNickeau * @var int the order, number of sections 19*04fd306cSNickeau */ 20*04fd306cSNickeau private int $sectionNumbers; 21*04fd306cSNickeau /** 22*04fd306cSNickeau * @var int the number of ads inserted 23*04fd306cSNickeau */ 24*04fd306cSNickeau private int $adsCounter; 25*04fd306cSNickeau /** 26*04fd306cSNickeau * @var OutlineSection The last section to be printed 27*04fd306cSNickeau */ 28*04fd306cSNickeau private OutlineSection $lastSectionToBePrinted; 29*04fd306cSNickeau private bool $inArticleEnabled; 30*04fd306cSNickeau 31*04fd306cSNickeau public function __construct(Outline $outline) 32*04fd306cSNickeau { 33*04fd306cSNickeau $this->outline = $outline; 34*04fd306cSNickeau $this->markupPath = $outline->getMarkupPath(); 35*04fd306cSNickeau 36*04fd306cSNickeau $this->inArticleEnabled = ExecutionContext::getActualOrCreateFromEnv() 37*04fd306cSNickeau ->getConfig() 38*04fd306cSNickeau ->getBooleanValue(AdTag::CONF_IN_ARTICLE_ENABLED, AdTag::CONF_IN_ARTICLE_ENABLED_DEFAULT); 39*04fd306cSNickeau // Running variables that permits to balance the creation of Ads 40*04fd306cSNickeau $this->currentLineCountSinceLastAd = 0; 41*04fd306cSNickeau $this->sectionNumbers = 0; 42*04fd306cSNickeau $this->adsCounter = 0; 43*04fd306cSNickeau $this->lastSectionToBePrinted = $this->getLastSectionToBePrinted(); 44*04fd306cSNickeau 45*04fd306cSNickeau } 46*04fd306cSNickeau 47*04fd306cSNickeau public static function create(Outline $outline): OutlineVisitor 48*04fd306cSNickeau { 49*04fd306cSNickeau return new OutlineVisitor($outline); 50*04fd306cSNickeau } 51*04fd306cSNickeau 52*04fd306cSNickeau public function getCalls() 53*04fd306cSNickeau { 54*04fd306cSNickeau 55*04fd306cSNickeau $totalCalls = []; 56*04fd306cSNickeau $sectionSequenceId = 0; 57*04fd306cSNickeau 58*04fd306cSNickeau /** 59*04fd306cSNickeau * Header Metadata 60*04fd306cSNickeau * 61*04fd306cSNickeau * On template that have an header, the h1 and the featured image are 62*04fd306cSNickeau * captured and deleted by default to allow complex header layout 63*04fd306cSNickeau * 64*04fd306cSNickeau * Delete the parsed value (runtime works only on rendering) 65*04fd306cSNickeau * TODO: move that to the metadata rendering by adding attributes 66*04fd306cSNickeau * because if the user changes the template, the parsing will not work 67*04fd306cSNickeau * it would need to parse the document again 68*04fd306cSNickeau */ 69*04fd306cSNickeau $markupPath = $this->outline->getMarkupPath(); 70*04fd306cSNickeau if ($markupPath !== null) { 71*04fd306cSNickeau FeaturedRasterImage::createFromResourcePage($markupPath)->setParsedValue(); 72*04fd306cSNickeau FeaturedSvgImage::createFromResourcePage($markupPath)->setParsedValue(); 73*04fd306cSNickeau } 74*04fd306cSNickeau $captureHeaderMeta = $this->outline->getMetaHeaderCapture(); 75*04fd306cSNickeau 76*04fd306cSNickeau 77*04fd306cSNickeau /** 78*04fd306cSNickeau * Transform and collect the calls in Instructions calls 79*04fd306cSNickeau */ 80*04fd306cSNickeau $this->toHtmlSectionOutlineCallsRecurse($this->outline->getRootOutlineSection(), $totalCalls, $sectionSequenceId, $captureHeaderMeta); 81*04fd306cSNickeau 82*04fd306cSNickeau return array_map(function (Call $element) { 83*04fd306cSNickeau return $element->getInstructionCall(); 84*04fd306cSNickeau }, $totalCalls); 85*04fd306cSNickeau } 86*04fd306cSNickeau 87*04fd306cSNickeau /** 88*04fd306cSNickeau * The visitor, we don't use a visitor pattern for now 89*04fd306cSNickeau * @param OutlineSection $outlineSection 90*04fd306cSNickeau * @param array $totalComboCalls 91*04fd306cSNickeau * @param int $sectionSequenceId 92*04fd306cSNickeau * @param bool $captureHeaderMeta 93*04fd306cSNickeau * @return void 94*04fd306cSNickeau */ 95*04fd306cSNickeau private function toHtmlSectionOutlineCallsRecurse(OutlineSection $outlineSection, array &$totalComboCalls, int &$sectionSequenceId, bool $captureHeaderMeta): void 96*04fd306cSNickeau { 97*04fd306cSNickeau 98*04fd306cSNickeau $totalComboCalls[] = Call::createComboCall( 99*04fd306cSNickeau SectionTag::TAG, 100*04fd306cSNickeau DOKU_LEXER_ENTER, 101*04fd306cSNickeau array(HeadingTag::LEVEL => $outlineSection->getLevel()), 102*04fd306cSNickeau null, 103*04fd306cSNickeau null, 104*04fd306cSNickeau null, 105*04fd306cSNickeau null, 106*04fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 107*04fd306cSNickeau ); 108*04fd306cSNickeau 109*04fd306cSNickeau /** 110*04fd306cSNickeau * In Ads Content Slot Calculation 111*04fd306cSNickeau */ 112*04fd306cSNickeau $adCalls = []; 113*04fd306cSNickeau if($this->inArticleEnabled) { 114*04fd306cSNickeau $adCall = $this->getAdCall($outlineSection); 115*04fd306cSNickeau if ($adCall !== null) { 116*04fd306cSNickeau $adCalls = [$adCall]; 117*04fd306cSNickeau } 118*04fd306cSNickeau } 119*04fd306cSNickeau 120*04fd306cSNickeau $contentCalls = $outlineSection->getContentCalls(); 121*04fd306cSNickeau if ($outlineSection->hasChildren()) { 122*04fd306cSNickeau 123*04fd306cSNickeau 124*04fd306cSNickeau $actualChildren = $outlineSection->getChildren(); 125*04fd306cSNickeau 126*04fd306cSNickeau if ($captureHeaderMeta && $outlineSection->getLevel() === 0) { 127*04fd306cSNickeau // should be only one 1 128*04fd306cSNickeau if (count($actualChildren) === 1) { 129*04fd306cSNickeau $h1Section = $actualChildren[array_key_first($actualChildren)]; 130*04fd306cSNickeau if ($h1Section->getLevel() === 1) { 131*04fd306cSNickeau $h1ContentCalls = $h1Section->getContentCalls(); 132*04fd306cSNickeau /** 133*04fd306cSNickeau * Capture the image if any 134*04fd306cSNickeau */ 135*04fd306cSNickeau if ($this->markupPath !== null) { 136*04fd306cSNickeau foreach ($h1ContentCalls as $h1ContentCall) { 137*04fd306cSNickeau $tagName = $h1ContentCall->getTagName(); 138*04fd306cSNickeau switch ($tagName) { 139*04fd306cSNickeau case "p": 140*04fd306cSNickeau continue 2; 141*04fd306cSNickeau case "media": 142*04fd306cSNickeau $h1ContentCall->addAttribute(Display::DISPLAY, Display::DISPLAY_NONE_VALUE); 143*04fd306cSNickeau try { 144*04fd306cSNickeau $fetcher = MediaMarkup::createFromCallStackArray($h1ContentCall->getAttributes())->getFetcher(); 145*04fd306cSNickeau switch (get_class($fetcher)) { 146*04fd306cSNickeau case FetcherRaster::class: 147*04fd306cSNickeau $path = $fetcher->getSourcePath()->toAbsoluteId(); 148*04fd306cSNickeau FeaturedRasterImage::createFromResourcePage($this->markupPath)->setParsedValue($path); 149*04fd306cSNickeau break; 150*04fd306cSNickeau case FetcherSvg::class: 151*04fd306cSNickeau $path = $fetcher->getSourcePath()->toAbsoluteId(); 152*04fd306cSNickeau FeaturedSvgImage::createFromResourcePage($this->markupPath)->setParsedValue($path); 153*04fd306cSNickeau break; 154*04fd306cSNickeau } 155*04fd306cSNickeau } catch (\Exception $e) { 156*04fd306cSNickeau LogUtility::error("Error while capturing the feature images. Error: " . $e->getMessage(), Outline::CANONICAL, $e); 157*04fd306cSNickeau } 158*04fd306cSNickeau continue 2; 159*04fd306cSNickeau default: 160*04fd306cSNickeau // only the images found just after h1 161*04fd306cSNickeau break; 162*04fd306cSNickeau } 163*04fd306cSNickeau } 164*04fd306cSNickeau } 165*04fd306cSNickeau $contentCalls = array_merge($contentCalls, $h1ContentCalls); 166*04fd306cSNickeau $actualChildren = $h1Section->getChildren(); 167*04fd306cSNickeau } 168*04fd306cSNickeau } 169*04fd306cSNickeau } 170*04fd306cSNickeau 171*04fd306cSNickeau 172*04fd306cSNickeau /** 173*04fd306cSNickeau * If header has content, 174*04fd306cSNickeau * we add it 175*04fd306cSNickeau */ 176*04fd306cSNickeau $headerHasContent = !(empty($contentCalls) && empty($outlineSection->getHeadingCalls()) && empty($adCall)); 177*04fd306cSNickeau if ($headerHasContent) { 178*04fd306cSNickeau $totalComboCalls = array_merge( 179*04fd306cSNickeau $totalComboCalls, 180*04fd306cSNickeau [$this->getOpenHeaderCall()], 181*04fd306cSNickeau $outlineSection->getHeadingCalls(), 182*04fd306cSNickeau $contentCalls, 183*04fd306cSNickeau $adCalls 184*04fd306cSNickeau ); 185*04fd306cSNickeau $this->addSectionEditButtonComboFormatIfNeeded($outlineSection, $sectionSequenceId, $totalComboCalls); 186*04fd306cSNickeau $totalComboCalls[] = $this->getCloseHeaderCall(); 187*04fd306cSNickeau } 188*04fd306cSNickeau 189*04fd306cSNickeau foreach ($actualChildren as $child) { 190*04fd306cSNickeau $this->toHtmlSectionOutlineCallsRecurse($child, $totalComboCalls, $sectionSequenceId, $captureHeaderMeta); 191*04fd306cSNickeau } 192*04fd306cSNickeau 193*04fd306cSNickeau } else { 194*04fd306cSNickeau 195*04fd306cSNickeau $totalComboCalls = array_merge( 196*04fd306cSNickeau $totalComboCalls, 197*04fd306cSNickeau $adCalls, 198*04fd306cSNickeau $outlineSection->getHeadingCalls(), 199*04fd306cSNickeau $contentCalls 200*04fd306cSNickeau ); 201*04fd306cSNickeau 202*04fd306cSNickeau $this->addSectionEditButtonComboFormatIfNeeded($outlineSection, $sectionSequenceId, $totalComboCalls); 203*04fd306cSNickeau } 204*04fd306cSNickeau 205*04fd306cSNickeau $totalComboCalls[] = Call::createComboCall( 206*04fd306cSNickeau SectionTag::TAG, 207*04fd306cSNickeau DOKU_LEXER_EXIT, 208*04fd306cSNickeau [], 209*04fd306cSNickeau null, 210*04fd306cSNickeau null, 211*04fd306cSNickeau null, 212*04fd306cSNickeau null, 213*04fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 214*04fd306cSNickeau ); 215*04fd306cSNickeau 216*04fd306cSNickeau 217*04fd306cSNickeau } 218*04fd306cSNickeau 219*04fd306cSNickeau /** 220*04fd306cSNickeau * Add the edit button if needed 221*04fd306cSNickeau * @param $outlineSection 222*04fd306cSNickeau * @param $sectionSequenceId 223*04fd306cSNickeau * @param array $totalInstructionCalls 224*04fd306cSNickeau */ 225*04fd306cSNickeau private 226*04fd306cSNickeau function addSectionEditButtonComboFormatIfNeeded(OutlineSection $outlineSection, int $sectionSequenceId, array &$totalInstructionCalls): void 227*04fd306cSNickeau { 228*04fd306cSNickeau if (!$outlineSection->hasParent()) { 229*04fd306cSNickeau // no button for the root (ie the page) 230*04fd306cSNickeau return; 231*04fd306cSNickeau } 232*04fd306cSNickeau if ($this->outline->isSectionEditingEnabled()) { 233*04fd306cSNickeau 234*04fd306cSNickeau $editButton = EditButton::create("Edit the section `{$outlineSection->getLabel()}`") 235*04fd306cSNickeau ->setStartPosition($outlineSection->getStartPosition()) 236*04fd306cSNickeau ->setEndPosition($outlineSection->getEndPosition()); 237*04fd306cSNickeau if ($outlineSection->hasHeading()) { 238*04fd306cSNickeau $editButton->setOutlineHeadingId($outlineSection->getHeadingId()); 239*04fd306cSNickeau } 240*04fd306cSNickeau 241*04fd306cSNickeau $totalInstructionCalls[] = $editButton 242*04fd306cSNickeau ->setOutlineSectionId($sectionSequenceId) 243*04fd306cSNickeau ->toComboCallComboFormat(); 244*04fd306cSNickeau 245*04fd306cSNickeau } 246*04fd306cSNickeau 247*04fd306cSNickeau } 248*04fd306cSNickeau 249*04fd306cSNickeau 250*04fd306cSNickeau private function getIsLastSectionToBePrinted(OutlineSection $outlineSection): bool 251*04fd306cSNickeau { 252*04fd306cSNickeau return $outlineSection === $this->lastSectionToBePrinted; 253*04fd306cSNickeau } 254*04fd306cSNickeau 255*04fd306cSNickeau /** 256*04fd306cSNickeau * @return OutlineSection - the last section to be printed 257*04fd306cSNickeau */ 258*04fd306cSNickeau private function getLastSectionToBePrinted(): OutlineSection 259*04fd306cSNickeau { 260*04fd306cSNickeau return $this->getLastSectionToBePrintedRecurse($this->outline->getRootOutlineSection()); 261*04fd306cSNickeau } 262*04fd306cSNickeau 263*04fd306cSNickeau private function getLastSectionToBePrintedRecurse(OutlineSection $section): OutlineSection 264*04fd306cSNickeau { 265*04fd306cSNickeau $outlineSections = $section->getChildren(); 266*04fd306cSNickeau $array_key_last = array_key_last($outlineSections); 267*04fd306cSNickeau if ($array_key_last !== null) { 268*04fd306cSNickeau $lastSection = $outlineSections[$array_key_last]; 269*04fd306cSNickeau return $this->getLastSectionToBePrintedRecurse($lastSection); 270*04fd306cSNickeau } 271*04fd306cSNickeau return $section; 272*04fd306cSNickeau } 273*04fd306cSNickeau 274*04fd306cSNickeau /** 275*04fd306cSNickeau * Section Header Creation 276*04fd306cSNickeau * If it has children and content, wrap the heading and the content 277*04fd306cSNickeau * in a header tag 278*04fd306cSNickeau * The header tag helps also to get the edit button to stay in place 279*04fd306cSNickeau */ 280*04fd306cSNickeau private function getOpenHeaderCall(): Call 281*04fd306cSNickeau { 282*04fd306cSNickeau 283*04fd306cSNickeau return Call::createComboCall( 284*04fd306cSNickeau \syntax_plugin_combo_header::TAG, 285*04fd306cSNickeau DOKU_LEXER_ENTER, 286*04fd306cSNickeau array( 287*04fd306cSNickeau TagAttributes::CLASS_KEY => StyleAttribute::addComboStrapSuffix("outline-header"), 288*04fd306cSNickeau ), 289*04fd306cSNickeau Outline::CONTEXT 290*04fd306cSNickeau ); 291*04fd306cSNickeau } 292*04fd306cSNickeau 293*04fd306cSNickeau private function getCloseHeaderCall(): Call 294*04fd306cSNickeau { 295*04fd306cSNickeau return Call::createComboCall( 296*04fd306cSNickeau \syntax_plugin_combo_header::TAG, 297*04fd306cSNickeau DOKU_LEXER_EXIT, 298*04fd306cSNickeau [], 299*04fd306cSNickeau Outline::CONTEXT 300*04fd306cSNickeau ); 301*04fd306cSNickeau } 302*04fd306cSNickeau 303*04fd306cSNickeau private function getAdCall(OutlineSection $outlineSection): ?Call 304*04fd306cSNickeau { 305*04fd306cSNickeau $sectionLineCount = $outlineSection->getLineCount(); 306*04fd306cSNickeau $this->currentLineCountSinceLastAd = $this->currentLineCountSinceLastAd + $sectionLineCount; 307*04fd306cSNickeau $this->sectionNumbers += 1; 308*04fd306cSNickeau $isLastSection = $this->getIsLastSectionToBePrinted($outlineSection); 309*04fd306cSNickeau if (AdTag::showAds( 310*04fd306cSNickeau $sectionLineCount, 311*04fd306cSNickeau $this->currentLineCountSinceLastAd, 312*04fd306cSNickeau $this->sectionNumbers, 313*04fd306cSNickeau $this->adsCounter, 314*04fd306cSNickeau $isLastSection, 315*04fd306cSNickeau $this->outline->getMarkupPath() 316*04fd306cSNickeau )) { 317*04fd306cSNickeau 318*04fd306cSNickeau // Number of ads inserted 319*04fd306cSNickeau $this->adsCounter += 1; 320*04fd306cSNickeau // Reset the number of line betwee the last ad 321*04fd306cSNickeau $this->currentLineCountSinceLastAd = 0; 322*04fd306cSNickeau 323*04fd306cSNickeau return Call::createComboCall( 324*04fd306cSNickeau AdTag::MARKUP, 325*04fd306cSNickeau DOKU_LEXER_SPECIAL, 326*04fd306cSNickeau array(AdTag::NAME_ATTRIBUTE => AdTag::PREFIX_IN_ARTICLE_ADS . $this->adsCounter), 327*04fd306cSNickeau Outline::CONTEXT, 328*04fd306cSNickeau null, 329*04fd306cSNickeau null, 330*04fd306cSNickeau null, 331*04fd306cSNickeau \syntax_plugin_combo_xmlblockemptytag::TAG 332*04fd306cSNickeau ); 333*04fd306cSNickeau } 334*04fd306cSNickeau return null; 335*04fd306cSNickeau } 336*04fd306cSNickeau} 337