123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- <?php
- /**
- * @package Pico
- * @subpackage PicoTableOfContent
- * @author {notabug,framagit}.org/ohnonot
- */
- class PicoTableOfContent extends AbstractPicoPlugin {
- const API_VERSION = 3;
- // only act if this var is set to True
- private $yeah = False;
- // default settings
- private $depth = 4;
- private $min_headers = 2;
- private $top_txt = ' ∆';
- private $top_txt_title = 'Home';
- private $caption = "Table of Contents";
- private $template = "post"; // config to valid twig template to only act on that template - or "all" for all.
- // internal
- private $toc = '';
- private $xpQuery = '';
- private $content;
- private $css= 'PicoTableOfContent.css';
- private $css_alt= '';
- public function onConfigLoaded(array &$config)
- {
- if(isset($config['PicoTableOfContent']['depth'])) $this->depth = &$config['PicoTableOfContent']['depth'];
- if(isset($config['PicoTableOfContent']['min_headers'])) $this->min_headers = &$config['PicoTableOfContent']['min_headers'];
- if(isset($config['PicoTableOfContent']['top_txt'])) $this->top_txt = &$config['PicoTableOfContent']['top_txt'];
- if(isset($config['PicoTableOfContent']['top_txt_title'])) $this->top_txt_title = &$config['PicoTableOfContent']['top_txt_title'];
- if(isset($config['PicoTableOfContent']['caption'])) $this->caption = &$config['PicoTableOfContent']['caption'];
- if(isset($config['PicoTableOfContent']['template'])) $this->template = &$config['PicoTableOfContent']['template'];
- // building the xpath query for all desired headers, e.g. '//h1|//h2|//h3'
- for ($i=1; $i <= $this->depth; $i++) {
- $this->xpQuery = $this->xpQuery.'//h'.$i;
- ( $i < $this->depth ) and $this->xpQuery = $this->xpQuery.'|';
- }
- }
- public function onMetaParsed(array &$meta)
- {
- if(isset($meta['toc_disabled']) && $meta['toc_disabled'] === TRUE) { $this->yeah = False; return; }
- if(isset($meta['toc_css'])) { $this->css_alt = $meta['toc_css']; }
- if($meta['template'] === $this->template || $this->template === "all") $this->yeah = True;
- }
- public function onContentParsed(&$content)
- {
- // only continue if we want TOC for this page
- if($this->yeah != True) return;
- if(trim($content)=="") { $this->yeah = False; return; }
- $dom = new DOMDocument();
- $dom->preserveWhiteSpace = true;
- // encoding problems require us to jump through hoops here...
- // please read this: stackoverflow.com/a/11310258
- // $content is a html fragment. We like to modify its content with DOMDocument, but it prefers complete documents and completes them
- // if necessary. This leads to strangeness. The best thing I found is to give it a complete document with all required headers and tags,
- // then strip those off in the end. That way we DOMDocument doesn't meddle, and we have control. Hah, hopefully.
- $dom->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$content.'</body></html>',
- LIBXML_NONET|LIBXML_COMPACT|LIBXML_HTML_NODEFDTD|LIBXML_NOWARNING);
- //~ LIBXML_NOEMPTYTAG|LIBXML_NOENT|LIBXML_HTML_NOIMPLIED|LIBXML_NOERROR);
- $trim_before=92; // What we need to trim when saving (the above string added to $content)
- $trim_after=-15; // - " -
- $xp = new DOMXPath($dom);
- $nodes =$xp->query($this->xpQuery);
- // DEBUG
- //~ echo '<pre>';
- //~ foreach ($nodes as $node) {
- //~ echo $node->nodeValue;
- //~ }
- //~ var_dump($nodes);
- //~ array_walk(iterator_to_array($nodes),'var_dump');
- //~ echo '</pre>';
- if($nodes->length < $this->min_headers)
- { $this->yeah = False; return; }
- // add id's to the h tags and at the same time build the TOC
- foreach($nodes as $i => $sort)
- {
- if (isset($sort->tagName) && $sort->tagName !== '')
- {
- if($sort->getAttribute('id') === "")
- {
- $text = preg_replace('~[^\\pL0-9_]+~u', '-', $sort->nodeValue);
- $text = trim($text,'-');
- $text = strtolower($text);
- // build TOC before manipulatng the nodes
- $this->toc = $this->toc.'<li class="toc'.substr($sort->nodeName, 1).'"><a href="#'.$text.'">'.$sort->nodeValue.'</a></li>';
- $sort->setAttribute('id',$text);
- $a = $dom->createElement('a', $this->top_txt);
- $a->setAttribute('title', $this->top_txt_title);
- $a->setAttribute('href', '#');
- $a->setAttribute('class', 'toc-nav');
- $sort->appendChild($a);
- }
- }
- }
- // we now have a full html document in $dom. Let's extract only what is between
- // <body> tags,as it was before. - stackoverflow.com/a/18090774
- //~ $content='';
- //~ foreach($dom->getElementsByTagName("body")->item(0)->childNodes as $child) {
- //~ $content .= $dom->saveHTML($child);
- //~ }
- $content = $dom->saveHTML();
- // remove the tag we added at loadHTML
- $content = substr($content,$trim_before,$trim_after);
- //~ // please read this: stackoverflow.com/a/20675396
- //~ $content = $dom->saveHTML($dom->documentElement);
- //~ $len = strlen($utf8); if(substr($content,0,$len) === $utf8) $content = substr($content,$len);
- $cap = $this->caption =='' ? "" : '<p id="toc-header">'.$this->caption.'</p>';
- $this->toc = '<div id="toc">'.$cap.'<ul>'.$this->toc.'</ul></div>';
- }
- public function onPageRendering(&$templateName, array &$twigVariables)
- {
- if($this->yeah != True) return;
- // adding the correct stylesheet
- if($this->css_alt != "" and substr($this->css_alt,0,1) === '/') $this->css=$twigVariables['base_url'].$this->css_alt;
- //~ else {
- //~ $test=$twigVariables['theme_url'].'/'.$this->css_alt;
- //~ if(file_exists($_SERVER['DOCUMENT_ROOT'].$test)) $this->css=$test;
- //~ else $this->css=$twigVariables['assets_url'].'/'.$this->css_alt;
- //~ }
- else {
- $test=$twigVariables['assets_url'].'/'.$this->css;
- if(file_exists($_SERVER['DOCUMENT_ROOT'].$test)) $this->css=$test;
- else $this->css=$twigVariables['plugins_url'].'/PicoTableOfContent/'.$this->css;
- }
- $this->toc='<link rel="stylesheet" href="'.$this->css.'" type="text/css" />'.$this->toc;
- $twigVariables['TableOfContent'] = new Twig_Markup($this->toc, 'UTF-8'); // tells twig to render this as raw html
- }
- }
|