xref: /plugin/autotooltip/helper.php (revision d39942acf22d9f01780a7916bedd6c84062f0cb1)
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