xref: /plugin/autotooltip/helper.php (revision 6e7b98e9b2358fada991beff059250b4651676ac)
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 */
10dd27e4fbSEli Fentonclass helper_plugin_autotooltip extends DokuWiki_Plugin {
116bfd3f23SEli Fenton	private $localRenderer;
12d39942acSEli 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	 */
24e2969550SEli Fenton	public 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',
35b9ce7d66SEli Fenton				'textClasses (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',
47b9ce7d66SEli Fenton				'textClasses (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.
63b9ce7d66SEli Fenton	 * @param string $textClasses - CSS classes to add to the linked text.
646bfd3f23SEli Fenton	 * @return string
656bfd3f23SEli Fenton	 */
66e2969550SEli Fenton	public function forText($content, $tooltip, $title='', $preTitle = '', $classes = '', $textClasses = '') {
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
85b9ce7d66SEli Fenton		if (empty($textClasses)) {
86b9ce7d66SEli Fenton			$textClasses = 'plugin-autotooltip_linked';
87969d3afcSEli Fenton			if (strstr($content, '<a ') === FALSE) {
88b9ce7d66SEli Fenton				$textClasses .= ' plugin-autotooltip_simple';
89969d3afcSEli Fenton			}
90969d3afcSEli Fenton		}
916bfd3f23SEli Fenton
92e4338dabSEli Fenton		$contentParts = [];
93e4338dabSEli Fenton		if (!empty($preTitle)) {
94948a1374SEli Fenton			$contentParts[] = $this->_formatTT($preTitle);
95e4338dabSEli Fenton		}
96e4338dabSEli Fenton		if (!empty($title)) {
97e4338dabSEli Fenton			$contentParts[] = '<span class="plugin-autotooltip-title">' . $title . '</span>';
98e4338dabSEli Fenton		}
99e4338dabSEli Fenton		if (!empty($tooltip)) {
100948a1374SEli Fenton			$contentParts[] = $this->_formatTT($tooltip);
101e4338dabSEli Fenton		}
102e4338dabSEli Fenton
103d20b44e1SEli Fenton		return '<span class="' . $textClasses . '" onmouseover="autotooltip.show(event)" onmouseout="autotooltip.hide()" data-delay="' . $delay . '">' .
1046bfd3f23SEli Fenton			$content .
10507c401fcSEli Fenton			'<span class="plugin-autotooltip-hidden-classes">' . $classes . '</span>' .
106*6e7b98e9SEli Fenton			'<!-- googleoff: all -->' .
107e4338dabSEli Fenton			'<span class="plugin-autotooltip-hidden-tip">' .
108e4338dabSEli Fenton			implode('<br><br>', $contentParts) .
109e4338dabSEli Fenton			'</span>' .
110*6e7b98e9SEli Fenton			'<!-- googleon: all -->' .
11107c401fcSEli Fenton		'</span>';
1126bfd3f23SEli Fenton	}
1136bfd3f23SEli Fenton
1146bfd3f23SEli Fenton
1156bfd3f23SEli Fenton	/**
1166bfd3f23SEli Fenton	 * Render a tooltip, with the title and abstract of a page.
1176bfd3f23SEli Fenton	 *
1186bfd3f23SEli Fenton	 * @param string $id - A page id.
119948a1374SEli Fenton	 * @param string $content - The on-page content. Newlines will be rendered as line breaks. Omit to use the page's title.
120948a1374SEli Fenton	 * @param string $preTitle - Text to display before the title in the tooltip. Newlines will be rendered as line breaks.
1216bfd3f23SEli Fenton	 * @param string $classes - CSS classes to add to this tooltip.
122b9ce7d66SEli Fenton	 * @param string $textClasses - CSS classes to add to the linked text.
1236bfd3f23SEli Fenton	 * @return string
1246bfd3f23SEli Fenton	 */
125e2969550SEli Fenton	public function forWikilink($id, $content = null, $preTitle = '', $classes = '', $textClasses = '') {
126d39942acSEli Fenton		global $ID;
127d39942acSEli Fenton		$id = resolve_id(getNS($ID), $id, false);
128d39942acSEli Fenton
129d39942acSEli Fenton		$meta = self::read_meta_fast($id);
130d39942acSEli Fenton		$title = $meta['title'];
1316bfd3f23SEli Fenton
132812ebc9aSEli Fenton		$link = $this->localRenderer->internallink($id, $content ?: $title, null, true);
1336bfd3f23SEli Fenton
134d39942acSEli Fenton		if (page_exists(preg_replace('/\#.*$/', '', $id))) {
1354cbd2578SEli Fenton			$link = $this->stripNativeTooltip($link);
136b9ce7d66SEli Fenton			return $this->forText($link, $meta['abstract'], $title, $preTitle, $classes, $textClasses);
1376bfd3f23SEli Fenton		}
1386bfd3f23SEli Fenton		else {
1396bfd3f23SEli Fenton			return $link;
1406bfd3f23SEli Fenton		}
1416bfd3f23SEli Fenton	}
1426bfd3f23SEli Fenton
1436bfd3f23SEli Fenton
1446bfd3f23SEli Fenton	/**
145e2969550SEli Fenton	 * Is this id excluded from the plugin?
146e2969550SEli Fenton	 *
147e2969550SEli Fenton	 * @param string $id
148e2969550SEli Fenton	 * @return boolean
149e2969550SEli Fenton	 */
150e2969550SEli Fenton	public function isExcluded($id) {
151e2969550SEli Fenton		$inclusions = $this->getConf('linkall_inclusions');
152e2969550SEli Fenton		$exclusions = $this->getConf('linkall_exclusions');
153e2969550SEli Fenton		return (!empty($inclusions) && !preg_match("/$inclusions/", $id)) ||
154e2969550SEli Fenton			(!empty($exclusions) && preg_match("/$exclusions/", $id));
155e2969550SEli Fenton	}
156e2969550SEli Fenton
157e2969550SEli Fenton
158e2969550SEli Fenton	/**
1594cbd2578SEli Fenton	 * Strip the native title= tooltip from an anchor tag.
1604cbd2578SEli Fenton	 *
1614cbd2578SEli Fenton	 * @param string $link
1624cbd2578SEli Fenton	 * @return string
1634cbd2578SEli Fenton	 */
164e2969550SEli Fenton	public function stripNativeTooltip($link) {
1654cbd2578SEli Fenton		return preg_replace('/title="[^"]*"/', '', $link);
1664cbd2578SEli Fenton	}
1674cbd2578SEli Fenton
1684cbd2578SEli Fenton
1694cbd2578SEli Fenton	/**
170d39942acSEli Fenton	 * Reads specific metadata about 10x faster than p_get_metadata. p_get_metadata only uses caching for the current
171d39942acSEli Fenton	 * page, and uses the very slow php serialization. However, in a wiki with infrequently accessed pages, it's
172d39942acSEli Fenton	 * extremely slow.
173d39942acSEli Fenton	 *
174d39942acSEli Fenton	 * @param string $id
175d39942acSEli Fenton	 * @return array - An array containing 'title' and 'abstract.'
176d39942acSEli Fenton	 */
177d39942acSEli Fenton	static function read_meta_fast($id) {
178d39942acSEli Fenton		global $ID;
179d39942acSEli Fenton		$id = resolve_id(getNS($ID), preg_replace('/\#.*$/', '', $id), true);
180d39942acSEli Fenton
181d39942acSEli Fenton		if (isset(self::$metaCache[$id])) {
182d39942acSEli Fenton			return self::$metaCache[$id];
183d39942acSEli Fenton		}
184d39942acSEli Fenton
185b9ce7d66SEli Fenton		$results = [
186b9ce7d66SEli Fenton			'title' => p_get_metadata(cleanID($id), 'title'),
187b9ce7d66SEli Fenton			'abstract' => p_get_metadata(cleanID($id), 'plugin_description keywords') ?: p_get_metadata(cleanID($id), 'description abstract')
188b9ce7d66SEli Fenton		];
189d39942acSEli Fenton
190d39942acSEli Fenton		// By default, the abstract starts with the title. Remove it so it's not displayed twice, but still fetch
191d39942acSEli Fenton		// both pieces of metadata, in case another plugin rewrote the abstract.
192d39942acSEli Fenton		$results['abstract'] = preg_replace(
193d39942acSEli Fenton			'/^' . self::_pregEscape($results['title']) . '(\r?\n)+/',
194d39942acSEli Fenton			'',
195d39942acSEli Fenton			$results['abstract']
196d39942acSEli Fenton		);
197d39942acSEli Fenton
198d39942acSEli Fenton		self::$metaCache[$id] = $results;
199d39942acSEli Fenton		return $results;
200d39942acSEli Fenton	}
201d39942acSEli Fenton
202d39942acSEli Fenton
203d39942acSEli Fenton	/**
2046bfd3f23SEli Fenton	 * Format tooltip text.
2056bfd3f23SEli Fenton	 *
2066bfd3f23SEli Fenton	 * @param string $tt - Tooltip text.
2076bfd3f23SEli Fenton	 * @return string
2086bfd3f23SEli Fenton	 */
2096bfd3f23SEli Fenton	private function _formatTT($tt) {
210948a1374SEli Fenton		// Convert double-newlines into vertical space.
211948a1374SEli Fenton		$tt = preg_replace('/(\r?\n){2,}/', '<br><br>', $tt);
212948a1374SEli Fenton		// Single newlines get collapsed, just like in HTML.
213948a1374SEli Fenton		return preg_replace('/(\r?\n)/', ' ', $tt);
2146bfd3f23SEli Fenton	}
215d852dc10SEli Fenton
216d852dc10SEli Fenton
217d852dc10SEli Fenton	/**
218d852dc10SEli Fenton	 * Escape a string for inclusion in a regular expression, assuming forward slash is used as the delimiter.
219d852dc10SEli Fenton	 *
220d852dc10SEli Fenton	 * @param string $r - The regex string, without delimiters.
221d852dc10SEli Fenton	 * @return string
222d852dc10SEli Fenton	 */
223d39942acSEli Fenton	private static function _pregEscape($r) {
224d852dc10SEli Fenton		return preg_replace('/\//', '\\/', preg_quote($r));
225d852dc10SEli Fenton	}
2266bfd3f23SEli Fenton}
227