1<?php
2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/');
3if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
4if(!defined('DOKU_REL')) define('DOKU_REL', '/dokuwiki/');
5require_once(DOKU_PLUGIN.'syntax.php');
6require_once(DOKU_PLUGIN.'autolink4/consts.php');
7
8//TODO: Bugs:
9// - lexer combines all plugin search strings. If you have too many links in a namespace (something between 500 and 1000),
10//   the regex gets too long. This has to be changed to an action plugin to fix that, and we can combined regexes in chunks.
11// - How does this play with ORPHANSWANTED?
12//TODO: Document:
13// - Regexes always match the thing that started first, or the longest string if there are two matches.
14//	'Mother in law' - [in law][Mother] => Mother
15//	'The Mother in law' - [Mother in law][Mother] => Mother in law
16
17/**
18 * Autolink 4 DokuWiki plugin
19 *
20 * @license    MIT
21 * @author     Eli Fenton
22 */
23class syntax_plugin_autolink4_regex extends DokuWiki_Syntax_Plugin {
24	use autotooltip4_consts;
25
26	/** @type helper_plugin_autotooltip $tooltip */
27	private $tooltip;
28	/** @type helper_plugin_autolink4 $tooltip */
29	private $helper;
30
31	private $foundMatches;
32
33	public function __construct() {
34		if (!plugin_isdisabled('autotooltip')) {
35			$this->tooltip = plugin_load('helper', 'autotooltip');
36		}
37
38		$this->helper = plugin_load('helper', 'autolink4');
39		$this->helper->loadAndProcessConfigFile();
40	}
41
42
43	/**
44	 * @return string
45	 */
46	function getType() {
47		return 'substition';
48	}
49
50
51	/**
52	 * @return string
53	 */
54	function getPType() {
55		return 'normal';
56	}
57
58
59	/**
60	 * @return int
61	 */
62	function getSort() {
63		// Try not to interfere with any other lexer patterns.
64		return 999;
65	}
66
67
68	/**
69	 * @param $mode
70	 */
71	function connectTo($mode) {
72		global $ID;
73		$ns = getNS($ID);
74
75		foreach ($this->helper->getSubs() as $s) {
76			// Check that it's in the right namespace, and skip links to the current page.
77			if ($this->_inNS($ns, $s[self::$IN]) && !$this->_isSamePage($s[self::$TO], $ID)) {
78				$this->Lexer->addSpecialPattern($s[self::$MATCH], $mode, 'plugin_autolink4_regex');
79			}
80		}
81	}
82
83
84	/**
85	 * Handle the found text, and send it off to render().
86	 *
87	 * @param string $match - The found text, from addSpecialPattern.
88	 * @param int $state - The DokuWiki event state.
89	 * @param int $pos - The position in the full text.
90	 * @param Doku_Handler $handler
91	 * @return array|string
92	 */
93	function handle($match, $state, $pos, Doku_Handler $handler) {
94		if ($this->foundMatches[$match]) {
95			return $match;
96		}
97
98		// Load from cache
99		if (isset($this->helper->getSimpleSubs()[$match])) {
100			$s = $this->helper->getSimpleSubs()[$match];
101			if ($s[self::$ONCE]) {
102				$this->foundMatches[$match] = true;
103			}
104
105			return $s;
106		}
107
108		// Annoyingly, there's no way (I know of) to determine which match sent us here, so we have to loop through the
109		// whole list.
110		foreach ($this->helper->getRegexSubs() as &$s) {
111			if ($s[self::$ONCE]) {
112				$this->foundMatches[$match] = true;
113			}
114
115			if (preg_match('/^' . $s[self::$MATCH] . '$/', $match)) {
116				// Add all found matches to simpleSubs, so we don't have to loop more than once for the same string.
117				$mod = null;
118				if (!isset($this->helper->getSimpleSubs()[$match])) {
119					$mod = $s;
120					$mod[self::$TEXT] = $match;
121					//TODO: Do this differently (cache locally).
122					$this->helper->getSimpleSubs()[$match] = $mod;
123				}
124
125				return $mod;
126			}
127		}
128
129		return $match;
130	}
131
132
133	/**
134	 * Render the replaced links.
135	 *
136	 * @param string $mode
137	 * @param Doku_Renderer|Doku_Renderer_metadata $renderer
138	 * @param array|string $data - Data from handle()
139	 * @return bool
140	 */
141	function render($mode, Doku_Renderer $renderer, $data) {
142		if (is_string($data)) {
143			$renderer->doc .= $data;
144		}
145		else if ($mode == 'xhtml') {
146			if ($this->tooltip && $data[self::$TOOLTIP]) {
147				$renderer->doc .= $this->tooltip->forWikilink($data[self::$TO], $data[self::$TEXT]);
148			}
149			else {
150				$renderer->internallink($data[self::$TO], $data[self::$TEXT]);
151			}
152		}
153		else {
154			if (!$renderer->capture) {
155				return false;
156			}
157			$renderer->doc .= $data[self::$TEXT];
158		}
159		return true;
160	}
161
162
163	/**
164	 * Is one namespace inside another.
165	 *
166	 * @param string $ns - Search inside this namespace.
167	 * @param string $test - Look for this namespace.
168	 * @return bool
169	 */
170	function _inNS($ns, $test) {
171		$len = strlen($test);
172		return !$len || substr($ns, 0, $len) == $test;
173	}
174
175
176	/**
177	 * Are these two the same page>
178	 *
179	 * @param string $p1 - One page.
180	 * @param string $p2 - Another page.
181	 * @return bool
182	 */
183	function _isSamePage($p1, $p2) {
184		return $p1 == $p2;
185	}
186}
187