xref: /plugin/autotooltip/helper.php (revision 1123b0f787acf27d93da9b0fc50c5d116da049ec)
16bfd3f23SEli Fenton<?php
26bfd3f23SEli Fentonif(!defined('DOKU_INC')) die();
3e3504fb9SZiothuse dokuwiki\File\PageResolver;
46bfd3f23SEli Fenton
56bfd3f23SEli Fenton/**
66bfd3f23SEli Fenton * Auto-Tooltip DokuWiki plugin
76bfd3f23SEli Fenton *
86bfd3f23SEli Fenton * @license    MIT
96bfd3f23SEli Fenton * @author     Eli Fenton
106bfd3f23SEli Fenton */
11dd27e4fbSEli Fentonclass helper_plugin_autotooltip extends DokuWiki_Plugin {
126bfd3f23SEli Fenton	private $localRenderer;
13d39942acSEli Fenton	private static $metaCache = [];
146bfd3f23SEli Fenton
156bfd3f23SEli Fenton	public function __construct() {
166bfd3f23SEli Fenton		$this->localRenderer = new Doku_Renderer_xhtml;
176bfd3f23SEli Fenton	}
186bfd3f23SEli Fenton
196bfd3f23SEli Fenton
206bfd3f23SEli Fenton	/**
219e2add7bSEli Fenton	 * Return methods of this helper
229e2add7bSEli Fenton	 *
239e2add7bSEli Fenton	 * @return array with methods description
249e2add7bSEli Fenton	 */
25e2969550SEli Fenton	public function getMethods() {
269e2add7bSEli Fenton		$result = array();
279e2add7bSEli Fenton		$result[] = array(
289e2add7bSEli Fenton			'name' => 'forText',
299e2add7bSEli Fenton			'desc' => 'Manually construct a tooltip',
309e2add7bSEli Fenton			'params' => array(
319e2add7bSEli Fenton				'content' => 'string',
329e2add7bSEli Fenton				'tooltip' => 'string',
339e2add7bSEli Fenton				'title (optional)' => 'string',
349e2add7bSEli Fenton				'preTitle (optional)' => 'string',
359e2add7bSEli Fenton				'classes (optional)' => 'string',
36b9ce7d66SEli Fenton				'textClasses (optional)' => 'string',
379e2add7bSEli Fenton			),
389e2add7bSEli Fenton			'return' => array('result' => 'string')
399e2add7bSEli Fenton		);
409e2add7bSEli Fenton		$result[] = array(
419e2add7bSEli Fenton			'name' => 'forWikilink',
429e2add7bSEli Fenton			'desc' => 'Generate a tooltip from a wikilink',
439e2add7bSEli Fenton			'params' => array(
449e2add7bSEli Fenton				'id' => 'string',
459e2add7bSEli Fenton				'content (optional)' => 'string',
469e2add7bSEli Fenton				'preTitle (optional)' => 'string',
479e2add7bSEli Fenton				'classes (optional)' => 'string',
48b9ce7d66SEli Fenton				'textClasses (optional)' => 'string',
499e2add7bSEli Fenton			),
509e2add7bSEli Fenton			'return' => array('result' => 'string')
519e2add7bSEli Fenton		);
529e2add7bSEli Fenton		return $result;
539e2add7bSEli Fenton	}
549e2add7bSEli Fenton
559e2add7bSEli Fenton
569e2add7bSEli Fenton	/**
576bfd3f23SEli Fenton	 * Return a simple tooltip.
586bfd3f23SEli Fenton	 *
5907c401fcSEli Fenton	 * @param string $content - The on-page content. May contain newlines.
60948a1374SEli Fenton	 * @param string $tooltip - The tooltip content. Newlines will be rendered as line breaks.
61948a1374SEli Fenton	 * @param string $title - The title inside the tooltip.
62948a1374SEli Fenton	 * @param string $preTitle - Text to display before the title. Newlines will be rendered as line breaks.
636bfd3f23SEli Fenton	 * @param string $classes - CSS classes to add to this tooltip.
64b9ce7d66SEli Fenton	 * @param string $textClasses - CSS classes to add to the linked text.
656bfd3f23SEli Fenton	 * @return string
666bfd3f23SEli Fenton	 */
67e2969550SEli Fenton	public function forText($content, $tooltip, $title='', $preTitle = '', $classes = '', $textClasses = '') {
68b7bccb09SEli Fenton		if (empty($classes)) {
69be213c94SEli Fenton			$classes = $this->getConf('style');
70be213c94SEli Fenton		}
71b7bccb09SEli Fenton		if (empty($classes)) {
72be213c94SEli Fenton			$classes = 'default';
73be213c94SEli Fenton		}
744cbd2578SEli Fenton		$delay = $this->getConf('delay') ?: 0;
75be213c94SEli Fenton
76be213c94SEli Fenton		// Sanitize
77be213c94SEli Fenton		$classes = htmlspecialchars($classes);
78be213c94SEli Fenton		// Add the plugin prefix to all classes.
79be213c94SEli Fenton		$classes = preg_replace('/(\w+)/', 'plugin-autotooltip__$1', $classes);
80be213c94SEli Fenton
81be213c94SEli Fenton		$partCount = (empty($title) ? 0 : 1) + (empty($preTitle) ? 0 : 1) + (empty($tooltip) ? 0 : 1);
82be213c94SEli Fenton		if ($partCount > 1 || strchr($tooltip, "\n") !== FALSE || strlen($tooltip) > 40) {
83be213c94SEli Fenton			$classes .= ' plugin-autotooltip_big';
846bfd3f23SEli Fenton		}
856bfd3f23SEli Fenton
86b9ce7d66SEli Fenton		if (empty($textClasses)) {
87b9ce7d66SEli Fenton			$textClasses = 'plugin-autotooltip_linked';
88969d3afcSEli Fenton			if (strstr($content, '<a ') === FALSE) {
89b9ce7d66SEli Fenton				$textClasses .= ' 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
104d20b44e1SEli Fenton		return '<span class="' . $textClasses . '" onmouseover="autotooltip.show(event)" onmouseout="autotooltip.hide()" data-delay="' . $delay . '">' .
1056bfd3f23SEli Fenton			$content .
10607c401fcSEli Fenton			'<span class="plugin-autotooltip-hidden-classes">' . $classes . '</span>' .
1076e7b98e9SEli Fenton			'<!-- googleoff: all -->' .
108e4338dabSEli Fenton			'<span class="plugin-autotooltip-hidden-tip">' .
109e4338dabSEli Fenton			implode('<br><br>', $contentParts) .
110e4338dabSEli Fenton			'</span>' .
1116e7b98e9SEli Fenton			'<!-- googleon: all -->' .
11207c401fcSEli Fenton		'</span>';
1136bfd3f23SEli Fenton	}
1146bfd3f23SEli Fenton
1156bfd3f23SEli Fenton
1166bfd3f23SEli Fenton	/**
1176bfd3f23SEli Fenton	 * Render a tooltip, with the title and abstract of a page.
1186bfd3f23SEli Fenton	 *
1196bfd3f23SEli Fenton	 * @param string $id - A page id.
120948a1374SEli Fenton	 * @param string $content - The on-page content. Newlines will be rendered as line breaks. Omit to use the page's title.
121948a1374SEli Fenton	 * @param string $preTitle - Text to display before the title in the tooltip. Newlines will be rendered as line breaks.
1226bfd3f23SEli Fenton	 * @param string $classes - CSS classes to add to this tooltip.
123b9ce7d66SEli Fenton	 * @param string $textClasses - CSS classes to add to the linked text.
1246bfd3f23SEli Fenton	 * @return string
1256bfd3f23SEli Fenton	 */
126e2969550SEli Fenton	public function forWikilink($id, $content = null, $preTitle = '', $classes = '', $textClasses = '') {
127d39942acSEli Fenton		global $ID;
128*1123b0f7SZioth		//$id = resolve_id(getNS($ID), $id, false);
129*1123b0f7SZioth		$resolver = new PageResolver($ID);
130*1123b0f7SZioth                $id = $resolver->resolveId($id, null, true);
131d39942acSEli Fenton
132d39942acSEli Fenton		$meta = self::read_meta_fast($id);
133d39942acSEli Fenton		$title = $meta['title'];
1346bfd3f23SEli Fenton
135812ebc9aSEli Fenton		$link = $this->localRenderer->internallink($id, $content ?: $title, null, true);
1366bfd3f23SEli Fenton
137d39942acSEli Fenton		if (page_exists(preg_replace('/\#.*$/', '', $id))) {
1384cbd2578SEli Fenton			$link = $this->stripNativeTooltip($link);
139b9ce7d66SEli Fenton			return $this->forText($link, $meta['abstract'], $title, $preTitle, $classes, $textClasses);
1406bfd3f23SEli Fenton		}
1416bfd3f23SEli Fenton		else {
1426bfd3f23SEli Fenton			return $link;
1436bfd3f23SEli Fenton		}
1446bfd3f23SEli Fenton	}
1456bfd3f23SEli Fenton
1466bfd3f23SEli Fenton
1476bfd3f23SEli Fenton	/**
148e2969550SEli Fenton	 * Is this id excluded from the plugin?
149e2969550SEli Fenton	 *
150e2969550SEli Fenton	 * @param string $id
151e2969550SEli Fenton	 * @return boolean
152e2969550SEli Fenton	 */
153e2969550SEli Fenton	public function isExcluded($id) {
154e2969550SEli Fenton		$inclusions = $this->getConf('linkall_inclusions');
155e2969550SEli Fenton		$exclusions = $this->getConf('linkall_exclusions');
156e2969550SEli Fenton		return (!empty($inclusions) && !preg_match("/$inclusions/", $id)) ||
157e2969550SEli Fenton			(!empty($exclusions) && preg_match("/$exclusions/", $id));
158e2969550SEli Fenton	}
159e2969550SEli Fenton
160e2969550SEli Fenton
161e2969550SEli Fenton	/**
1624cbd2578SEli Fenton	 * Strip the native title= tooltip from an anchor tag.
1634cbd2578SEli Fenton	 *
1644cbd2578SEli Fenton	 * @param string $link
1654cbd2578SEli Fenton	 * @return string
1664cbd2578SEli Fenton	 */
167e2969550SEli Fenton	public function stripNativeTooltip($link) {
1684cbd2578SEli Fenton		return preg_replace('/title="[^"]*"/', '', $link);
1694cbd2578SEli Fenton	}
1704cbd2578SEli Fenton
1714cbd2578SEli Fenton
1724cbd2578SEli Fenton	/**
173d39942acSEli Fenton	 * Reads specific metadata about 10x faster than p_get_metadata. p_get_metadata only uses caching for the current
174d39942acSEli Fenton	 * page, and uses the very slow php serialization. However, in a wiki with infrequently accessed pages, it's
175d39942acSEli Fenton	 * extremely slow.
176d39942acSEli Fenton	 *
177d39942acSEli Fenton	 * @param string $id
178d39942acSEli Fenton	 * @return array - An array containing 'title' and 'abstract.'
179d39942acSEli Fenton	 */
180d39942acSEli Fenton	static function read_meta_fast($id) {
181d39942acSEli Fenton		global $ID;
182e3504fb9SZioth
183e3504fb9SZioth		$resolver = new PageResolver($ID);
184e3504fb9SZioth		$id = $resolver->resolveId(preg_replace('/\#.*$/', '', $id), null, true);
185e3504fb9SZioth
186d39942acSEli Fenton
187d39942acSEli Fenton		if (isset(self::$metaCache[$id])) {
188d39942acSEli Fenton			return self::$metaCache[$id];
189d39942acSEli Fenton		}
190d39942acSEli Fenton
191b9ce7d66SEli Fenton		$results = [
192b9ce7d66SEli Fenton			'title' => p_get_metadata(cleanID($id), 'title'),
193b9ce7d66SEli Fenton			'abstract' => p_get_metadata(cleanID($id), 'plugin_description keywords') ?: p_get_metadata(cleanID($id), 'description abstract')
194b9ce7d66SEli Fenton		];
195d39942acSEli Fenton
196d39942acSEli Fenton		// By default, the abstract starts with the title. Remove it so it's not displayed twice, but still fetch
197d39942acSEli Fenton		// both pieces of metadata, in case another plugin rewrote the abstract.
198d39942acSEli Fenton		$results['abstract'] = preg_replace(
199d39942acSEli Fenton			'/^' . self::_pregEscape($results['title']) . '(\r?\n)+/',
200d39942acSEli Fenton			'',
201d39942acSEli Fenton			$results['abstract']
202d39942acSEli Fenton		);
203d39942acSEli Fenton
204d39942acSEli Fenton		self::$metaCache[$id] = $results;
205d39942acSEli Fenton		return $results;
206d39942acSEli Fenton	}
207d39942acSEli Fenton
208d39942acSEli Fenton
209d39942acSEli Fenton	/**
2106bfd3f23SEli Fenton	 * Format tooltip text.
2116bfd3f23SEli Fenton	 *
2126bfd3f23SEli Fenton	 * @param string $tt - Tooltip text.
2136bfd3f23SEli Fenton	 * @return string
2146bfd3f23SEli Fenton	 */
2156bfd3f23SEli Fenton	private function _formatTT($tt) {
216948a1374SEli Fenton		// Convert double-newlines into vertical space.
217948a1374SEli Fenton		$tt = preg_replace('/(\r?\n){2,}/', '<br><br>', $tt);
218948a1374SEli Fenton		// Single newlines get collapsed, just like in HTML.
219948a1374SEli Fenton		return preg_replace('/(\r?\n)/', ' ', $tt);
2206bfd3f23SEli Fenton	}
221d852dc10SEli Fenton
222d852dc10SEli Fenton
223d852dc10SEli Fenton	/**
224d852dc10SEli Fenton	 * Escape a string for inclusion in a regular expression, assuming forward slash is used as the delimiter.
225d852dc10SEli Fenton	 *
226d852dc10SEli Fenton	 * @param string $r - The regex string, without delimiters.
227d852dc10SEli Fenton	 * @return string
228d852dc10SEli Fenton	 */
229d39942acSEli Fenton	private static function _pregEscape($r) {
230d852dc10SEli Fenton		return preg_replace('/\//', '\\/', preg_quote($r));
231d852dc10SEli Fenton	}
2326bfd3f23SEli Fenton}
233