16bfd3f23SEli Fenton<?php 26bfd3f23SEli Fentonif(!defined('DOKU_INC')) die(); 36bfd3f23SEli Fenton 46bfd3f23SEli Fenton/** 56bfd3f23SEli Fenton * Auto-Tooltip DokuWiki plugin 66bfd3f23SEli Fenton * 76bfd3f23SEli Fenton * @license MIT 86bfd3f23SEli Fenton * @author Eli Fenton 96bfd3f23SEli Fenton */ 106bfd3f23SEli Fentonclass helper_plugin_autotooltip extends DokuWiki_Admin_Plugin { 116bfd3f23SEli Fenton private $localRenderer; 12*d39942acSEli Fenton private static $metaCache = []; 136bfd3f23SEli Fenton 146bfd3f23SEli Fenton public function __construct() { 156bfd3f23SEli Fenton $this->localRenderer = new Doku_Renderer_xhtml; 166bfd3f23SEli Fenton } 176bfd3f23SEli Fenton 186bfd3f23SEli Fenton 196bfd3f23SEli Fenton /** 209e2add7bSEli Fenton * Return methods of this helper 219e2add7bSEli Fenton * 229e2add7bSEli Fenton * @return array with methods description 239e2add7bSEli Fenton */ 249e2add7bSEli Fenton function getMethods() { 259e2add7bSEli Fenton $result = array(); 269e2add7bSEli Fenton $result[] = array( 279e2add7bSEli Fenton 'name' => 'forText', 289e2add7bSEli Fenton 'desc' => 'Manually construct a tooltip', 299e2add7bSEli Fenton 'params' => array( 309e2add7bSEli Fenton 'content' => 'string', 319e2add7bSEli Fenton 'tooltip' => 'string', 329e2add7bSEli Fenton 'title (optional)' => 'string', 339e2add7bSEli Fenton 'preTitle (optional)' => 'string', 349e2add7bSEli Fenton 'classes (optional)' => 'string', 359e2add7bSEli Fenton 'textStyles (optional)' => 'string', 369e2add7bSEli Fenton ), 379e2add7bSEli Fenton 'return' => array('result' => 'string') 389e2add7bSEli Fenton ); 399e2add7bSEli Fenton $result[] = array( 409e2add7bSEli Fenton 'name' => 'forWikilink', 419e2add7bSEli Fenton 'desc' => 'Generate a tooltip from a wikilink', 429e2add7bSEli Fenton 'params' => array( 439e2add7bSEli Fenton 'id' => 'string', 449e2add7bSEli Fenton 'content (optional)' => 'string', 459e2add7bSEli Fenton 'preTitle (optional)' => 'string', 469e2add7bSEli Fenton 'classes (optional)' => 'string', 479e2add7bSEli Fenton 'textStyles (optional)' => 'string', 489e2add7bSEli Fenton ), 499e2add7bSEli Fenton 'return' => array('result' => 'string') 509e2add7bSEli Fenton ); 519e2add7bSEli Fenton return $result; 529e2add7bSEli Fenton } 539e2add7bSEli Fenton 549e2add7bSEli Fenton 559e2add7bSEli Fenton /** 566bfd3f23SEli Fenton * Return a simple tooltip. 576bfd3f23SEli Fenton * 5807c401fcSEli Fenton * @param string $content - The on-page content. May contain newlines. 59948a1374SEli Fenton * @param string $tooltip - The tooltip content. Newlines will be rendered as line breaks. 60948a1374SEli Fenton * @param string $title - The title inside the tooltip. 61948a1374SEli Fenton * @param string $preTitle - Text to display before the title. Newlines will be rendered as line breaks. 626bfd3f23SEli Fenton * @param string $classes - CSS classes to add to this tooltip. 63969d3afcSEli Fenton * @param string $textStyle - CSS styles for the linked content 646bfd3f23SEli Fenton * @return string 656bfd3f23SEli Fenton */ 66e4338dabSEli Fenton function forText($content, $tooltip, $title='', $preTitle = '', $classes = '', $textStyle = '') { 67b7bccb09SEli Fenton if (empty($classes)) { 68be213c94SEli Fenton $classes = $this->getConf('style'); 69be213c94SEli Fenton } 70b7bccb09SEli Fenton if (empty($classes)) { 71be213c94SEli Fenton $classes = 'default'; 72be213c94SEli Fenton } 734cbd2578SEli Fenton $delay = $this->getConf('delay') ?: 0; 74be213c94SEli Fenton 75be213c94SEli Fenton // Sanitize 76be213c94SEli Fenton $classes = htmlspecialchars($classes); 77be213c94SEli Fenton // Add the plugin prefix to all classes. 78be213c94SEli Fenton $classes = preg_replace('/(\w+)/', 'plugin-autotooltip__$1', $classes); 79be213c94SEli Fenton 80be213c94SEli Fenton $partCount = (empty($title) ? 0 : 1) + (empty($preTitle) ? 0 : 1) + (empty($tooltip) ? 0 : 1); 81be213c94SEli Fenton if ($partCount > 1 || strchr($tooltip, "\n") !== FALSE || strlen($tooltip) > 40) { 82be213c94SEli Fenton $classes .= ' plugin-autotooltip_big'; 836bfd3f23SEli Fenton } 846bfd3f23SEli Fenton 85969d3afcSEli Fenton $textClass = ''; 86969d3afcSEli Fenton if (empty($textStyle)) { 87969d3afcSEli Fenton $textClass = 'plugin-autotooltip_linked'; 88969d3afcSEli Fenton if (strstr($content, '<a ') === FALSE) { 89b7bccb09SEli Fenton $textClass .= ' plugin-autotooltip_simple'; 90969d3afcSEli Fenton } 91969d3afcSEli Fenton } 926bfd3f23SEli Fenton 93e4338dabSEli Fenton $contentParts = []; 94e4338dabSEli Fenton if (!empty($preTitle)) { 95948a1374SEli Fenton $contentParts[] = $this->_formatTT($preTitle); 96e4338dabSEli Fenton } 97e4338dabSEli Fenton if (!empty($title)) { 98e4338dabSEli Fenton $contentParts[] = '<span class="plugin-autotooltip-title">' . $title . '</span>'; 99e4338dabSEli Fenton } 100e4338dabSEli Fenton if (!empty($tooltip)) { 101948a1374SEli Fenton $contentParts[] = $this->_formatTT($tooltip); 102e4338dabSEli Fenton } 103e4338dabSEli Fenton 1044cbd2578SEli Fenton return '<span class="' . $textClass . '" style="' . $textStyle . '" onmouseover="autotooltip.show(this)" onmouseout="autotooltip.hide()" data-delay="' . $delay . '">' . 1056bfd3f23SEli Fenton $content . 10607c401fcSEli Fenton '<span class="plugin-autotooltip-hidden-classes">' . $classes . '</span>' . 107e4338dabSEli Fenton '<span class="plugin-autotooltip-hidden-tip">' . 108e4338dabSEli Fenton implode('<br><br>', $contentParts) . 109e4338dabSEli Fenton '</span>' . 11007c401fcSEli Fenton '</span>'; 1116bfd3f23SEli Fenton } 1126bfd3f23SEli Fenton 1136bfd3f23SEli Fenton 1146bfd3f23SEli Fenton /** 1156bfd3f23SEli Fenton * Render a tooltip, with the title and abstract of a page. 1166bfd3f23SEli Fenton * 1176bfd3f23SEli Fenton * @param string $id - A page id. 118948a1374SEli Fenton * @param string $content - The on-page content. Newlines will be rendered as line breaks. Omit to use the page's title. 119948a1374SEli Fenton * @param string $preTitle - Text to display before the title in the tooltip. Newlines will be rendered as line breaks. 1206bfd3f23SEli Fenton * @param string $classes - CSS classes to add to this tooltip. 121969d3afcSEli Fenton * @param string $linkStyle - Style attribute for the link. 1226bfd3f23SEli Fenton * @return string 1236bfd3f23SEli Fenton */ 124e4338dabSEli Fenton function forWikilink($id, $content = null, $preTitle = '', $classes = '', $linkStyle = '') { 125*d39942acSEli Fenton global $ID; 126*d39942acSEli Fenton $id = resolve_id(getNS($ID), $id, false); 127*d39942acSEli Fenton 128*d39942acSEli Fenton $meta = self::read_meta_fast($id); 129*d39942acSEli Fenton $title = $meta['title']; 1306bfd3f23SEli Fenton 131812ebc9aSEli Fenton $link = $this->localRenderer->internallink($id, $content ?: $title, null, true); 1326bfd3f23SEli Fenton 133969d3afcSEli Fenton if (!empty($linkStyle)) { 134969d3afcSEli Fenton $link = preg_replace('/<a /', '<a style="' . $linkStyle . '" ', $link); 135969d3afcSEli Fenton } 136969d3afcSEli Fenton 137*d39942acSEli Fenton if (page_exists(preg_replace('/\#.*$/', '', $id))) { 1384cbd2578SEli Fenton $link = $this->stripNativeTooltip($link); 139*d39942acSEli Fenton return $this->forText($link, $meta['abstract'], $title, $preTitle, $classes); 1406bfd3f23SEli Fenton } 1416bfd3f23SEli Fenton else { 1426bfd3f23SEli Fenton return $link; 1436bfd3f23SEli Fenton } 1446bfd3f23SEli Fenton } 1456bfd3f23SEli Fenton 1466bfd3f23SEli Fenton 1476bfd3f23SEli Fenton /** 1484cbd2578SEli Fenton * Strip the native title= tooltip from an anchor tag. 1494cbd2578SEli Fenton * 1504cbd2578SEli Fenton * @param string $link 1514cbd2578SEli Fenton * @return string 1524cbd2578SEli Fenton */ 1534cbd2578SEli Fenton function stripNativeTooltip($link) { 1544cbd2578SEli Fenton return preg_replace('/title="[^"]*"/', '', $link); 1554cbd2578SEli Fenton } 1564cbd2578SEli Fenton 1574cbd2578SEli Fenton 1584cbd2578SEli Fenton /** 159*d39942acSEli Fenton * Reads specific metadata about 10x faster than p_get_metadata. p_get_metadata only uses caching for the current 160*d39942acSEli Fenton * page, and uses the very slow php serialization. However, in a wiki with infrequently accessed pages, it's 161*d39942acSEli Fenton * extremely slow. 162*d39942acSEli Fenton * 163*d39942acSEli Fenton * @param string $id 164*d39942acSEli Fenton * @return array - An array containing 'title' and 'abstract.' 165*d39942acSEli Fenton */ 166*d39942acSEli Fenton static function read_meta_fast($id) { 167*d39942acSEli Fenton global $ID; 168*d39942acSEli Fenton $id = resolve_id(getNS($ID), preg_replace('/\#.*$/', '', $id), true); 169*d39942acSEli Fenton 170*d39942acSEli Fenton if (isset(self::$metaCache[$id])) { 171*d39942acSEli Fenton return self::$metaCache[$id]; 172*d39942acSEli Fenton } 173*d39942acSEli Fenton 174*d39942acSEli Fenton // These two lines are from metaFn. Doing it inline is much faster. 175*d39942acSEli Fenton global $conf; 176*d39942acSEli Fenton $mfile = $conf['metadir'].'/'.utf8_encodeFN(str_replace(':','/',$id)).'.meta'; 177*d39942acSEli Fenton 178*d39942acSEli Fenton $txt = file_get_contents($mfile); 179*d39942acSEli Fenton $results = []; 180*d39942acSEli Fenton 181*d39942acSEli Fenton // p_get_metadata(cleanID($id), 'title') 182*d39942acSEli Fenton preg_match('/"title";s:\d+:"([^"]+)"/', $txt, $m); 183*d39942acSEli Fenton if ($m) { 184*d39942acSEli Fenton $results['title'] = $m[1]; 185*d39942acSEli Fenton } 186*d39942acSEli Fenton 187*d39942acSEli Fenton // Description plugin 188*d39942acSEli Fenton // p_get_metadata(cleanID($id), 'plugin_description keywords') 189*d39942acSEli Fenton preg_match('/"plugin_description".+?"keywords";s:\d+:"([^"]+)"/', $txt, $m); 190*d39942acSEli Fenton if ($m) { 191*d39942acSEli Fenton $results['abstract'] = $m[1]; 192*d39942acSEli Fenton } 193*d39942acSEli Fenton else { 194*d39942acSEli Fenton // Default doku abstract is the first 200-500 characters of the page content. 195*d39942acSEli Fenton // p_get_metadata(cleanID($id), 'description abstract') 196*d39942acSEli Fenton preg_match('/"description".+?"abstract";s:\d+:"([^"]+)"/', $txt, $m); 197*d39942acSEli Fenton if ($m) { 198*d39942acSEli Fenton $results['abstract'] = $m[1]; 199*d39942acSEli Fenton } 200*d39942acSEli Fenton } 201*d39942acSEli Fenton 202*d39942acSEli Fenton // By default, the abstract starts with the title. Remove it so it's not displayed twice, but still fetch 203*d39942acSEli Fenton // both pieces of metadata, in case another plugin rewrote the abstract. 204*d39942acSEli Fenton $results['abstract'] = preg_replace( 205*d39942acSEli Fenton '/^' . self::_pregEscape($results['title']) . '(\r?\n)+/', 206*d39942acSEli Fenton '', 207*d39942acSEli Fenton $results['abstract'] 208*d39942acSEli Fenton ); 209*d39942acSEli Fenton 210*d39942acSEli Fenton self::$metaCache[$id] = $results; 211*d39942acSEli Fenton return $results; 212*d39942acSEli Fenton } 213*d39942acSEli Fenton 214*d39942acSEli Fenton 215*d39942acSEli Fenton /** 2166bfd3f23SEli Fenton * Format tooltip text. 2176bfd3f23SEli Fenton * 2186bfd3f23SEli Fenton * @param string $tt - Tooltip text. 2196bfd3f23SEli Fenton * @return string 2206bfd3f23SEli Fenton */ 2216bfd3f23SEli Fenton private function _formatTT($tt) { 222948a1374SEli Fenton // Convert double-newlines into vertical space. 223948a1374SEli Fenton $tt = preg_replace('/(\r?\n){2,}/', '<br><br>', $tt); 224948a1374SEli Fenton // Single newlines get collapsed, just like in HTML. 225948a1374SEli Fenton return preg_replace('/(\r?\n)/', ' ', $tt); 2266bfd3f23SEli Fenton } 227d852dc10SEli Fenton 228d852dc10SEli Fenton 229d852dc10SEli Fenton /** 230d852dc10SEli Fenton * Escape a string for inclusion in a regular expression, assuming forward slash is used as the delimiter. 231d852dc10SEli Fenton * 232d852dc10SEli Fenton * @param string $r - The regex string, without delimiters. 233d852dc10SEli Fenton * @return string 234d852dc10SEli Fenton */ 235*d39942acSEli Fenton private static function _pregEscape($r) { 236d852dc10SEli Fenton return preg_replace('/\//', '\\/', preg_quote($r)); 237d852dc10SEli Fenton } 2386bfd3f23SEli Fenton} 239