1*04fd306cSNickeau<?php 2*04fd306cSNickeau/** 3*04fd306cSNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4*04fd306cSNickeau * 5*04fd306cSNickeau * This source code is licensed under the GPL license found in the 6*04fd306cSNickeau * COPYING file in the root directory of this source tree. 7*04fd306cSNickeau * 8*04fd306cSNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9*04fd306cSNickeau * @author ComboStrap <support@combostrap.com> 10*04fd306cSNickeau * 11*04fd306cSNickeau */ 12*04fd306cSNickeau 13*04fd306cSNickeaunamespace ComboStrap; 14*04fd306cSNickeau 15*04fd306cSNickeau 16*04fd306cSNickeauuse ComboStrap\Meta\Api\Metadata; 17*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 18*04fd306cSNickeauuse Doku_Renderer; 19*04fd306cSNickeauuse DokuWiki_Admin_Plugin; 20*04fd306cSNickeauuse syntax_plugin_combo_toc; 21*04fd306cSNickeau 22*04fd306cSNickeauclass Toc extends Metadata 23*04fd306cSNickeau{ 24*04fd306cSNickeau 25*04fd306cSNickeau 26*04fd306cSNickeau const CANONICAL = syntax_plugin_combo_toc::CANONICAL; 27*04fd306cSNickeau private ?array $tocData = null; 28*04fd306cSNickeau 29*04fd306cSNickeau 30*04fd306cSNickeau public static function createForRequestedPage(): Toc 31*04fd306cSNickeau { 32*04fd306cSNickeau return self::createForPage(MarkupPath::createFromRequestedPage()); 33*04fd306cSNickeau } 34*04fd306cSNickeau 35*04fd306cSNickeau public static function getClass(): string 36*04fd306cSNickeau { 37*04fd306cSNickeau return StyleAttribute::addComboStrapSuffix(self::CANONICAL); 38*04fd306cSNickeau } 39*04fd306cSNickeau 40*04fd306cSNickeau /** 41*04fd306cSNickeau * @throws ExceptionBadArgument - if the TOC is not an array 42*04fd306cSNickeau * @throws ExceptionNotFound - if the TOC variable was not found 43*04fd306cSNickeau */ 44*04fd306cSNickeau public static function createFromGlobalVariable(): Toc 45*04fd306cSNickeau { 46*04fd306cSNickeau global $TOC; 47*04fd306cSNickeau if ($TOC === null) { 48*04fd306cSNickeau throw new ExceptionNotFound("No global TOC variable found"); 49*04fd306cSNickeau } 50*04fd306cSNickeau return (new Toc()) 51*04fd306cSNickeau ->setValue($TOC); 52*04fd306cSNickeau } 53*04fd306cSNickeau 54*04fd306cSNickeau public static function createEmpty(): Toc 55*04fd306cSNickeau { 56*04fd306cSNickeau return new Toc(); 57*04fd306cSNickeau } 58*04fd306cSNickeau 59*04fd306cSNickeau 60*04fd306cSNickeau public function toXhtml(): string 61*04fd306cSNickeau { 62*04fd306cSNickeau 63*04fd306cSNickeau $this->buildCheck(); 64*04fd306cSNickeau 65*04fd306cSNickeau if ($this->tocData === null) { 66*04fd306cSNickeau return ""; 67*04fd306cSNickeau } 68*04fd306cSNickeau 69*04fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::CANONICAL); 70*04fd306cSNickeau 71*04fd306cSNickeau $toc = $this->tocData; 72*04fd306cSNickeau 73*04fd306cSNickeau $tocMinHeads = Site::getTocMinHeadings(); 74*04fd306cSNickeau if (count($toc) < $tocMinHeads) { 75*04fd306cSNickeau return ""; 76*04fd306cSNickeau } 77*04fd306cSNickeau 78*04fd306cSNickeau /** 79*04fd306cSNickeau * Adding toc number style 80*04fd306cSNickeau */ 81*04fd306cSNickeau try { 82*04fd306cSNickeau $css = Outline::getCssNumberingRulesFor(Outline::TOC_NUMBERING); 83*04fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(Outline::TOC_NUMBERING, $css); 84*04fd306cSNickeau } catch (ExceptionNotEnabled $e) { 85*04fd306cSNickeau // not enabled 86*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 87*04fd306cSNickeau LogUtility::error("The toc numbering type was unknown", self::CANONICAL); 88*04fd306cSNickeau } 89*04fd306cSNickeau 90*04fd306cSNickeau /** 91*04fd306cSNickeau * Creating the html 92*04fd306cSNickeau */ 93*04fd306cSNickeau global $lang; 94*04fd306cSNickeau 95*04fd306cSNickeau // To keep track of the HTML level (levels may be badly encoded) 96*04fd306cSNickeau $htmlLevel = 0; 97*04fd306cSNickeau $previousLevel = 0; 98*04fd306cSNickeau $topTocLevel = Site::getTopTocLevel(); 99*04fd306cSNickeau $ulMarkup = ""; 100*04fd306cSNickeau foreach ($toc as $tocItem) { 101*04fd306cSNickeau 102*04fd306cSNickeau $actualLevel = $tocItem["level"]; 103*04fd306cSNickeau 104*04fd306cSNickeau /** 105*04fd306cSNickeau * Skipping to the first top level 106*04fd306cSNickeau */ 107*04fd306cSNickeau if ($actualLevel < $topTocLevel) { 108*04fd306cSNickeau $previousLevel = $actualLevel; 109*04fd306cSNickeau continue; 110*04fd306cSNickeau } 111*04fd306cSNickeau 112*04fd306cSNickeau /** 113*04fd306cSNickeau * Closing 114*04fd306cSNickeau */ 115*04fd306cSNickeau $levelDiff = $previousLevel - $actualLevel; 116*04fd306cSNickeau switch (true) { 117*04fd306cSNickeau case $levelDiff === 0 && (!empty($ulMarkup)): 118*04fd306cSNickeau /** 119*04fd306cSNickeau * Same level 120*04fd306cSNickeau */ 121*04fd306cSNickeau $ulMarkup .= "</li>"; 122*04fd306cSNickeau break; 123*04fd306cSNickeau case ($actualLevel < $previousLevel && !empty($ulMarkup)): 124*04fd306cSNickeau /** 125*04fd306cSNickeau * One or multiple level up 126*04fd306cSNickeau * (from 4 to 2) 127*04fd306cSNickeau */ 128*04fd306cSNickeau $htmlLevel += $levelDiff; 129*04fd306cSNickeau $ulMarkup .= str_repeat("</li></ul>", $levelDiff); 130*04fd306cSNickeau $ulMarkup .= "</li>"; 131*04fd306cSNickeau break; 132*04fd306cSNickeau default: 133*04fd306cSNickeau /** 134*04fd306cSNickeau * One level down 135*04fd306cSNickeau * (We can't go multiple at once) 136*04fd306cSNickeau */ 137*04fd306cSNickeau $htmlLevel -= 1; 138*04fd306cSNickeau $ulMarkup .= "<ul>"; 139*04fd306cSNickeau break; 140*04fd306cSNickeau } 141*04fd306cSNickeau 142*04fd306cSNickeau $href = $tocItem['link']; 143*04fd306cSNickeau $label = $tocItem['title']; 144*04fd306cSNickeau $tocLevelClass = StyleAttribute::addComboStrapSuffix("toc-level-$actualLevel"); 145*04fd306cSNickeau $ulMarkup .= "<li><a href=\"$href\" class=\"$tocLevelClass\">$label</a>"; 146*04fd306cSNickeau /** 147*04fd306cSNickeau * Close 148*04fd306cSNickeau */ 149*04fd306cSNickeau $previousLevel = $actualLevel; 150*04fd306cSNickeau } 151*04fd306cSNickeau // grand closing 152*04fd306cSNickeau $ulMarkup .= str_repeat("</li></ul>", abs($htmlLevel)); 153*04fd306cSNickeau $tocHeaderLang = $lang['toc']; 154*04fd306cSNickeau $tocHeaderClass = StyleAttribute::addComboStrapSuffix("toc-header"); 155*04fd306cSNickeau return <<<EOF 156*04fd306cSNickeau<p class="$tocHeaderClass">$tocHeaderLang</p> 157*04fd306cSNickeau$ulMarkup 158*04fd306cSNickeauEOF; 159*04fd306cSNickeau 160*04fd306cSNickeau 161*04fd306cSNickeau } 162*04fd306cSNickeau 163*04fd306cSNickeau 164*04fd306cSNickeau /** 165*04fd306cSNickeau * @param Doku_Renderer $renderer 166*04fd306cSNickeau * @return bool if the toc need to be shown 167*04fd306cSNickeau * 168*04fd306cSNickeau * From {@link Doku_Renderer::notoc()} 169*04fd306cSNickeau * $this->info['toc'] = false; 170*04fd306cSNickeau * when 171*04fd306cSNickeau * ~~NOTOC~~ 172*04fd306cSNickeau */ 173*04fd306cSNickeau public static function showToc(Doku_Renderer $renderer): bool 174*04fd306cSNickeau { 175*04fd306cSNickeau 176*04fd306cSNickeau global $ACT; 177*04fd306cSNickeau 178*04fd306cSNickeau /** 179*04fd306cSNickeau * Search page, no toc 180*04fd306cSNickeau */ 181*04fd306cSNickeau if ($ACT === 'search') { 182*04fd306cSNickeau return false; 183*04fd306cSNickeau } 184*04fd306cSNickeau 185*04fd306cSNickeau /** 186*04fd306cSNickeau * If this is another template such as Dokuwiki, we get two TOC. 187*04fd306cSNickeau */ 188*04fd306cSNickeau if (!Site::isStrapTemplate()) { 189*04fd306cSNickeau return false; 190*04fd306cSNickeau } 191*04fd306cSNickeau 192*04fd306cSNickeau /** 193*04fd306cSNickeau * On the admin page 194*04fd306cSNickeau */ 195*04fd306cSNickeau if ($ACT === 'admin') { 196*04fd306cSNickeau 197*04fd306cSNickeau global $INPUT; 198*04fd306cSNickeau $plugin = null; 199*04fd306cSNickeau $class = $INPUT->str('page'); 200*04fd306cSNickeau if (!empty($class)) { 201*04fd306cSNickeau 202*04fd306cSNickeau $pluginList = plugin_list('admin'); 203*04fd306cSNickeau 204*04fd306cSNickeau if (in_array($class, $pluginList)) { 205*04fd306cSNickeau // attempt to load the plugin 206*04fd306cSNickeau /** @var $plugin DokuWiki_Admin_Plugin */ 207*04fd306cSNickeau $plugin = plugin_load('admin', $class); 208*04fd306cSNickeau } 209*04fd306cSNickeau 210*04fd306cSNickeau if ($plugin !== null) { 211*04fd306cSNickeau global $TOC; 212*04fd306cSNickeau if (!is_array($TOC)) $TOC = $plugin->getTOC(); //if TOC wasn't requested yet 213*04fd306cSNickeau if (!is_array($TOC)) { 214*04fd306cSNickeau return false; 215*04fd306cSNickeau } else { 216*04fd306cSNickeau return true; 217*04fd306cSNickeau } 218*04fd306cSNickeau 219*04fd306cSNickeau } 220*04fd306cSNickeau 221*04fd306cSNickeau } 222*04fd306cSNickeau 223*04fd306cSNickeau } 224*04fd306cSNickeau 225*04fd306cSNickeau // return it if set otherwise return true 226*04fd306cSNickeau return $renderer->info['toc'] ?? true; 227*04fd306cSNickeau 228*04fd306cSNickeau } 229*04fd306cSNickeau 230*04fd306cSNickeau public function shouldTocBePrinted(): bool 231*04fd306cSNickeau { 232*04fd306cSNickeau global $conf; 233*04fd306cSNickeau return $conf['tocminheads'] && count($this->tocData) >= $conf['tocminheads']; 234*04fd306cSNickeau } 235*04fd306cSNickeau 236*04fd306cSNickeau public static function createForPage($page): Toc 237*04fd306cSNickeau { 238*04fd306cSNickeau return (new Toc()) 239*04fd306cSNickeau ->setResource($page); 240*04fd306cSNickeau } 241*04fd306cSNickeau 242*04fd306cSNickeau 243*04fd306cSNickeau /** 244*04fd306cSNickeau * @throws ExceptionBadArgument 245*04fd306cSNickeau */ 246*04fd306cSNickeau public function setValue($value): Toc 247*04fd306cSNickeau { 248*04fd306cSNickeau if (!is_array($value)) { 249*04fd306cSNickeau throw new ExceptionBadArgument("The toc value ($value) is not an array"); 250*04fd306cSNickeau } 251*04fd306cSNickeau $this->tocData = $value; 252*04fd306cSNickeau /** 253*04fd306cSNickeau * We don't set the global TOC because 254*04fd306cSNickeau * if the global TOC is set {@link tpl_admin()}, will not 255*04fd306cSNickeau * ask the toc to the admin plugin 256*04fd306cSNickeau */ 257*04fd306cSNickeau// global $TOC; 258*04fd306cSNickeau// $TOC = $value; 259*04fd306cSNickeau return $this; 260*04fd306cSNickeau } 261*04fd306cSNickeau 262*04fd306cSNickeau public function valueIsNotNull(): bool 263*04fd306cSNickeau { 264*04fd306cSNickeau return $this->tocData !== null; 265*04fd306cSNickeau } 266*04fd306cSNickeau 267*04fd306cSNickeau static public function getDataType(): string 268*04fd306cSNickeau { 269*04fd306cSNickeau return DataType::ARRAY_VALUE; 270*04fd306cSNickeau } 271*04fd306cSNickeau 272*04fd306cSNickeau static public function getDescription(): string 273*04fd306cSNickeau { 274*04fd306cSNickeau return "Table of Contents"; 275*04fd306cSNickeau } 276*04fd306cSNickeau 277*04fd306cSNickeau static public function getLabel(): string 278*04fd306cSNickeau { 279*04fd306cSNickeau return "The table of content for the page"; 280*04fd306cSNickeau } 281*04fd306cSNickeau 282*04fd306cSNickeau public static function getName(): string 283*04fd306cSNickeau { 284*04fd306cSNickeau return "toc"; 285*04fd306cSNickeau } 286*04fd306cSNickeau 287*04fd306cSNickeau static public function getPersistenceType(): string 288*04fd306cSNickeau { 289*04fd306cSNickeau return Metadata::DERIVED_METADATA; 290*04fd306cSNickeau } 291*04fd306cSNickeau 292*04fd306cSNickeau static public function isMutable(): bool 293*04fd306cSNickeau { 294*04fd306cSNickeau return true; 295*04fd306cSNickeau } 296*04fd306cSNickeau 297*04fd306cSNickeau public function setFromStoreValueWithoutException($value): Metadata 298*04fd306cSNickeau { 299*04fd306cSNickeau $this->tocData = $value; 300*04fd306cSNickeau return $this; 301*04fd306cSNickeau // We can't modify the toc of dokuwiki 302*04fd306cSNickeau // This data shows how to get the table of content from dokuwiki 303*04fd306cSNickeau // $description = $metaDataStore->getCurrentFromName("description"); 304*04fd306cSNickeau // if($description!==null) { 305*04fd306cSNickeau // $this->tocData = $description["tableofcontents"]; 306*04fd306cSNickeau // } 307*04fd306cSNickeau 308*04fd306cSNickeau } 309*04fd306cSNickeau 310*04fd306cSNickeau /** 311*04fd306cSNickeau * @return array 312*04fd306cSNickeau * @throws ExceptionNotFound 313*04fd306cSNickeau */ 314*04fd306cSNickeau public function getValue(): array 315*04fd306cSNickeau { 316*04fd306cSNickeau $this->buildCheck(); 317*04fd306cSNickeau if ($this->tocData === null) { 318*04fd306cSNickeau throw new ExceptionNotFound("No toc"); 319*04fd306cSNickeau } 320*04fd306cSNickeau return $this->tocData; 321*04fd306cSNickeau } 322*04fd306cSNickeau 323*04fd306cSNickeau public function getDefaultValue(): array 324*04fd306cSNickeau { 325*04fd306cSNickeau return []; 326*04fd306cSNickeau } 327*04fd306cSNickeau} 328