*/ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN . 'syntax.php'); require_once(DOKU_PLUGIN . 'subjectindex/inc/common.php'); class syntax_plugin_subjectindex_index extends DokuWiki_Syntax_Plugin { function getType() { return 'substition'; } function getPType() { return 'block'; } function getSort() { return 98; } function connectTo($mode) { // allow for multi-line syntax (clearer when writing many options) $this->Lexer->addSpecialPattern('\{\{subjectindex>(?m).*?(?-m)\}\}', $mode, 'plugin_subjectindex_index'); } function handle($match, $state, $pos, Doku_Handler $handler) { global $ID; $match = substr($match, 15, -2); // strip "{{subjectindex>...}}" markup $opt = array(); // defaults $opt['abstract'] = true; // show snippet (abstract) of page content $opt['border'] = 'none'; // show borders around table and between columns $opt['cols'] = 1; // number of columns in a SubjectIndex display page (max=12) $opt['default'] = false; // whether this display index page is the default for this index section number $opt['hideatoz'] = false; // turn off the A,B,C main headings $opt['proper'] = false; // use proper-case for page names $opt['title'] = false; // use title (first heading) instead of page name $opt['section'] = 0; // which section to use and display (0-9)...hopefully 10 is enough $opt['showorder'] = false; // display any bullet numbers used for ordering $opt['label'] = ''; // table header label at top $opt['regex'] = null; // a regex for filtering the index list $opt['hidejump'] = false; // hide the 'jump to top' link // remove any trailing spaces caused by multi-line syntax $args = explode(';', $match); $args = array_map('trim', $args); foreach ($args as $arg) { list($key, $value) = explode('=', $arg); $key = strtolower($key); switch ($key) { case 'abstract': case 'default': case 'hideatoz': case 'proper': case 'showorder': case 'showcount': case 'hidejump': case 'title': $opt[strtolower($key)] = true; break; case 'border': switch ($value) { case 'none': case 'inside': case 'outside': case 'both': $opt['border'] = $value; break; default: $opt['border'] = 'both'; } break; case 'cols': if ($value < 1) { $value = 1; } elseif ($value > 12) { $value = 12; } $opt['cols'] = $value; break; case 'section': $opt['section'] = ($value < 0) ? 0 : $value; break; case 'label': case 'regex': $opt[$key] = $value; break; default: } } // update the list of default target pages for entry links if ($opt['default'] === true) { SI_Utils::set_target_page($ID, $opt['section']); } return $opt; } function render($mode, Doku_Renderer $renderer, $opt) { if ($mode == 'xhtml') { $renderer->info['cache'] = false; require_once (DOKU_INC . 'inc/indexer.php'); $all_pages = idx_getIndex('page', ''); $all_entries = SI_Utils::get_index(); if ($all_entries->is_empty()) { $renderer->doc .= $this->getLang('empty_index'); return false; } // grab items for chosen index section only $section_entries = $all_entries->filtered($opt['section'], $opt['regex']); $count = count($section_entries->paths); $lines = $this->_create_index($section_entries, $all_pages, $opt['hideatoz'], $opt['proper']); $renderer->doc .= $this->_render_index($lines, $opt, $count); return true; } else { return false; } } // first build a list of valid subject entries to be rendered private function _create_index(SI_Index $section_entries, $all_pages, $hideAtoZ, $proper) { $lines = array(); $links = array(); $prev_path = ''; list($next_entry, $next_pid) = $section_entries->current(); do { $entry = $anchor = $next_entry; $pid = $next_pid; // cache the next entry for comparison purposes later list($next_entry, $next_pid) = $section_entries->next(); // remove any trailing whitespace which could falsify the later comparison $page = rtrim($all_pages[$pid], "\n\r"); // skip to next page if it is not valid: exists, accessible, permitted if ( ! SI_Utils::is_valid_page($page)) { continue; } // note: all comparisons are case-less // (this is an A-Z index after all humans don't distinguish between case when searching) // Need to do this check BEFORE adding the A-Z headings below, because $entry is modified $next_differs = strcasecmp($entry, $next_entry) !== 0; // Create the A-Z heading if ( ! $hideAtoZ) { $matches = array(); $matched = preg_match('/(^\d+\.)?(.).+/', $entry, $matches); // check for ordered entries 1st if ($matched > 0) { $entry = $matches[1] . strtoupper($matches[2]) . '/' . $entry; } else { $entry = strtoupper($entry[0]) . '/' . $entry; } } $cur_node = strtok($entry, '/'); $cur_path = ''; $heading = 1; // html heading number 1-6 do { $is_heading = $is_link = false; // build headers by adding each node $cur_path .= (empty($cur_path)) ? $cur_node : '/' . $cur_node; // we can add the page link(s) only if this is the final level; // links take priority over headings! $next_node = strtok('/'); if ($next_node === false) { $links[] = $page; $is_link = true; // we only make headings if they are completely different from the previous } elseif (strpos($prev_path, $cur_path) !== 0) { $is_heading = true; } // the next_differs check ensures that links will be grouped if (($is_link && $next_differs) || $is_heading) { if ($proper) { $cur_node = ucwords($cur_node); } if ($is_link) { $anchor = SI_Utils::valid_id($anchor); $lines[] = array($heading, $cur_node, $links, $anchor); $links = array(); } else { $lines[] = array($heading, $cur_node, '' ,''); } } // forgive the magic no's = html h1 to h6 is fixed anyway $heading = ($heading > 5) ? 6 : $heading + 1; $cur_node = $next_node; } while ($next_node !== false); $prev_path = $entry; } while ($section_entries->valid()); return $lines; } private function _render_index($lines, $opt, $count){ $links = ''; $label = ''; $show_count = ''; $show_jump = ''; // now render the subject index table $outer_border = ($opt['border'] == 'outside' || $opt['border'] == 'both') ? 'border' : ''; $inner_border = ($opt['border'] == 'inside' || $opt['border'] == 'both') ? 'inner-border' : ''; // fixed point to jump back to at top of the table $top_id = 'top-' . mt_rand(); if ($opt['label'] != '') { $label = '

' . $opt['label'] . '

' . DOKU_LF; } // optional columns width adjustments if ($count > SUBJ_IDX_HONOUR_COLS) { $cols = $opt['cols']; } else { $cols = 1; } if (is_numeric($cols)) { $col_style = 'column-count:' . $cols . '; -moz-column-count:' . $cols . '; -webkit-column-count:' . $cols . ';'; } else { $col_style = 'column-width:' . $cols . '; -moz-column-width:' . $cols . '; -webkit-column-width:' . $cols . ';'; } if ($opt['showcount'] === true) { $show_count = '
' . $count . ' ∞
' . DOKU_LF; } if ($opt['hidejump'] === false) { $show_jump = '' . $this->getLang('link_to_top') . '' . DOKU_LF; } $subjectindex = ''; foreach ($lines as $line) { // grab each entry line list($heading, $cur_node, $pages, $anchor) = $line; // remove the ordering number from the entry if requested if ( ! $opt['showorder']) { $matched = preg_match('/^\d+\.(.+)/', $cur_node, $matches); if ($matched > 0) { $cur_node = $matches[1]; } } $indent_style = 'margin-left:' . ($heading - 1) * 10 . 'px'; $entry = '_render_wikilink($page, $opt['proper'], $opt['title'], $opt['abstract'], $anchor); $cnt++; } if ($cnt > 1) { $freq = '' . count($pages) . ''; } $anchor = ' id="' . $anchor . '"'; $entry .= $anchor . '>' . $cur_node . $freq . '' . $links . ''; $links = ''; // render headings } else { $entry .= '>' . $cur_node . ''; } $subjectindex .= $entry . DOKU_LF; } // actual rendering to wiki page $render = '
' . DOKU_LF; $render .= $label . DOKU_LF;; $render .= '
' . DOKU_LF;; $render .= $subjectindex; $render .= $show_count . $show_jump; $render .= '
' . DOKU_LF; return $render; } /** * Renders a complete page link, plus tooltip, abstract, casing, etc... * * @param string $id * @param bool $proper * @param bool $title * @param mixed $abstract * @param string $anchor * @return string */ private function _render_wikilink($id, $proper, $title, $abstract, $anchor) { $id = (strpos($id, ':') === false) ? ':' . $id : $id; // : needed for root pages // does the user want to see the "title" instead "pagename" if ($title) { $value = p_get_metadata($id, 'title', true); $name = (empty($value)) ? $this->_proper(noNS($id)) : $value; } elseif ($proper) { $name = $this->_proper(noNS($id)); } else { $name = ''; } $link = html_wikilink($id, $name); $link = $this->_add_page_anchor($link, $anchor); // show the "abstract" as a tooltip if ($abstract) { $link = $this->_add_tooltip($link, $id); } return $link; } private function _proper($id) { $id = str_replace(':', ': ', $id); $id = str_replace('_', ' ', $id); $id = ucwords($id); $id = str_replace(': ', ':', $id); return $id; } /** * Swap normal link title (popup) for a more useful preview * * @param string $link display name * @param string $id page id * @return string */ private function _add_tooltip($link, $id) { $tooltip = $this->_get_abstract($id); if (!empty($tooltip)) { $tooltip = str_replace("\n", ' ', $tooltip); $link = preg_replace('/title=\".+?\"/', 'title="' . $tooltip . '"', $link, 1); } return $link; } private function _get_abstract($id) { $meta = \p_get_metadata($id, 'description abstract', true); $meta = ( ! empty($meta)) ? htmlspecialchars($meta, ENT_NOQUOTES, 'UTF-8') : ''; return $meta; } private function _add_page_anchor($link, $anchor) { $link = preg_replace('/\" class/', '#' . $anchor . '" class', $link, 1); return $link; } }