1<?php 2/** 3 * AutoLink 4 DokuWiki plugin 4 * 5 * @license MIT 6 * @author Eli Fenton 7 */ 8if(!defined('DOKU_INC')) die(); 9require_once(DOKU_PLUGIN.'autolink4/consts.php'); 10 11class helper_plugin_autolink4 extends DokuWiki_Plugin { 12 use autotooltip4_consts; 13 const CONFIG_FILE = DOKU_CONF . 'autolink4.conf'; 14 15 static $didInit = false; 16 static $subs = []; 17 static $regexSubs = []; // flat array of data 18 static $simpleSubs = []; // 2d map of [namespace][string match]=>data 19 20 private $ignoreMatches = []; // Ignore these, because they were already found once and they're configured to be unique. 21 22 public function getSubs() { 23 return self::$subs; //TODO: Remove this later? 24 } 25 26 /** 27 * Saves the config file 28 * 29 * @param string $config the raw text for the config 30 * @return bool 31 */ 32 public function saveConfigFile($config) { 33 return io_saveFile(self::CONFIG_FILE, cleanText($config)); 34 } 35 36 37 /** 38 * Load the config file 39 */ 40 public function loadConfigFile() { 41 if (file_exists(self::CONFIG_FILE)) { 42 return io_readFile(self::CONFIG_FILE); 43 } 44 } 45 46 47 /** 48 * Load the config file 49 */ 50 public function loadAndProcessConfigFile() { 51 // Only load once, so we don't re-process with things like plugin:include. 52 if (self::$didInit) { 53 return; 54 } 55 self::$didInit = true; 56 57 if (!file_exists(self::CONFIG_FILE)) { 58 return; 59 } 60 61 $cfg = io_readFile(self::CONFIG_FILE); 62 63 global $ID; 64 $current_ns = getNS($ID); 65 66 // Convert the config into usable data. 67 $lines = preg_split('/[\n\r]+/', $cfg); 68 foreach ($lines as $line) { 69 $line = trim($line); 70 if (strlen($line) == 0) { 71 continue; 72 } 73 74 $data = array_pad(str_getcsv($line), self::$MAX_VAL, ''); 75 if (!strlen($data[self::$ORIG]) || !strlen($data[self::$TO])) { 76 continue; 77 } 78 79 $orig = trim($data[self::$ORIG]); 80 81 $ns = isset($data[self::$IN]) ? trim($data[self::$IN]) : null; 82 if (!$this->inNS($current_ns, $ns)) { 83 continue; 84 } 85 86 $s = []; 87 $s[self::$ORIG] = $orig; 88 $s[self::$TO] = trim($data[self::$TO]); 89 $s[self::$IN] = $ns; 90 $s[self::$FLAGS] = isset($data[self::$FLAGS]) ? trim($data[self::$FLAGS]) : null; 91 $s[self::$TOOLTIP] = isset($data[self::$FLAGS]) ? strstr($data[self::$FLAGS], 'tt') !== FALSE : false; 92 $s[self::$ONCE] = isset($data[self::$FLAGS]) ? strstr($data[self::$FLAGS], 'once') !== FALSE : false; 93 $s[self::$INWORD] = isset($data[self::$FLAGS]) ? strstr($data[self::$FLAGS], 'inword') !== FALSE : false; 94 95 // Add word breaks, and collapse one space (allows newlines). 96 if ($s[self::$INWORD]) { 97 $s[self::$MATCH] = preg_replace('/ /', '\s', $orig); 98 } 99 else { 100 $s[self::$MATCH] = '\b' . preg_replace('/ /', '\s', $orig) . '\b'; 101 } 102 103 self::$subs[] = $s; 104 105 if (preg_match('/[\\\[?.+*^$]/', $orig)) { 106 self::$regexSubs[] = $s; 107 } 108 else { 109 // If the search string is not a regex, cache it right away, so we don't have to loop 110 // through regexes later. 111 $s = $this->cacheMatch($orig, $s); 112 } 113 } 114 } 115 116 117 /** 118 * Get a simple match 119 */ 120 public function getMatch($match) { 121 if (array_key_exists($match, $this->ignoreMatches)) { 122 return null; 123 } 124 125 // If there's a matching non-regex pattern, or we cached it after finding the regex patter on the page, 126 // we can load it from the cache. 127 $found = null; 128 if (isset(self::$simpleSubs[$match])) { 129 $found = self::$simpleSubs[$match]; 130 } 131 132 if ($found == null) { 133 // There's no way to determine which match sent us here, so we have to loop through the whole list. 134 foreach (self::$regexSubs as &$s) { 135 if (preg_match('/^' . $s[self::$MATCH] . '$/', $match)) { 136 // Cache the matched string, so we don't have to loop more than once for the same match. 137 $found = $this->cacheMatch($match, $s); 138 break; 139 } 140 } 141 } 142 143 if ($found != null && $found[self::$ONCE]) { 144 $this->ignoreMatches[$match] = true; 145 } 146 return $found; 147 } 148 149 150 /** 151 * Cache a simple match 152 */ 153 public function cacheMatch($match, $data) { 154 // We usually call this with a different text match, so that two things can link to the same page. 155 $data[self::$TEXT] = $match; 156 self::$simpleSubs[$match] = $data; 157 return $data; 158 } 159 160 /** 161 * Is one namespace inside another. 162 * 163 * @param string $ns - Search inside this namespace. 164 * @param string $test - Look for this namespace. 165 * @return bool 166 */ 167 function inNS($ns, $test) { 168 $len = strlen($test); 169 return !$len || substr($ns, 0, $len) == $test; 170 } 171} 172