0
|
1 <?php
|
|
2
|
|
3 /**
|
|
4 +-----------------------------------------------------------------------+
|
|
5 | This file is part of the Roundcube Webmail client |
|
|
6 | Copyright (C) 2009-2012, The Roundcube Dev Team |
|
|
7 | |
|
|
8 | Licensed under the GNU General Public License version 3 or |
|
|
9 | any later version with exceptions for skins & plugins. |
|
|
10 | See the README file for a full license statement. |
|
|
11 | |
|
|
12 | PURPOSE: |
|
|
13 | Handle string replacements based on preg_replace_callback |
|
|
14 +-----------------------------------------------------------------------+
|
|
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
|
|
16 +-----------------------------------------------------------------------+
|
|
17 */
|
|
18
|
|
19 /**
|
|
20 * Helper class for string replacements based on preg_replace_callback
|
|
21 *
|
|
22 * @package Framework
|
|
23 * @subpackage Utils
|
|
24 */
|
|
25 class rcube_string_replacer
|
|
26 {
|
|
27 public static $pattern = '/##str_replacement_(\d+)##/';
|
|
28 public $mailto_pattern;
|
|
29 public $link_pattern;
|
|
30 public $linkref_index;
|
|
31 public $linkref_pattern;
|
|
32
|
|
33 protected $values = array();
|
|
34 protected $options = array();
|
|
35 protected $linkrefs = array();
|
|
36 protected $urls = array();
|
|
37 protected $noword = '[^\w@.#-]';
|
|
38
|
|
39
|
|
40 function __construct($options = array())
|
|
41 {
|
|
42 // Simplified domain expression for UTF8 characters handling
|
|
43 // Support unicode/punycode in top-level domain part
|
|
44 $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
|
|
45 $url1 = '.:;,';
|
|
46 $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*\x80-\xFE-';
|
|
47
|
|
48 // Supported link prefixes
|
|
49 $link_prefix = "([\w]+:\/\/|{$this->noword}[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)";
|
|
50
|
|
51 $this->options = $options;
|
|
52 $this->linkref_index = '/\[([^\]#]+)\](:?\s*##str_replacement_(\d+)##)/';
|
|
53 $this->linkref_pattern = '/\[([^\]#]+)\]/';
|
|
54 $this->link_pattern = "/$link_prefix($utf_domain([$url1]*[$url2]+)*)/";
|
|
55 $this->mailto_pattern = "/("
|
|
56 ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part
|
|
57 ."@$utf_domain" // domain-part
|
|
58 ."(\?[$url1$url2]+)?" // e.g. ?subject=test...
|
|
59 .")/";
|
|
60 }
|
|
61
|
|
62 /**
|
|
63 * Add a string to the internal list
|
|
64 *
|
|
65 * @param string String value
|
|
66 *
|
|
67 * @return int Index of value for retrieval
|
|
68 */
|
|
69 public function add($str)
|
|
70 {
|
|
71 $i = count($this->values);
|
|
72 $this->values[$i] = $str;
|
|
73 return $i;
|
|
74 }
|
|
75
|
|
76 /**
|
|
77 * Build replacement string
|
|
78 */
|
|
79 public function get_replacement($i)
|
|
80 {
|
|
81 return '##str_replacement_' . $i . '##';
|
|
82 }
|
|
83
|
|
84 /**
|
|
85 * Callback function used to build HTML links around URL strings
|
|
86 *
|
|
87 * @param array Matches result from preg_replace_callback
|
|
88 * @return int Index of saved string value
|
|
89 */
|
|
90 public function link_callback($matches)
|
|
91 {
|
|
92 $i = -1;
|
|
93 $scheme = strtolower($matches[1]);
|
|
94
|
|
95 if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
|
|
96 $url = $matches[1] . $matches[2];
|
|
97 }
|
|
98 else if (preg_match("/^({$this->noword}*)(www\.)$/i", $matches[1], $m)) {
|
|
99 $url = $m[2] . $matches[2];
|
|
100 $url_prefix = 'http://';
|
|
101 $prefix = $m[1];
|
|
102 }
|
|
103
|
|
104 if ($url) {
|
|
105 $suffix = $this->parse_url_brackets($url);
|
|
106 $attrib = (array)$this->options['link_attribs'];
|
|
107 $attrib['href'] = $url_prefix . $url;
|
|
108
|
|
109 $i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix);
|
|
110 $this->urls[$i] = $attrib['href'];
|
|
111 }
|
|
112
|
|
113 // Return valid link for recognized schemes, otherwise
|
|
114 // return the unmodified string for unrecognized schemes.
|
|
115 return $i >= 0 ? $prefix . $this->get_replacement($i) : $matches[0];
|
|
116 }
|
|
117
|
|
118 /**
|
|
119 * Callback to add an entry to the link index
|
|
120 */
|
|
121 public function linkref_addindex($matches)
|
|
122 {
|
|
123 $key = $matches[1];
|
|
124 $this->linkrefs[$key] = $this->urls[$matches[3]];
|
|
125
|
|
126 return $this->get_replacement($this->add('['.$key.']')) . $matches[2];
|
|
127 }
|
|
128
|
|
129 /**
|
|
130 * Callback to replace link references with real links
|
|
131 */
|
|
132 public function linkref_callback($matches)
|
|
133 {
|
|
134 $i = 0;
|
|
135 if ($url = $this->linkrefs[$matches[1]]) {
|
|
136 $attrib = (array)$this->options['link_attribs'];
|
|
137 $attrib['href'] = $url;
|
|
138 $i = $this->add(html::a($attrib, rcube::Q($matches[1])));
|
|
139 }
|
|
140
|
|
141 return $i > 0 ? '['.$this->get_replacement($i).']' : $matches[0];
|
|
142 }
|
|
143
|
|
144 /**
|
|
145 * Callback function used to build mailto: links around e-mail strings
|
|
146 *
|
|
147 * @param array Matches result from preg_replace_callback
|
|
148 *
|
|
149 * @return int Index of saved string value
|
|
150 */
|
|
151 public function mailto_callback($matches)
|
|
152 {
|
|
153 $href = $matches[1];
|
|
154 $suffix = $this->parse_url_brackets($href);
|
|
155 $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
|
|
156
|
|
157 return $i >= 0 ? $this->get_replacement($i) : '';
|
|
158 }
|
|
159
|
|
160 /**
|
|
161 * Look up the index from the preg_replace matches array
|
|
162 * and return the substitution value.
|
|
163 *
|
|
164 * @param array Matches result from preg_replace_callback
|
|
165 * @return string Value at index $matches[1]
|
|
166 */
|
|
167 public function replace_callback($matches)
|
|
168 {
|
|
169 return $this->values[$matches[1]];
|
|
170 }
|
|
171
|
|
172 /**
|
|
173 * Replace all defined (link|mailto) patterns with replacement string
|
|
174 *
|
|
175 * @param string $str Text
|
|
176 *
|
|
177 * @return string Text
|
|
178 */
|
|
179 public function replace($str)
|
|
180 {
|
|
181 // search for patterns like links and e-mail addresses
|
|
182 $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
|
|
183 $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
|
|
184 // resolve link references
|
|
185 $str = preg_replace_callback($this->linkref_index, array($this, 'linkref_addindex'), $str);
|
|
186 $str = preg_replace_callback($this->linkref_pattern, array($this, 'linkref_callback'), $str);
|
|
187
|
|
188 return $str;
|
|
189 }
|
|
190
|
|
191 /**
|
|
192 * Replace substituted strings with original values
|
|
193 */
|
|
194 public function resolve($str)
|
|
195 {
|
|
196 return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
|
|
197 }
|
|
198
|
|
199 /**
|
|
200 * Fixes bracket characters in URL handling
|
|
201 */
|
|
202 public static function parse_url_brackets(&$url)
|
|
203 {
|
|
204 // #1487672: special handling of square brackets,
|
|
205 // URL regexp allows [] characters in URL, for example:
|
|
206 // "http://example.com/?a[b]=c". However we need to handle
|
|
207 // properly situation when a bracket is placed at the end
|
|
208 // of the link e.g. "[http://example.com]"
|
|
209 // Yes, this is not perfect handles correctly only paired characters
|
|
210 // but it should work for common cases
|
|
211
|
|
212 if (preg_match('/(\\[|\\])/', $url)) {
|
|
213 $in = false;
|
|
214 for ($i=0, $len=strlen($url); $i<$len; $i++) {
|
|
215 if ($url[$i] == '[') {
|
|
216 if ($in)
|
|
217 break;
|
|
218 $in = true;
|
|
219 }
|
|
220 else if ($url[$i] == ']') {
|
|
221 if (!$in)
|
|
222 break;
|
|
223 $in = false;
|
|
224 }
|
|
225 }
|
|
226
|
|
227 if ($i < $len) {
|
|
228 $suffix = substr($url, $i);
|
|
229 $url = substr($url, 0, $i);
|
|
230 }
|
|
231 }
|
|
232
|
|
233 // Do the same for parentheses
|
|
234 if (preg_match('/(\\(|\\))/', $url)) {
|
|
235 $in = false;
|
|
236 for ($i=0, $len=strlen($url); $i<$len; $i++) {
|
|
237 if ($url[$i] == '(') {
|
|
238 if ($in)
|
|
239 break;
|
|
240 $in = true;
|
|
241 }
|
|
242 else if ($url[$i] == ')') {
|
|
243 if (!$in)
|
|
244 break;
|
|
245 $in = false;
|
|
246 }
|
|
247 }
|
|
248
|
|
249 if ($i < $len) {
|
|
250 $suffix = substr($url, $i);
|
|
251 $url = substr($url, 0, $i);
|
|
252 }
|
|
253 }
|
|
254
|
|
255 return $suffix;
|
|
256 }
|
|
257 }
|