xref: /plugin/autotooltip/helper.php (revision ad10369cfaaef2f17f06fc3a11172ed6c15a6587)
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.
65*ad10369cSZioth	 * @param string $link - Link to this external URL. TODO: It would be better to parse arbitrary wikitext
66*ad10369cSZioth	 *    in $content, but that requires a refactor to use $renderer instead of generated HTML.
676bfd3f23SEli Fenton	 * @return string
686bfd3f23SEli Fenton	 */
69*ad10369cSZioth	public function forText($content, $tooltip, $title='', $preTitle = '', $classes = '', $textClasses = '', $link = '') {
70b7bccb09SEli Fenton		if (empty($classes)) {
71be213c94SEli Fenton			$classes = $this->getConf('style');
72be213c94SEli Fenton		}
73b7bccb09SEli Fenton		if (empty($classes)) {
74be213c94SEli Fenton			$classes = 'default';
75be213c94SEli Fenton		}
764cbd2578SEli Fenton		$delay = $this->getConf('delay') ?: 0;
77be213c94SEli Fenton
78be213c94SEli Fenton		// Sanitize
79be213c94SEli Fenton		$classes = htmlspecialchars($classes);
80be213c94SEli Fenton		// Add the plugin prefix to all classes.
81be213c94SEli Fenton		$classes = preg_replace('/(\w+)/', 'plugin-autotooltip__$1', $classes);
82be213c94SEli Fenton
83be213c94SEli Fenton		$partCount = (empty($title) ? 0 : 1) + (empty($preTitle) ? 0 : 1) + (empty($tooltip) ? 0 : 1);
84be213c94SEli Fenton		if ($partCount > 1 || strchr($tooltip, "\n") !== FALSE || strlen($tooltip) > 40) {
85be213c94SEli Fenton			$classes .= ' plugin-autotooltip_big';
866bfd3f23SEli Fenton		}
876bfd3f23SEli Fenton
88b9ce7d66SEli Fenton		if (empty($textClasses)) {
89b9ce7d66SEli Fenton			$textClasses = 'plugin-autotooltip_linked';
90969d3afcSEli Fenton			if (strstr($content, '<a ') === FALSE) {
91b9ce7d66SEli Fenton				$textClasses .= ' plugin-autotooltip_simple';
92969d3afcSEli Fenton			}
93969d3afcSEli Fenton		}
946bfd3f23SEli Fenton
95e4338dabSEli Fenton		$contentParts = [];
96e4338dabSEli Fenton		if (!empty($preTitle)) {
97948a1374SEli Fenton			$contentParts[] = $this->_formatTT($preTitle);
98e4338dabSEli Fenton		}
99e4338dabSEli Fenton		if (!empty($title)) {
100e4338dabSEli Fenton			$contentParts[] = '<span class="plugin-autotooltip-title">' . $title . '</span>';
101e4338dabSEli Fenton		}
102e4338dabSEli Fenton		if (!empty($tooltip)) {
103948a1374SEli Fenton			$contentParts[] = $this->_formatTT($tooltip);
104e4338dabSEli Fenton		}
105*ad10369cSZioth		if (!empty($link)) {
106*ad10369cSZioth			$content = '<a href="'.$link.'">'.$content.'</a>';
107*ad10369cSZioth		}
108e4338dabSEli Fenton
109d20b44e1SEli Fenton		return '<span class="' . $textClasses . '" onmouseover="autotooltip.show(event)" onmouseout="autotooltip.hide()" data-delay="' . $delay . '">' .
1106bfd3f23SEli Fenton			$content .
11107c401fcSEli Fenton			'<span class="plugin-autotooltip-hidden-classes">' . $classes . '</span>' .
1126e7b98e9SEli Fenton			'<!-- googleoff: all -->' .
113e4338dabSEli Fenton			'<span class="plugin-autotooltip-hidden-tip">' .
114e4338dabSEli Fenton			implode('<br><br>', $contentParts) .
115e4338dabSEli Fenton			'</span>' .
1166e7b98e9SEli Fenton			'<!-- googleon: all -->' .
11707c401fcSEli Fenton		'</span>';
1186bfd3f23SEli Fenton	}
1196bfd3f23SEli Fenton
1206bfd3f23SEli Fenton
1216bfd3f23SEli Fenton	/**
1226bfd3f23SEli Fenton	 * Render a tooltip, with the title and abstract of a page.
1236bfd3f23SEli Fenton	 *
1246bfd3f23SEli Fenton	 * @param string $id - A page id.
125948a1374SEli Fenton	 * @param string $content - The on-page content. Newlines will be rendered as line breaks. Omit to use the page's title.
126948a1374SEli Fenton	 * @param string $preTitle - Text to display before the title in the tooltip. Newlines will be rendered as line breaks.
1276bfd3f23SEli Fenton	 * @param string $classes - CSS classes to add to this tooltip.
128b9ce7d66SEli Fenton	 * @param string $textClasses - CSS classes to add to the linked text.
1296bfd3f23SEli Fenton	 * @return string
1306bfd3f23SEli Fenton	 */
131e2969550SEli Fenton	public function forWikilink($id, $content = null, $preTitle = '', $classes = '', $textClasses = '') {
132d39942acSEli Fenton		global $ID;
1331123b0f7SZioth		//$id = resolve_id(getNS($ID), $id, false);
1341123b0f7SZioth		$resolver = new PageResolver($ID);
1351123b0f7SZioth                $id = $resolver->resolveId($id, null, true);
136d39942acSEli Fenton
137d39942acSEli Fenton		$meta = self::read_meta_fast($id);
138d39942acSEli Fenton		$title = $meta['title'];
1396bfd3f23SEli Fenton
140812ebc9aSEli Fenton		$link = $this->localRenderer->internallink($id, $content ?: $title, null, true);
1416bfd3f23SEli Fenton
142d39942acSEli Fenton		if (page_exists(preg_replace('/\#.*$/', '', $id))) {
1434cbd2578SEli Fenton			$link = $this->stripNativeTooltip($link);
144b9ce7d66SEli Fenton			return $this->forText($link, $meta['abstract'], $title, $preTitle, $classes, $textClasses);
1456bfd3f23SEli Fenton		}
1466bfd3f23SEli Fenton		else {
1476bfd3f23SEli Fenton			return $link;
1486bfd3f23SEli Fenton		}
1496bfd3f23SEli Fenton	}
1506bfd3f23SEli Fenton
1516bfd3f23SEli Fenton
1526bfd3f23SEli Fenton	/**
153e2969550SEli Fenton	 * Is this id excluded from the plugin?
154e2969550SEli Fenton	 *
155e2969550SEli Fenton	 * @param string $id
156e2969550SEli Fenton	 * @return boolean
157e2969550SEli Fenton	 */
158e2969550SEli Fenton	public function isExcluded($id) {
159e2969550SEli Fenton		$inclusions = $this->getConf('linkall_inclusions');
160e2969550SEli Fenton		$exclusions = $this->getConf('linkall_exclusions');
161e2969550SEli Fenton		return (!empty($inclusions) && !preg_match("/$inclusions/", $id)) ||
162e2969550SEli Fenton			(!empty($exclusions) && preg_match("/$exclusions/", $id));
163e2969550SEli Fenton	}
164e2969550SEli Fenton
165e2969550SEli Fenton
166e2969550SEli Fenton	/**
1674cbd2578SEli Fenton	 * Strip the native title= tooltip from an anchor tag.
1684cbd2578SEli Fenton	 *
1694cbd2578SEli Fenton	 * @param string $link
1704cbd2578SEli Fenton	 * @return string
1714cbd2578SEli Fenton	 */
172e2969550SEli Fenton	public function stripNativeTooltip($link) {
1734cbd2578SEli Fenton		return preg_replace('/title="[^"]*"/', '', $link);
1744cbd2578SEli Fenton	}
1754cbd2578SEli Fenton
1764cbd2578SEli Fenton
1774cbd2578SEli Fenton	/**
178d39942acSEli Fenton	 * Reads specific metadata about 10x faster than p_get_metadata. p_get_metadata only uses caching for the current
179d39942acSEli Fenton	 * page, and uses the very slow php serialization. However, in a wiki with infrequently accessed pages, it's
180d39942acSEli Fenton	 * extremely slow.
181d39942acSEli Fenton	 *
182d39942acSEli Fenton	 * @param string $id
183d39942acSEli Fenton	 * @return array - An array containing 'title' and 'abstract.'
184d39942acSEli Fenton	 */
185d39942acSEli Fenton	static function read_meta_fast($id) {
186d39942acSEli Fenton		global $ID;
187e3504fb9SZioth
188e3504fb9SZioth		$resolver = new PageResolver($ID);
189e3504fb9SZioth		$id = $resolver->resolveId(preg_replace('/\#.*$/', '', $id), null, true);
190e3504fb9SZioth
191d39942acSEli Fenton
192d39942acSEli Fenton		if (isset(self::$metaCache[$id])) {
193d39942acSEli Fenton			return self::$metaCache[$id];
194d39942acSEli Fenton		}
195d39942acSEli Fenton
196b9ce7d66SEli Fenton		$results = [
197b9ce7d66SEli Fenton			'title' => p_get_metadata(cleanID($id), 'title'),
198b9ce7d66SEli Fenton			'abstract' => p_get_metadata(cleanID($id), 'plugin_description keywords') ?: p_get_metadata(cleanID($id), 'description abstract')
199b9ce7d66SEli Fenton		];
200d39942acSEli Fenton
201d39942acSEli Fenton		// By default, the abstract starts with the title. Remove it so it's not displayed twice, but still fetch
202d39942acSEli Fenton		// both pieces of metadata, in case another plugin rewrote the abstract.
203d39942acSEli Fenton		$results['abstract'] = preg_replace(
204d39942acSEli Fenton			'/^' . self::_pregEscape($results['title']) . '(\r?\n)+/',
205d39942acSEli Fenton			'',
206d39942acSEli Fenton			$results['abstract']
207d39942acSEli Fenton		);
208d39942acSEli Fenton
209d39942acSEli Fenton		self::$metaCache[$id] = $results;
210d39942acSEli Fenton		return $results;
211d39942acSEli Fenton	}
212d39942acSEli Fenton
213d39942acSEli Fenton
214d39942acSEli Fenton	/**
2156bfd3f23SEli Fenton	 * Format tooltip text.
2166bfd3f23SEli Fenton	 *
2176bfd3f23SEli Fenton	 * @param string $tt - Tooltip text.
2186bfd3f23SEli Fenton	 * @return string
2196bfd3f23SEli Fenton	 */
2206bfd3f23SEli Fenton	private function _formatTT($tt) {
221948a1374SEli Fenton		// Convert double-newlines into vertical space.
222948a1374SEli Fenton		$tt = preg_replace('/(\r?\n){2,}/', '<br><br>', $tt);
223948a1374SEli Fenton		// Single newlines get collapsed, just like in HTML.
224948a1374SEli Fenton		return preg_replace('/(\r?\n)/', ' ', $tt);
2256bfd3f23SEli Fenton	}
226d852dc10SEli Fenton
227d852dc10SEli Fenton
228d852dc10SEli Fenton	/**
229d852dc10SEli Fenton	 * Escape a string for inclusion in a regular expression, assuming forward slash is used as the delimiter.
230d852dc10SEli Fenton	 *
231d852dc10SEli Fenton	 * @param string $r - The regex string, without delimiters.
232d852dc10SEli Fenton	 * @return string
233d852dc10SEli Fenton	 */
234d39942acSEli Fenton	private static function _pregEscape($r) {
235d852dc10SEli Fenton		return preg_replace('/\//', '\\/', preg_quote($r));
236d852dc10SEli Fenton	}
2376bfd3f23SEli Fenton}
238