1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * This file contains those functions specific to the editing box and is |
||||
5 | * generally used for WYSIWYG type functionality. |
||||
6 | * |
||||
7 | * Simple Machines Forum (SMF) |
||||
8 | * |
||||
9 | * @package SMF |
||||
10 | * @author Simple Machines https://www.simplemachines.org |
||||
11 | * @copyright 2022 Simple Machines and individual contributors |
||||
12 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||||
13 | * |
||||
14 | * @version 2.1.2 |
||||
15 | */ |
||||
16 | |||||
17 | if (!defined('SMF')) |
||||
18 | die('No direct access...'); |
||||
19 | |||||
20 | /** |
||||
21 | * As of SMF 2.1, this is unused. But it is available if any mod wants to use it. |
||||
22 | * Convert only the BBC that can be edited in HTML mode for the (old) editor. |
||||
23 | * |
||||
24 | * @deprecated since version 2.1 |
||||
25 | * @param string $text The text with bbcode in it |
||||
26 | * @param boolean $compat_mode Whether to actually convert the text |
||||
27 | * @return string The text |
||||
28 | */ |
||||
29 | function bbc_to_html($text, $compat_mode = false) |
||||
30 | { |
||||
31 | global $modSettings; |
||||
32 | |||||
33 | if (!$compat_mode) |
||||
34 | return $text; |
||||
35 | |||||
36 | // Turn line breaks back into br's. |
||||
37 | $text = strtr($text, array("\r" => '', "\n" => '<br>')); |
||||
38 | |||||
39 | // Prevent conversion of all bbcode inside these bbcodes. |
||||
40 | // @todo Tie in with bbc permissions ? |
||||
41 | foreach (array('code', 'php', 'nobbc') as $code) |
||||
42 | { |
||||
43 | if (strpos($text, '[' . $code) !== false) |
||||
44 | { |
||||
45 | $parts = preg_split('~(\[/' . $code . '\]|\[' . $code . '(?:=[^\]]+)?\])~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); |
||||
46 | |||||
47 | // Only mess with stuff inside tags. |
||||
48 | for ($i = 0, $n = count($parts); $i < $n; $i++) |
||||
49 | { |
||||
50 | // Value of 2 means we're inside the tag. |
||||
51 | if ($i % 4 == 2) |
||||
52 | $parts[$i] = strtr($parts[$i], array('[' => '[', ']' => ']', "'" => "'")); |
||||
53 | } |
||||
54 | // Put our humpty dumpty message back together again. |
||||
55 | $text = implode('', $parts); |
||||
56 | } |
||||
57 | } |
||||
58 | |||||
59 | // What tags do we allow? |
||||
60 | $allowed_tags = array('b', 'u', 'i', 's', 'hr', 'list', 'li', 'font', 'size', 'color', 'img', 'left', 'center', 'right', 'url', 'email', 'ftp', 'sub', 'sup'); |
||||
61 | |||||
62 | $text = parse_bbc($text, true, '', $allowed_tags); |
||||
63 | |||||
64 | // Fix for having a line break then a thingy. |
||||
65 | $text = strtr($text, array('<br><div' => '<div', "\n" => '', "\r" => '')); |
||||
66 | |||||
67 | // Note that IE doesn't understand spans really - make them something "legacy" |
||||
68 | $working_html = array( |
||||
69 | '~<del>(.+?)</del>~i' => '<strike>$1</strike>', |
||||
70 | '~<span\sclass="bbc_u">(.+?)</span>~i' => '<u>$1</u>', |
||||
71 | '~<span\sstyle="color:\s*([#\d\w]+);" class="bbc_color">(.+?)</span>~i' => '<font color="$1">$2</font>', |
||||
72 | '~<span\sstyle="font-family:\s*([#\d\w\s]+);" class="bbc_font">(.+?)</span>~i' => '<font face="$1">$2</font>', |
||||
73 | '~<div\sstyle="text-align:\s*(left|right);">(.+?)</div>~i' => '<p align="$1">$2</p>', |
||||
74 | ); |
||||
75 | $text = preg_replace(array_keys($working_html), array_values($working_html), $text); |
||||
76 | |||||
77 | // Parse unique ID's and disable javascript into the smileys - using the double space. |
||||
78 | $i = 1; |
||||
79 | $text = preg_replace_callback( |
||||
80 | '~(?:\s| )?<(img\ssrc="' . preg_quote($modSettings['smileys_url'], '~') . '/[^<>]+?/([^<>]+?)"\s*)[^<>]*?class="smiley">~', |
||||
81 | function($m) use (&$i) |
||||
82 | { |
||||
83 | return '<' . stripslashes($m[1]) . 'alt="" title="" onresizestart="return false;" id="smiley_' . $i++ . '_' . $m[2] . '" style="padding: 0 3px 0 3px;">'; |
||||
84 | }, |
||||
85 | $text |
||||
86 | ); |
||||
87 | |||||
88 | return $text; |
||||
89 | } |
||||
90 | |||||
91 | /** |
||||
92 | * Converts HTML to BBC |
||||
93 | * As of SMF 2.1, only used by ManageBoards.php (and possibly mods) |
||||
94 | * |
||||
95 | * @param string $text Text containing HTML |
||||
96 | * @return string The text with html converted to bbc |
||||
97 | */ |
||||
98 | function html_to_bbc($text) |
||||
99 | { |
||||
100 | global $modSettings, $smcFunc, $scripturl, $context; |
||||
101 | |||||
102 | // Replace newlines with spaces, as that's how browsers usually interpret them. |
||||
103 | $text = preg_replace("~\s*[\r\n]+\s*~", ' ', $text); |
||||
104 | |||||
105 | // Though some of us love paragraphs, the parser will do better with breaks. |
||||
106 | $text = preg_replace('~</p>\s*?<p~i', '</p><br><p', $text); |
||||
107 | $text = preg_replace('~</p>\s*(?!<)~i', '</p><br>', $text); |
||||
108 | |||||
109 | // Safari/webkit wraps lines in Wysiwyg in <div>'s. |
||||
110 | if (isBrowser('webkit')) |
||||
111 | $text = preg_replace(array('~<div(?:\s(?:[^<>]*?))?' . '>~i', '</div>'), array('<br>', ''), $text); |
||||
112 | |||||
113 | // If there's a trailing break get rid of it - Firefox tends to add one. |
||||
114 | $text = preg_replace('~<br\s?/?' . '>$~i', '', $text); |
||||
115 | |||||
116 | // Remove any formatting within code tags. |
||||
117 | if (strpos($text, '[code') !== false) |
||||
118 | { |
||||
119 | $text = preg_replace('~<br\s?/?' . '>~i', '#smf_br_spec_grudge_cool!#', $text); |
||||
120 | $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); |
||||
121 | |||||
122 | // Only mess with stuff outside [code] tags. |
||||
123 | for ($i = 0, $n = count($parts); $i < $n; $i++) |
||||
124 | { |
||||
125 | // Value of 2 means we're inside the tag. |
||||
126 | if ($i % 4 == 2) |
||||
127 | $parts[$i] = strip_tags($parts[$i]); |
||||
128 | } |
||||
129 | |||||
130 | $text = strtr(implode('', $parts), array('#smf_br_spec_grudge_cool!#' => '<br>')); |
||||
131 | } |
||||
132 | |||||
133 | // Remove scripts, style and comment blocks. |
||||
134 | $text = preg_replace('~<script[^>]*[^/]?' . '>.*?</script>~i', '', $text); |
||||
135 | $text = preg_replace('~<style[^>]*[^/]?' . '>.*?</style>~i', '', $text); |
||||
136 | $text = preg_replace('~\\<\\!--.*?-->~i', '', $text); |
||||
137 | $text = preg_replace('~\\<\\!\\[CDATA\\[.*?\\]\\]\\>~i', '', $text); |
||||
138 | |||||
139 | // Do the smileys ultra first! |
||||
140 | preg_match_all('~<img\b[^>]+alt="([^"]+)"[^>]+class="smiley"[^>]*>(?:\s)?~i', $text, $matches); |
||||
141 | if (!empty($matches[0])) |
||||
142 | { |
||||
143 | // Get all our smiley codes |
||||
144 | $request = $smcFunc['db_query']('', ' |
||||
145 | SELECT code |
||||
146 | FROM {db_prefix}smileys |
||||
147 | ORDER BY LENGTH(code) DESC', |
||||
148 | array() |
||||
149 | ); |
||||
150 | $smiley_codes = $smcFunc['db_fetch_all']($request); |
||||
151 | $smcFunc['db_free_result']($request); |
||||
152 | |||||
153 | foreach ($matches[1] as $k => $possible_code) |
||||
154 | { |
||||
155 | $possible_code = un_htmlspecialchars($possible_code); |
||||
156 | |||||
157 | if (in_array($possible_code, $smiley_codes)) |
||||
158 | $matches[1][$k] = '-[]-smf_smily_start#|#' . $possible_code . '-[]-smf_smily_end#|#'; |
||||
159 | else |
||||
160 | $matches[1][$k] = $matches[0][$k]; |
||||
161 | } |
||||
162 | |||||
163 | // Replace the tags! |
||||
164 | $text = str_replace($matches[0], $matches[1], $text); |
||||
165 | |||||
166 | // Now sort out spaces |
||||
167 | $text = str_replace(array('-[]-smf_smily_end#|#-[]-smf_smily_start#|#', '-[]-smf_smily_end#|#', '-[]-smf_smily_start#|#'), ' ', $text); |
||||
168 | } |
||||
169 | |||||
170 | // Only try to buy more time if the client didn't quit. |
||||
171 | if (connection_aborted() && $context['server']['is_apache']) |
||||
172 | @apache_reset_timeout(); |
||||
173 | |||||
174 | $parts = preg_split('~(<[A-Za-z]+\s*[^<>]*?style="?[^<>"]+"?[^<>]*?(?:/?)>|</[A-Za-z]+>)~', $text, -1, PREG_SPLIT_DELIM_CAPTURE); |
||||
175 | $replacement = ''; |
||||
176 | $stack = array(); |
||||
177 | |||||
178 | foreach ($parts as $part) |
||||
179 | { |
||||
180 | if (preg_match('~(<([A-Za-z]+)\s*[^<>]*?)style="?([^<>"]+)"?([^<>]*?(/?)>)~', $part, $matches) === 1) |
||||
181 | { |
||||
182 | // If it's being closed instantly, we can't deal with it...yet. |
||||
183 | if ($matches[5] === '/') |
||||
184 | continue; |
||||
185 | else |
||||
186 | { |
||||
187 | // Get an array of styles that apply to this element. (The strtr is there to combat HTML generated by Word.) |
||||
188 | $styles = explode(';', strtr($matches[3], array('"' => ''))); |
||||
189 | $curElement = $matches[2]; |
||||
190 | $precedingStyle = $matches[1]; |
||||
191 | $afterStyle = $matches[4]; |
||||
192 | $curCloseTags = ''; |
||||
193 | $extra_attr = ''; |
||||
194 | |||||
195 | foreach ($styles as $type_value_pair) |
||||
196 | { |
||||
197 | // Remove spaces and convert uppercase letters. |
||||
198 | $clean_type_value_pair = strtolower(strtr(trim($type_value_pair), '=', ':')); |
||||
199 | |||||
200 | // Something like 'font-weight: bold' is expected here. |
||||
201 | if (strpos($clean_type_value_pair, ':') === false) |
||||
202 | continue; |
||||
203 | |||||
204 | // Capture the elements of a single style item (e.g. 'font-weight' and 'bold'). |
||||
205 | list ($style_type, $style_value) = explode(':', $type_value_pair); |
||||
206 | |||||
207 | $style_value = trim($style_value); |
||||
208 | |||||
209 | switch (trim($style_type)) |
||||
210 | { |
||||
211 | case 'font-weight': |
||||
212 | if ($style_value === 'bold') |
||||
213 | { |
||||
214 | $curCloseTags .= '[/b]'; |
||||
215 | $replacement .= '[b]'; |
||||
216 | } |
||||
217 | break; |
||||
218 | |||||
219 | case 'text-decoration': |
||||
220 | if ($style_value == 'underline') |
||||
221 | { |
||||
222 | $curCloseTags .= '[/u]'; |
||||
223 | $replacement .= '[u]'; |
||||
224 | } |
||||
225 | elseif ($style_value == 'line-through') |
||||
226 | { |
||||
227 | $curCloseTags .= '[/s]'; |
||||
228 | $replacement .= '[s]'; |
||||
229 | } |
||||
230 | break; |
||||
231 | |||||
232 | case 'text-align': |
||||
233 | if ($style_value == 'left') |
||||
234 | { |
||||
235 | $curCloseTags .= '[/left]'; |
||||
236 | $replacement .= '[left]'; |
||||
237 | } |
||||
238 | elseif ($style_value == 'center') |
||||
239 | { |
||||
240 | $curCloseTags .= '[/center]'; |
||||
241 | $replacement .= '[center]'; |
||||
242 | } |
||||
243 | elseif ($style_value == 'right') |
||||
244 | { |
||||
245 | $curCloseTags .= '[/right]'; |
||||
246 | $replacement .= '[right]'; |
||||
247 | } |
||||
248 | break; |
||||
249 | |||||
250 | case 'font-style': |
||||
251 | if ($style_value == 'italic') |
||||
252 | { |
||||
253 | $curCloseTags .= '[/i]'; |
||||
254 | $replacement .= '[i]'; |
||||
255 | } |
||||
256 | break; |
||||
257 | |||||
258 | case 'color': |
||||
259 | $curCloseTags .= '[/color]'; |
||||
260 | $replacement .= '[color=' . $style_value . ']'; |
||||
261 | break; |
||||
262 | |||||
263 | case 'font-size': |
||||
264 | // Sometimes people put decimals where decimals should not be. |
||||
265 | if (preg_match('~(\d)+\.\d+(p[xt])~i', $style_value, $dec_matches) === 1) |
||||
266 | $style_value = $dec_matches[1] . $dec_matches[2]; |
||||
267 | |||||
268 | $curCloseTags .= '[/size]'; |
||||
269 | $replacement .= '[size=' . $style_value . ']'; |
||||
270 | break; |
||||
271 | |||||
272 | case 'font-family': |
||||
273 | // Only get the first freaking font if there's a list! |
||||
274 | if (strpos($style_value, ',') !== false) |
||||
275 | $style_value = substr($style_value, 0, strpos($style_value, ',')); |
||||
276 | |||||
277 | $curCloseTags .= '[/font]'; |
||||
278 | $replacement .= '[font=' . strtr($style_value, array("'" => '')) . ']'; |
||||
279 | break; |
||||
280 | |||||
281 | // This is a hack for images with dimensions embedded. |
||||
282 | case 'width': |
||||
283 | case 'height': |
||||
284 | if (preg_match('~[1-9]\d*~i', $style_value, $dimension) === 1) |
||||
285 | $extra_attr .= ' ' . $style_type . '="' . $dimension[0] . '"'; |
||||
286 | break; |
||||
287 | |||||
288 | case 'list-style-type': |
||||
289 | if (preg_match('~none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha~i', $style_value, $listType) === 1) |
||||
290 | $extra_attr .= ' listtype="' . $listType[0] . '"'; |
||||
291 | break; |
||||
292 | } |
||||
293 | } |
||||
294 | |||||
295 | // Preserve some tags stripping the styling. |
||||
296 | if (in_array($matches[2], array('a', 'font', 'td'))) |
||||
297 | { |
||||
298 | $replacement .= $precedingStyle . $afterStyle; |
||||
299 | $curCloseTags = '</' . $matches[2] . '>' . $curCloseTags; |
||||
300 | } |
||||
301 | |||||
302 | // If there's something that still needs closing, push it to the stack. |
||||
303 | if (!empty($curCloseTags)) |
||||
304 | array_push($stack, array( |
||||
305 | 'element' => strtolower($curElement), |
||||
306 | 'closeTags' => $curCloseTags |
||||
307 | ) |
||||
308 | ); |
||||
309 | elseif (!empty($extra_attr)) |
||||
310 | $replacement .= $precedingStyle . $extra_attr . $afterStyle; |
||||
311 | } |
||||
312 | } |
||||
313 | |||||
314 | elseif (preg_match('~</([A-Za-z]+)>~', $part, $matches) === 1) |
||||
315 | { |
||||
316 | // Is this the element that we've been waiting for to be closed? |
||||
317 | if (!empty($stack) && strtolower($matches[1]) === $stack[count($stack) - 1]['element']) |
||||
318 | { |
||||
319 | $byebyeTag = array_pop($stack); |
||||
320 | $replacement .= $byebyeTag['closeTags']; |
||||
321 | } |
||||
322 | |||||
323 | // Must've been something else. |
||||
324 | else |
||||
325 | $replacement .= $part; |
||||
326 | } |
||||
327 | // In all other cases, just add the part to the replacement. |
||||
328 | else |
||||
329 | $replacement .= $part; |
||||
330 | } |
||||
331 | |||||
332 | // Now put back the replacement in the text. |
||||
333 | $text = $replacement; |
||||
334 | |||||
335 | // We are not finished yet, request more time. |
||||
336 | if (connection_aborted() && $context['server']['is_apache']) |
||||
337 | @apache_reset_timeout(); |
||||
338 | |||||
339 | // Let's pull out any legacy alignments. |
||||
340 | while (preg_match('~<([A-Za-z]+)\s+[^<>]*?(align="*(left|center|right)"*)[^<>]*?(/?)>~i', $text, $matches) === 1) |
||||
341 | { |
||||
342 | // Find the position in the text of this tag over again. |
||||
343 | $start_pos = strpos($text, $matches[0]); |
||||
344 | if ($start_pos === false) |
||||
345 | break; |
||||
346 | |||||
347 | // End tag? |
||||
348 | if ($matches[4] != '/' && strpos($text, '</' . $matches[1] . '>', $start_pos) !== false) |
||||
349 | { |
||||
350 | $end_pos = strpos($text, '</' . $matches[1] . '>', $start_pos); |
||||
351 | |||||
352 | // Remove the align from that tag so it's never checked again. |
||||
353 | $tag = substr($text, $start_pos, strlen($matches[0])); |
||||
354 | $content = substr($text, $start_pos + strlen($matches[0]), $end_pos - $start_pos - strlen($matches[0])); |
||||
355 | $tag = str_replace($matches[2], '', $tag); |
||||
356 | |||||
357 | // Put the tags back into the body. |
||||
358 | $text = substr($text, 0, $start_pos) . $tag . '[' . $matches[3] . ']' . $content . '[/' . $matches[3] . ']' . substr($text, $end_pos); |
||||
359 | } |
||||
360 | else |
||||
361 | { |
||||
362 | // Just get rid of this evil tag. |
||||
363 | $text = substr($text, 0, $start_pos) . substr($text, $start_pos + strlen($matches[0])); |
||||
364 | } |
||||
365 | } |
||||
366 | |||||
367 | // Let's do some special stuff for fonts - cause we all love fonts. |
||||
368 | while (preg_match('~<font\s+([^<>]*)>~i', $text, $matches) === 1) |
||||
369 | { |
||||
370 | // Find the position of this again. |
||||
371 | $start_pos = strpos($text, $matches[0]); |
||||
372 | $end_pos = false; |
||||
373 | if ($start_pos === false) |
||||
374 | break; |
||||
375 | |||||
376 | // This must have an end tag - and we must find the right one. |
||||
377 | $lower_text = strtolower($text); |
||||
378 | |||||
379 | $start_pos_test = $start_pos + 4; |
||||
380 | // How many starting tags must we find closing ones for first? |
||||
381 | $start_font_tag_stack = 0; |
||||
382 | while ($start_pos_test < strlen($text)) |
||||
383 | { |
||||
384 | // Where is the next starting font? |
||||
385 | $next_start_pos = strpos($lower_text, '<font', $start_pos_test); |
||||
386 | $next_end_pos = strpos($lower_text, '</font>', $start_pos_test); |
||||
387 | |||||
388 | // Did we past another starting tag before an end one? |
||||
389 | if ($next_start_pos !== false && $next_start_pos < $next_end_pos) |
||||
390 | { |
||||
391 | $start_font_tag_stack++; |
||||
392 | $start_pos_test = $next_start_pos + 4; |
||||
393 | } |
||||
394 | // Otherwise we have an end tag but not the right one? |
||||
395 | elseif ($start_font_tag_stack) |
||||
396 | { |
||||
397 | $start_font_tag_stack--; |
||||
398 | $start_pos_test = $next_end_pos + 4; |
||||
399 | } |
||||
400 | // Otherwise we're there! |
||||
401 | else |
||||
402 | { |
||||
403 | $end_pos = $next_end_pos; |
||||
404 | break; |
||||
405 | } |
||||
406 | } |
||||
407 | if ($end_pos === false) |
||||
408 | break; |
||||
409 | |||||
410 | // Now work out what the attributes are. |
||||
411 | $attribs = fetchTagAttributes($matches[1]); |
||||
412 | $tags = array(); |
||||
413 | $sizes_equivalence = array(1 => '8pt', '10pt', '12pt', '14pt', '18pt', '24pt', '36pt'); |
||||
414 | foreach ($attribs as $s => $v) |
||||
415 | { |
||||
416 | if ($s == 'size') |
||||
417 | { |
||||
418 | // Cast before empty chech because casting a string results in a 0 and we don't have zeros in the array! ;) |
||||
419 | $v = (int) trim($v); |
||||
420 | $v = empty($v) ? 1 : $v; |
||||
421 | $tags[] = array('[size=' . $sizes_equivalence[$v] . ']', '[/size]'); |
||||
422 | } |
||||
423 | elseif ($s == 'face') |
||||
424 | $tags[] = array('[font=' . trim(strtolower($v)) . ']', '[/font]'); |
||||
425 | elseif ($s == 'color') |
||||
426 | $tags[] = array('[color=' . trim(strtolower($v)) . ']', '[/color]'); |
||||
427 | } |
||||
428 | |||||
429 | // As before add in our tags. |
||||
430 | $before = $after = ''; |
||||
431 | foreach ($tags as $tag) |
||||
432 | { |
||||
433 | $before .= $tag[0]; |
||||
434 | if (isset($tag[1])) |
||||
435 | $after = $tag[1] . $after; |
||||
436 | } |
||||
437 | |||||
438 | // Remove the tag so it's never checked again. |
||||
439 | $content = substr($text, $start_pos + strlen($matches[0]), $end_pos - $start_pos - strlen($matches[0])); |
||||
440 | |||||
441 | // Put the tags back into the body. |
||||
442 | $text = substr($text, 0, $start_pos) . $before . $content . $after . substr($text, $end_pos + 7); |
||||
443 | } |
||||
444 | |||||
445 | // Almost there, just a little more time. |
||||
446 | if (connection_aborted() && $context['server']['is_apache']) |
||||
447 | @apache_reset_timeout(); |
||||
448 | |||||
449 | if (count($parts = preg_split('~<(/?)(li|ol|ul)([^>]*)>~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) |
||||
450 | { |
||||
451 | // A toggle that dermines whether we're directly under a <ol> or <ul>. |
||||
452 | $inList = false; |
||||
453 | |||||
454 | // Keep track of the number of nested list levels. |
||||
455 | $listDepth = 0; |
||||
456 | |||||
457 | // Map what we can expect from the HTML to what is supported by SMF. |
||||
458 | $listTypeMapping = array( |
||||
459 | '1' => 'decimal', |
||||
460 | 'A' => 'upper-alpha', |
||||
461 | 'a' => 'lower-alpha', |
||||
462 | 'I' => 'upper-roman', |
||||
463 | 'i' => 'lower-roman', |
||||
464 | 'disc' => 'disc', |
||||
465 | 'square' => 'square', |
||||
466 | 'circle' => 'circle', |
||||
467 | ); |
||||
468 | |||||
469 | // $i: text, $i + 1: '/', $i + 2: tag, $i + 3: tail. |
||||
470 | for ($i = 0, $numParts = count($parts) - 1; $i < $numParts; $i += 4) |
||||
471 | { |
||||
472 | $tag = strtolower($parts[$i + 2]); |
||||
473 | $isOpeningTag = $parts[$i + 1] === ''; |
||||
474 | |||||
475 | if ($isOpeningTag) |
||||
476 | { |
||||
477 | switch ($tag) |
||||
478 | { |
||||
479 | case 'ol': |
||||
480 | case 'ul': |
||||
481 | |||||
482 | // We have a problem, we're already in a list. |
||||
483 | if ($inList) |
||||
484 | { |
||||
485 | // Inject a list opener, we'll deal with the ol/ul next loop. |
||||
486 | array_splice($parts, $i, 0, array( |
||||
487 | '', |
||||
488 | '', |
||||
489 | str_repeat("\t", $listDepth) . '[li]', |
||||
490 | '', |
||||
491 | )); |
||||
492 | $numParts = count($parts) - 1; |
||||
493 | |||||
494 | // The inlist status changes a bit. |
||||
495 | $inList = false; |
||||
496 | } |
||||
497 | |||||
498 | // Just starting a new list. |
||||
499 | else |
||||
500 | { |
||||
501 | $inList = true; |
||||
502 | |||||
503 | if ($tag === 'ol') |
||||
504 | $listType = 'decimal'; |
||||
505 | elseif (preg_match('~type="?(' . implode('|', array_keys($listTypeMapping)) . ')"?~', $parts[$i + 3], $match) === 1) |
||||
506 | $listType = $listTypeMapping[$match[1]]; |
||||
507 | else |
||||
508 | $listType = null; |
||||
509 | |||||
510 | $listDepth++; |
||||
511 | |||||
512 | $parts[$i + 2] = '[list' . ($listType === null ? '' : ' type=' . $listType) . ']' . "\n"; |
||||
513 | $parts[$i + 3] = ''; |
||||
514 | } |
||||
515 | break; |
||||
516 | |||||
517 | case 'li': |
||||
518 | |||||
519 | // This is how it should be: a list item inside the list. |
||||
520 | if ($inList) |
||||
521 | { |
||||
522 | $parts[$i + 2] = str_repeat("\t", $listDepth) . '[li]'; |
||||
523 | $parts[$i + 3] = ''; |
||||
524 | |||||
525 | // Within a list item, it's almost as if you're outside. |
||||
526 | $inList = false; |
||||
527 | } |
||||
528 | |||||
529 | // The li is no direct child of a list. |
||||
530 | else |
||||
531 | { |
||||
532 | // We are apparently in a list item. |
||||
533 | if ($listDepth > 0) |
||||
534 | { |
||||
535 | $parts[$i + 2] = '[/li]' . "\n" . str_repeat("\t", $listDepth) . '[li]'; |
||||
536 | $parts[$i + 3] = ''; |
||||
537 | } |
||||
538 | |||||
539 | // We're not even near a list. |
||||
540 | else |
||||
541 | { |
||||
542 | // Quickly create a list with an item. |
||||
543 | $listDepth++; |
||||
544 | |||||
545 | $parts[$i + 2] = '[list]' . "\n\t" . '[li]'; |
||||
546 | $parts[$i + 3] = ''; |
||||
547 | } |
||||
548 | } |
||||
549 | |||||
550 | break; |
||||
551 | } |
||||
552 | } |
||||
553 | |||||
554 | // Handle all the closing tags. |
||||
555 | else |
||||
556 | { |
||||
557 | switch ($tag) |
||||
558 | { |
||||
559 | case 'ol': |
||||
560 | case 'ul': |
||||
561 | |||||
562 | // As we expected it, closing the list while we're in it. |
||||
563 | if ($inList) |
||||
564 | { |
||||
565 | $inList = false; |
||||
566 | |||||
567 | $listDepth--; |
||||
568 | |||||
569 | $parts[$i + 1] = ''; |
||||
570 | $parts[$i + 2] = str_repeat("\t", $listDepth) . '[/list]'; |
||||
571 | $parts[$i + 3] = ''; |
||||
572 | } |
||||
573 | |||||
574 | else |
||||
575 | { |
||||
576 | // We're in a list item. |
||||
577 | if ($listDepth > 0) |
||||
578 | { |
||||
579 | // Inject closure for this list item first. |
||||
580 | // The content of $parts[$i] is left as is! |
||||
581 | array_splice($parts, $i + 1, 0, array( |
||||
582 | '', // $i + 1 |
||||
583 | '[/li]' . "\n", // $i + 2 |
||||
584 | '', // $i + 3 |
||||
585 | '', // $i + 4 |
||||
586 | )); |
||||
587 | $numParts = count($parts) - 1; |
||||
588 | |||||
589 | // Now that we've closed the li, we're in list space. |
||||
590 | $inList = true; |
||||
591 | } |
||||
592 | |||||
593 | // We're not even in a list, ignore |
||||
594 | else |
||||
595 | { |
||||
596 | $parts[$i + 1] = ''; |
||||
597 | $parts[$i + 2] = ''; |
||||
598 | $parts[$i + 3] = ''; |
||||
599 | } |
||||
600 | } |
||||
601 | break; |
||||
602 | |||||
603 | case 'li': |
||||
604 | |||||
605 | if ($inList) |
||||
606 | { |
||||
607 | // There's no use for a </li> after <ol> or <ul>, ignore. |
||||
608 | $parts[$i + 1] = ''; |
||||
609 | $parts[$i + 2] = ''; |
||||
610 | $parts[$i + 3] = ''; |
||||
611 | } |
||||
612 | |||||
613 | else |
||||
614 | { |
||||
615 | // Remove the trailing breaks from the list item. |
||||
616 | $parts[$i] = preg_replace('~\s*<br\s*' . '/?' . '>\s*$~', '', $parts[$i]); |
||||
617 | $parts[$i + 1] = ''; |
||||
618 | $parts[$i + 2] = '[/li]' . "\n"; |
||||
619 | $parts[$i + 3] = ''; |
||||
620 | |||||
621 | // And we're back in the [list] space. |
||||
622 | $inList = true; |
||||
623 | } |
||||
624 | |||||
625 | break; |
||||
626 | } |
||||
627 | } |
||||
628 | |||||
629 | // If we're in the [list] space, no content is allowed. |
||||
630 | if ($inList && trim(preg_replace('~\s*<br\s*' . '/?' . '>\s*~', '', $parts[$i + 4])) !== '') |
||||
631 | { |
||||
632 | // Fix it by injecting an extra list item. |
||||
633 | array_splice($parts, $i + 4, 0, array( |
||||
634 | '', // No content. |
||||
635 | '', // Opening tag. |
||||
636 | 'li', // It's a <li>. |
||||
637 | '', // No tail. |
||||
638 | )); |
||||
639 | $numParts = count($parts) - 1; |
||||
640 | } |
||||
641 | } |
||||
642 | |||||
643 | $text = implode('', $parts); |
||||
644 | |||||
645 | if ($inList) |
||||
646 | { |
||||
647 | $listDepth--; |
||||
648 | $text .= str_repeat("\t", $listDepth) . '[/list]'; |
||||
649 | } |
||||
650 | |||||
651 | for ($i = $listDepth; $i > 0; $i--) |
||||
652 | $text .= '[/li]' . "\n" . str_repeat("\t", $i - 1) . '[/list]'; |
||||
653 | } |
||||
654 | |||||
655 | // I love my own image... |
||||
656 | while (preg_match('~<img\s+([^<>]*)/*>~i', $text, $matches) === 1) |
||||
657 | { |
||||
658 | // Find the position of the image. |
||||
659 | $start_pos = strpos($text, $matches[0]); |
||||
660 | if ($start_pos === false) |
||||
661 | break; |
||||
662 | $end_pos = $start_pos + strlen($matches[0]); |
||||
663 | |||||
664 | $params = ''; |
||||
665 | $src = ''; |
||||
666 | |||||
667 | $attrs = fetchTagAttributes($matches[1]); |
||||
668 | foreach ($attrs as $attrib => $value) |
||||
669 | { |
||||
670 | if (in_array($attrib, array('width', 'height'))) |
||||
671 | $params .= ' ' . $attrib . '=' . (int) $value; |
||||
672 | elseif ($attrib == 'alt' && trim($value) != '') |
||||
673 | $params .= ' alt=' . trim($value); |
||||
674 | elseif ($attrib == 'src') |
||||
675 | $src = trim($value); |
||||
676 | } |
||||
677 | |||||
678 | $tag = ''; |
||||
679 | if (!empty($src)) |
||||
680 | { |
||||
681 | // Attempt to fix the path in case it's not present. |
||||
682 | if (preg_match('~^https?://~i', $src) === 0 && is_array($parsedURL = parse_iri($scripturl)) && isset($parsedURL['host'])) |
||||
683 | { |
||||
684 | $baseURL = (isset($parsedURL['scheme']) ? $parsedURL['scheme'] : 'http') . '://' . $parsedURL['host'] . (empty($parsedURL['port']) ? '' : ':' . $parsedURL['port']); |
||||
685 | |||||
686 | if (substr($src, 0, 1) === '/') |
||||
687 | $src = $baseURL . $src; |
||||
688 | else |
||||
689 | $src = $baseURL . (empty($parsedURL['path']) ? '/' : preg_replace('~/(?:index\\.php)?$~', '', $parsedURL['path'])) . '/' . $src; |
||||
690 | } |
||||
691 | |||||
692 | $tag = '[img' . $params . ']' . $src . '[/img]'; |
||||
693 | } |
||||
694 | |||||
695 | // Replace the tag |
||||
696 | $text = substr($text, 0, $start_pos) . $tag . substr($text, $end_pos); |
||||
697 | } |
||||
698 | |||||
699 | // The final bits are the easy ones - tags which map to tags which map to tags - etc etc. |
||||
700 | $tags = array( |
||||
701 | '~<b(\s(.)*?)*?' . '>~i' => function() |
||||
702 | { |
||||
703 | return '[b]'; |
||||
704 | }, |
||||
705 | '~</b>~i' => function() |
||||
706 | { |
||||
707 | return '[/b]'; |
||||
708 | }, |
||||
709 | '~<i(\s(.)*?)*?' . '>~i' => function() |
||||
710 | { |
||||
711 | return '[i]'; |
||||
712 | }, |
||||
713 | '~</i>~i' => function() |
||||
714 | { |
||||
715 | return '[/i]'; |
||||
716 | }, |
||||
717 | '~<u(\s(.)*?)*?' . '>~i' => function() |
||||
718 | { |
||||
719 | return '[u]'; |
||||
720 | }, |
||||
721 | '~</u>~i' => function() |
||||
722 | { |
||||
723 | return '[/u]'; |
||||
724 | }, |
||||
725 | '~<strong(\s(.)*?)*?' . '>~i' => function() |
||||
726 | { |
||||
727 | return '[b]'; |
||||
728 | }, |
||||
729 | '~</strong>~i' => function() |
||||
730 | { |
||||
731 | return '[/b]'; |
||||
732 | }, |
||||
733 | '~<em(\s(.)*?)*?' . '>~i' => function() |
||||
734 | { |
||||
735 | return '[i]'; |
||||
736 | }, |
||||
737 | '~</em>~i' => function() |
||||
738 | { |
||||
739 | return '[i]'; |
||||
740 | }, |
||||
741 | '~<s(\s(.)*?)*?' . '>~i' => function() |
||||
742 | { |
||||
743 | return "[s]"; |
||||
744 | }, |
||||
745 | '~</s>~i' => function() |
||||
746 | { |
||||
747 | return "[/s]"; |
||||
748 | }, |
||||
749 | '~<strike(\s(.)*?)*?' . '>~i' => function() |
||||
750 | { |
||||
751 | return '[s]'; |
||||
752 | }, |
||||
753 | '~</strike>~i' => function() |
||||
754 | { |
||||
755 | return '[/s]'; |
||||
756 | }, |
||||
757 | '~<del(\s(.)*?)*?' . '>~i' => function() |
||||
758 | { |
||||
759 | return '[s]'; |
||||
760 | }, |
||||
761 | '~</del>~i' => function() |
||||
762 | { |
||||
763 | return '[/s]'; |
||||
764 | }, |
||||
765 | '~<center(\s(.)*?)*?' . '>~i' => function() |
||||
766 | { |
||||
767 | return '[center]'; |
||||
768 | }, |
||||
769 | '~</center>~i' => function() |
||||
770 | { |
||||
771 | return '[/center]'; |
||||
772 | }, |
||||
773 | '~<pre(\s(.)*?)*?' . '>~i' => function() |
||||
774 | { |
||||
775 | return '[pre]'; |
||||
776 | }, |
||||
777 | '~</pre>~i' => function() |
||||
778 | { |
||||
779 | return '[/pre]'; |
||||
780 | }, |
||||
781 | '~<sub(\s(.)*?)*?' . '>~i' => function() |
||||
782 | { |
||||
783 | return '[sub]'; |
||||
784 | }, |
||||
785 | '~</sub>~i' => function() |
||||
786 | { |
||||
787 | return '[/sub]'; |
||||
788 | }, |
||||
789 | '~<sup(\s(.)*?)*?' . '>~i' => function() |
||||
790 | { |
||||
791 | return '[sup]'; |
||||
792 | }, |
||||
793 | '~</sup>~i' => function() |
||||
794 | { |
||||
795 | return '[/sup]'; |
||||
796 | }, |
||||
797 | '~<tt(\s(.)*?)*?' . '>~i' => function() |
||||
798 | { |
||||
799 | return '[tt]'; |
||||
800 | }, |
||||
801 | '~</tt>~i' => function() |
||||
802 | { |
||||
803 | return '[/tt]'; |
||||
804 | }, |
||||
805 | '~<table(\s(.)*?)*?' . '>~i' => function() |
||||
806 | { |
||||
807 | return '[table]'; |
||||
808 | }, |
||||
809 | '~</table>~i' => function() |
||||
810 | { |
||||
811 | return '[/table]'; |
||||
812 | }, |
||||
813 | '~<tr(\s(.)*?)*?' . '>~i' => function() |
||||
814 | { |
||||
815 | return '[tr]'; |
||||
816 | }, |
||||
817 | '~</tr>~i' => function() |
||||
818 | { |
||||
819 | return '[/tr]'; |
||||
820 | }, |
||||
821 | '~<(td|th)\s[^<>]*?colspan="?(\d{1,2})"?.*?' . '>~i' => function($matches) |
||||
822 | { |
||||
823 | return str_repeat('[td][/td]', $matches[2] - 1) . '[td]'; |
||||
824 | }, |
||||
825 | '~<(td|th)(\s(.)*?)*?' . '>~i' => function() |
||||
826 | { |
||||
827 | return '[td]'; |
||||
828 | }, |
||||
829 | '~</(td|th)>~i' => function() |
||||
830 | { |
||||
831 | return '[/td]'; |
||||
832 | }, |
||||
833 | '~<br(?:\s[^<>]*?)?' . '>~i' => function() |
||||
834 | { |
||||
835 | return "\n"; |
||||
836 | }, |
||||
837 | '~<hr[^<>]*>(\n)?~i' => function($matches) |
||||
838 | { |
||||
839 | return "[hr]\n" . $matches[0]; |
||||
840 | }, |
||||
841 | '~(\n)?\\[hr\\]~i' => function() |
||||
842 | { |
||||
843 | return "\n[hr]"; |
||||
844 | }, |
||||
845 | '~^\n\\[hr\\]~i' => function() |
||||
846 | { |
||||
847 | return "[hr]"; |
||||
848 | }, |
||||
849 | '~<blockquote(\s(.)*?)*?' . '>~i' => function() |
||||
850 | { |
||||
851 | return "<blockquote>"; |
||||
852 | }, |
||||
853 | '~</blockquote>~i' => function() |
||||
854 | { |
||||
855 | return "</blockquote>"; |
||||
856 | }, |
||||
857 | '~<ins(\s(.)*?)*?' . '>~i' => function() |
||||
858 | { |
||||
859 | return "<ins>"; |
||||
860 | }, |
||||
861 | '~</ins>~i' => function() |
||||
862 | { |
||||
863 | return "</ins>"; |
||||
864 | }, |
||||
865 | ); |
||||
866 | |||||
867 | foreach ($tags as $tag => $replace) |
||||
868 | $text = preg_replace_callback($tag, $replace, $text); |
||||
869 | |||||
870 | // Please give us just a little more time. |
||||
871 | if (connection_aborted() && $context['server']['is_apache']) |
||||
872 | @apache_reset_timeout(); |
||||
873 | |||||
874 | // What about URL's - the pain in the ass of the tag world. |
||||
875 | while (preg_match('~<a\s+([^<>]*)>([^<>]*)</a>~i', $text, $matches) === 1) |
||||
876 | { |
||||
877 | // Find the position of the URL. |
||||
878 | $start_pos = strpos($text, $matches[0]); |
||||
879 | if ($start_pos === false) |
||||
880 | break; |
||||
881 | $end_pos = $start_pos + strlen($matches[0]); |
||||
882 | |||||
883 | $tag_type = 'url'; |
||||
884 | $href = ''; |
||||
885 | |||||
886 | $attrs = fetchTagAttributes($matches[1]); |
||||
887 | foreach ($attrs as $attrib => $value) |
||||
888 | { |
||||
889 | if ($attrib == 'href') |
||||
890 | { |
||||
891 | $href = trim($value); |
||||
892 | |||||
893 | // Are we dealing with an FTP link? |
||||
894 | if (preg_match('~^ftps?://~', $href) === 1) |
||||
895 | $tag_type = 'ftp'; |
||||
896 | |||||
897 | // Or is this a link to an email address? |
||||
898 | elseif (substr($href, 0, 7) == 'mailto:') |
||||
899 | { |
||||
900 | $tag_type = 'email'; |
||||
901 | $href = substr($href, 7); |
||||
902 | } |
||||
903 | |||||
904 | // No http(s), so attempt to fix this potential relative URL. |
||||
905 | elseif (preg_match('~^https?://~i', $href) === 0 && is_array($parsedURL = parse_iri($scripturl)) && isset($parsedURL['host'])) |
||||
906 | { |
||||
907 | $baseURL = (isset($parsedURL['scheme']) ? $parsedURL['scheme'] : 'http') . '://' . $parsedURL['host'] . (empty($parsedURL['port']) ? '' : ':' . $parsedURL['port']); |
||||
908 | |||||
909 | if (substr($href, 0, 1) === '/') |
||||
910 | $href = $baseURL . $href; |
||||
911 | else |
||||
912 | $href = $baseURL . (empty($parsedURL['path']) ? '/' : preg_replace('~/(?:index\\.php)?$~', '', $parsedURL['path'])) . '/' . $href; |
||||
913 | } |
||||
914 | } |
||||
915 | |||||
916 | // External URL? |
||||
917 | if ($attrib == 'target' && $tag_type == 'url') |
||||
918 | { |
||||
919 | if (trim($value) == '_blank') |
||||
920 | $tag_type == 'iurl'; |
||||
921 | } |
||||
922 | } |
||||
923 | |||||
924 | $tag = ''; |
||||
925 | if ($href != '') |
||||
926 | { |
||||
927 | if ($matches[2] == $href) |
||||
928 | $tag = '[' . $tag_type . ']' . $href . '[/' . $tag_type . ']'; |
||||
929 | else |
||||
930 | $tag = '[' . $tag_type . '=' . $href . ']' . $matches[2] . '[/' . $tag_type . ']'; |
||||
931 | } |
||||
932 | |||||
933 | // Replace the tag |
||||
934 | $text = substr($text, 0, $start_pos) . $tag . substr($text, $end_pos); |
||||
935 | } |
||||
936 | |||||
937 | $text = strip_tags($text); |
||||
938 | |||||
939 | // Some tags often end up as just dummy tags - remove those. |
||||
940 | $text = preg_replace('~\[[bisu]\]\s*\[/[bisu]\]~', '', $text); |
||||
941 | |||||
942 | // Fix up entities. |
||||
943 | $text = preg_replace('~&~i', '&#38;', $text); |
||||
944 | |||||
945 | $text = legalise_bbc($text); |
||||
946 | |||||
947 | return $text; |
||||
948 | } |
||||
949 | |||||
950 | /** |
||||
951 | * Returns an array of attributes associated with a tag. |
||||
952 | * |
||||
953 | * @param string $text A tag |
||||
954 | * @return array An array of attributes |
||||
955 | */ |
||||
956 | function fetchTagAttributes($text) |
||||
957 | { |
||||
958 | $attribs = array(); |
||||
959 | $key = $value = ''; |
||||
960 | $tag_state = 0; // 0 = key, 1 = attribute with no string, 2 = attribute with string |
||||
961 | for ($i = 0; $i < strlen($text); $i++) |
||||
962 | { |
||||
963 | // We're either moving from the key to the attribute or we're in a string and this is fine. |
||||
964 | if ($text[$i] == '=') |
||||
965 | { |
||||
966 | if ($tag_state == 0) |
||||
967 | $tag_state = 1; |
||||
968 | elseif ($tag_state == 2) |
||||
969 | $value .= '='; |
||||
970 | } |
||||
971 | // A space is either moving from an attribute back to a potential key or in a string is fine. |
||||
972 | elseif ($text[$i] == ' ') |
||||
973 | { |
||||
974 | if ($tag_state == 2) |
||||
975 | $value .= ' '; |
||||
976 | elseif ($tag_state == 1) |
||||
977 | { |
||||
978 | $attribs[$key] = $value; |
||||
979 | $key = $value = ''; |
||||
980 | $tag_state = 0; |
||||
981 | } |
||||
982 | } |
||||
983 | // A quote? |
||||
984 | elseif ($text[$i] == '"') |
||||
985 | { |
||||
986 | // Must be either going into or out of a string. |
||||
987 | if ($tag_state == 1) |
||||
988 | $tag_state = 2; |
||||
989 | else |
||||
990 | $tag_state = 1; |
||||
991 | } |
||||
992 | // Otherwise it's fine. |
||||
993 | else |
||||
994 | { |
||||
995 | if ($tag_state == 0) |
||||
996 | $key .= $text[$i]; |
||||
997 | else |
||||
998 | $value .= $text[$i]; |
||||
999 | } |
||||
1000 | } |
||||
1001 | |||||
1002 | // Anything left? |
||||
1003 | if ($key != '' && $value != '') |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
1004 | $attribs[$key] = $value; |
||||
1005 | |||||
1006 | return $attribs; |
||||
1007 | } |
||||
1008 | |||||
1009 | /** |
||||
1010 | * Attempt to clean up illegal BBC caused by browsers like Opera which don't obey the rules |
||||
1011 | * |
||||
1012 | * @param string $text Text |
||||
1013 | * @return string Cleaned up text |
||||
1014 | */ |
||||
1015 | function legalise_bbc($text) |
||||
1016 | { |
||||
1017 | global $modSettings; |
||||
1018 | |||||
1019 | // Don't care about the texts that are too short. |
||||
1020 | if (strlen($text) < 3) |
||||
1021 | return $text; |
||||
1022 | |||||
1023 | // A list of tags that's disabled by the admin. |
||||
1024 | $disabled = empty($modSettings['disabledBBC']) ? array() : array_flip(explode(',', strtolower($modSettings['disabledBBC']))); |
||||
1025 | |||||
1026 | // Get a list of all the tags that are not disabled. |
||||
1027 | $all_tags = parse_bbc(false); |
||||
1028 | $valid_tags = array(); |
||||
1029 | $self_closing_tags = array(); |
||||
1030 | foreach ($all_tags as $tag) |
||||
0 ignored issues
–
show
|
|||||
1031 | { |
||||
1032 | if (!isset($disabled[$tag['tag']])) |
||||
1033 | $valid_tags[$tag['tag']] = !empty($tag['block_level']); |
||||
1034 | if (isset($tag['type']) && $tag['type'] == 'closed') |
||||
1035 | $self_closing_tags[] = $tag['tag']; |
||||
1036 | } |
||||
1037 | |||||
1038 | // Right - we're going to start by going through the whole lot to make sure we don't have align stuff crossed as this happens load and is stupid! |
||||
1039 | $align_tags = array('left', 'center', 'right', 'pre'); |
||||
1040 | |||||
1041 | // Remove those align tags that are not valid. |
||||
1042 | $align_tags = array_intersect($align_tags, array_keys($valid_tags)); |
||||
1043 | |||||
1044 | // These keep track of where we are! |
||||
1045 | if (!empty($align_tags) && count($matches = preg_split('~(\\[/?(?:' . implode('|', $align_tags) . ')\\])~', $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) |
||||
1046 | { |
||||
1047 | // The first one is never a tag. |
||||
1048 | $isTag = false; |
||||
1049 | |||||
1050 | // By default we're not inside a tag too. |
||||
1051 | $insideTag = null; |
||||
1052 | |||||
1053 | foreach ($matches as $i => $match) |
||||
1054 | { |
||||
1055 | // We're only interested in tags, not text. |
||||
1056 | if ($isTag) |
||||
1057 | { |
||||
1058 | $isClosingTag = substr($match, 1, 1) === '/'; |
||||
1059 | $tagName = substr($match, $isClosingTag ? 2 : 1, -1); |
||||
1060 | |||||
1061 | // We're closing the exact same tag that we opened. |
||||
1062 | if ($isClosingTag && $insideTag === $tagName) |
||||
1063 | $insideTag = null; |
||||
1064 | |||||
1065 | // We're opening a tag and we're not yet inside one either |
||||
1066 | elseif (!$isClosingTag && $insideTag === null) |
||||
1067 | $insideTag = $tagName; |
||||
1068 | |||||
1069 | // In all other cases, this tag must be invalid |
||||
1070 | else |
||||
1071 | unset($matches[$i]); |
||||
1072 | } |
||||
1073 | |||||
1074 | // The next one is gonna be the other one. |
||||
1075 | $isTag = !$isTag; |
||||
0 ignored issues
–
show
|
|||||
1076 | } |
||||
1077 | |||||
1078 | // We're still inside a tag and had no chance for closure? |
||||
1079 | if ($insideTag !== null) |
||||
0 ignored issues
–
show
|
|||||
1080 | $matches[] = '[/' . $insideTag . ']'; |
||||
1081 | |||||
1082 | // And a complete text string again. |
||||
1083 | $text = implode('', $matches); |
||||
1084 | } |
||||
1085 | |||||
1086 | // Quickly remove any tags which are back to back. |
||||
1087 | $backToBackPattern = '~\\[(' . implode('|', array_diff(array_keys($valid_tags), array('td', 'anchor'))) . ')[^<>\\[\\]]*\\]\s*\\[/\\1\\]~'; |
||||
1088 | $lastlen = 0; |
||||
1089 | while (strlen($text) !== $lastlen) |
||||
1090 | $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text)); |
||||
1091 | |||||
1092 | // Need to sort the tags by name length. |
||||
1093 | uksort( |
||||
1094 | $valid_tags, |
||||
1095 | function($a, $b) |
||||
1096 | { |
||||
1097 | return strlen($a) < strlen($b) ? 1 : -1; |
||||
1098 | } |
||||
1099 | ); |
||||
1100 | |||||
1101 | // These inline tags can compete with each other regarding style. |
||||
1102 | $competing_tags = array( |
||||
1103 | 'color', |
||||
1104 | 'size', |
||||
1105 | ); |
||||
1106 | |||||
1107 | // These keep track of where we are! |
||||
1108 | if (count($parts = preg_split(sprintf('~(\\[)(/?)(%1$s)((?:[\\s=][^\\]\\[]*)?\\])~', implode('|', array_keys($valid_tags))), $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) |
||||
1109 | { |
||||
1110 | // Start outside [nobbc] or [code] blocks. |
||||
1111 | $inCode = false; |
||||
1112 | $inNoBbc = false; |
||||
1113 | |||||
1114 | // A buffer containing all opened inline elements. |
||||
1115 | $inlineElements = array(); |
||||
1116 | |||||
1117 | // A buffer containing all opened block elements. |
||||
1118 | $blockElements = array(); |
||||
1119 | |||||
1120 | // A buffer containing the opened inline elements that might compete. |
||||
1121 | $competingElements = array(); |
||||
1122 | |||||
1123 | // $i: text, $i + 1: '[', $i + 2: '/', $i + 3: tag, $i + 4: tag tail. |
||||
1124 | for ($i = 0, $n = count($parts) - 1; $i < $n; $i += 5) |
||||
1125 | { |
||||
1126 | $tag = $parts[$i + 3]; |
||||
1127 | $isOpeningTag = $parts[$i + 2] === ''; |
||||
1128 | $isClosingTag = $parts[$i + 2] === '/'; |
||||
1129 | $isBlockLevelTag = isset($valid_tags[$tag]) && $valid_tags[$tag] && !in_array($tag, $self_closing_tags); |
||||
1130 | $isCompetingTag = in_array($tag, $competing_tags); |
||||
1131 | |||||
1132 | // Check if this might be one of those cleaned out tags. |
||||
1133 | if ($tag === '') |
||||
1134 | continue; |
||||
1135 | |||||
1136 | // Special case: inside [code] blocks any code is left untouched. |
||||
1137 | elseif ($tag === 'code') |
||||
1138 | { |
||||
1139 | // We're inside a code block and closing it. |
||||
1140 | if ($inCode && $isClosingTag) |
||||
1141 | { |
||||
1142 | $inCode = false; |
||||
1143 | |||||
1144 | // Reopen tags that were closed before the code block. |
||||
1145 | if (!empty($inlineElements)) |
||||
1146 | $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']'; |
||||
1147 | } |
||||
1148 | |||||
1149 | // We're outside a coding and nobbc block and opening it. |
||||
1150 | elseif (!$inCode && !$inNoBbc && $isOpeningTag) |
||||
1151 | { |
||||
1152 | // If there are still inline elements left open, close them now. |
||||
1153 | if (!empty($inlineElements)) |
||||
1154 | { |
||||
1155 | $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; |
||||
1156 | //$inlineElements = array(); |
||||
1157 | } |
||||
1158 | |||||
1159 | $inCode = true; |
||||
1160 | } |
||||
1161 | |||||
1162 | // Nothing further to do. |
||||
1163 | continue; |
||||
1164 | } |
||||
1165 | |||||
1166 | // Special case: inside [nobbc] blocks any BBC is left untouched. |
||||
1167 | elseif ($tag === 'nobbc') |
||||
1168 | { |
||||
1169 | // We're inside a nobbc block and closing it. |
||||
1170 | if ($inNoBbc && $isClosingTag) |
||||
1171 | { |
||||
1172 | $inNoBbc = false; |
||||
1173 | |||||
1174 | // Some inline elements might've been closed that need reopening. |
||||
1175 | if (!empty($inlineElements)) |
||||
1176 | $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']'; |
||||
1177 | } |
||||
1178 | |||||
1179 | // We're outside a nobbc and coding block and opening it. |
||||
1180 | elseif (!$inNoBbc && !$inCode && $isOpeningTag) |
||||
1181 | { |
||||
1182 | // Can't have inline elements still opened. |
||||
1183 | if (!empty($inlineElements)) |
||||
1184 | { |
||||
1185 | $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; |
||||
1186 | //$inlineElements = array(); |
||||
1187 | } |
||||
1188 | |||||
1189 | $inNoBbc = true; |
||||
1190 | } |
||||
1191 | |||||
1192 | continue; |
||||
1193 | } |
||||
1194 | |||||
1195 | // So, we're inside one of the special blocks: ignore any tag. |
||||
1196 | elseif ($inCode || $inNoBbc) |
||||
1197 | continue; |
||||
1198 | |||||
1199 | // We're dealing with an opening tag. |
||||
1200 | if ($isOpeningTag) |
||||
1201 | { |
||||
1202 | // Everyting inside the square brackets of the opening tag. |
||||
1203 | $elementContent = $parts[$i + 3] . substr($parts[$i + 4], 0, -1); |
||||
1204 | |||||
1205 | // A block level opening tag. |
||||
1206 | if ($isBlockLevelTag) |
||||
1207 | { |
||||
1208 | // Are there inline elements still open? |
||||
1209 | if (!empty($inlineElements)) |
||||
1210 | { |
||||
1211 | // Close all the inline tags, a block tag is coming... |
||||
1212 | $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; |
||||
1213 | |||||
1214 | // Now open them again, we're inside the block tag now. |
||||
1215 | $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5]; |
||||
1216 | } |
||||
1217 | |||||
1218 | $blockElements[] = $tag; |
||||
1219 | } |
||||
1220 | |||||
1221 | // Inline opening tag. |
||||
1222 | elseif (!in_array($tag, $self_closing_tags)) |
||||
1223 | { |
||||
1224 | // Can't have two opening elements with the same contents! |
||||
1225 | if (isset($inlineElements[$elementContent])) |
||||
1226 | { |
||||
1227 | // Get rid of this tag. |
||||
1228 | $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; |
||||
1229 | |||||
1230 | // Now try to find the corresponding closing tag. |
||||
1231 | $curLevel = 1; |
||||
1232 | for ($j = $i + 5, $m = count($parts) - 1; $j < $m; $j += 5) |
||||
1233 | { |
||||
1234 | // Find the tags with the same tagname |
||||
1235 | if ($parts[$j + 3] === $tag) |
||||
1236 | { |
||||
1237 | // If it's an opening tag, increase the level. |
||||
1238 | if ($parts[$j + 2] === '') |
||||
1239 | $curLevel++; |
||||
1240 | |||||
1241 | // A closing tag, decrease the level. |
||||
1242 | else |
||||
1243 | { |
||||
1244 | $curLevel--; |
||||
1245 | |||||
1246 | // Gotcha! Clean out this closing tag gone rogue. |
||||
1247 | if ($curLevel === 0) |
||||
1248 | { |
||||
1249 | $parts[$j + 1] = $parts[$j + 2] = $parts[$j + 3] = $parts[$j + 4] = ''; |
||||
1250 | break; |
||||
1251 | } |
||||
1252 | } |
||||
1253 | } |
||||
1254 | } |
||||
1255 | } |
||||
1256 | |||||
1257 | // Otherwise, add this one to the list. |
||||
1258 | else |
||||
1259 | { |
||||
1260 | if ($isCompetingTag) |
||||
1261 | { |
||||
1262 | if (!isset($competingElements[$tag])) |
||||
1263 | $competingElements[$tag] = array(); |
||||
1264 | |||||
1265 | $competingElements[$tag][] = $parts[$i + 4]; |
||||
1266 | |||||
1267 | if (count($competingElements[$tag]) > 1) |
||||
1268 | $parts[$i] .= '[/' . $tag . ']'; |
||||
1269 | } |
||||
1270 | |||||
1271 | $inlineElements[$elementContent] = $tag; |
||||
1272 | } |
||||
1273 | } |
||||
1274 | } |
||||
1275 | |||||
1276 | // Closing tag. |
||||
1277 | else |
||||
1278 | { |
||||
1279 | // Closing the block tag. |
||||
1280 | if ($isBlockLevelTag) |
||||
1281 | { |
||||
1282 | // Close the elements that should've been closed by closing this tag. |
||||
1283 | if (!empty($blockElements)) |
||||
1284 | { |
||||
1285 | $addClosingTags = array(); |
||||
1286 | while ($element = array_pop($blockElements)) |
||||
1287 | { |
||||
1288 | if ($element === $tag) |
||||
1289 | break; |
||||
1290 | |||||
1291 | // Still a block tag was open not equal to this tag. |
||||
1292 | $addClosingTags[] = $element['type']; |
||||
1293 | } |
||||
1294 | |||||
1295 | if (!empty($addClosingTags)) |
||||
1296 | $parts[$i + 1] = '[/' . implode('][/', array_reverse($addClosingTags)) . ']' . $parts[$i + 1]; |
||||
1297 | |||||
1298 | // Apparently the closing tag was not found on the stack. |
||||
1299 | if (!is_string($element) || $element !== $tag) |
||||
1300 | { |
||||
1301 | // Get rid of this particular closing tag, it was never opened. |
||||
1302 | $parts[$i + 1] = substr($parts[$i + 1], 0, -1); |
||||
1303 | $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; |
||||
1304 | continue; |
||||
1305 | } |
||||
1306 | } |
||||
1307 | else |
||||
1308 | { |
||||
1309 | // Get rid of this closing tag! |
||||
1310 | $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; |
||||
1311 | continue; |
||||
1312 | } |
||||
1313 | |||||
1314 | // Inline elements are still left opened? |
||||
1315 | if (!empty($inlineElements)) |
||||
1316 | { |
||||
1317 | // Close them first.. |
||||
1318 | $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; |
||||
1319 | |||||
1320 | // Then reopen them. |
||||
1321 | $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5]; |
||||
1322 | } |
||||
1323 | } |
||||
1324 | // Inline tag. |
||||
1325 | else |
||||
1326 | { |
||||
1327 | // Are we expecting this tag to end? |
||||
1328 | if (in_array($tag, $inlineElements)) |
||||
1329 | { |
||||
1330 | foreach (array_reverse($inlineElements, true) as $tagContentToBeClosed => $tagToBeClosed) |
||||
1331 | { |
||||
1332 | // Closing it one way or the other. |
||||
1333 | unset($inlineElements[$tagContentToBeClosed]); |
||||
1334 | |||||
1335 | // Was this the tag we were looking for? |
||||
1336 | if ($tagToBeClosed === $tag) |
||||
1337 | break; |
||||
1338 | |||||
1339 | // Nope, close it and look further! |
||||
1340 | else |
||||
1341 | $parts[$i] .= '[/' . $tagToBeClosed . ']'; |
||||
1342 | } |
||||
1343 | |||||
1344 | if ($isCompetingTag && !empty($competingElements[$tag])) |
||||
1345 | { |
||||
1346 | array_pop($competingElements[$tag]); |
||||
1347 | |||||
1348 | if (count($competingElements[$tag]) > 0) |
||||
1349 | $parts[$i + 5] = '[' . $tag . $competingElements[$tag][count($competingElements[$tag]) - 1] . $parts[$i + 5]; |
||||
1350 | } |
||||
1351 | } |
||||
1352 | |||||
1353 | // Unexpected closing tag, ex-ter-mi-nate. |
||||
1354 | else |
||||
1355 | $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; |
||||
1356 | } |
||||
1357 | } |
||||
1358 | } |
||||
1359 | |||||
1360 | // Close the code tags. |
||||
1361 | if ($inCode) |
||||
0 ignored issues
–
show
|
|||||
1362 | $parts[$i] .= '[/code]'; |
||||
1363 | |||||
1364 | // The same for nobbc tags. |
||||
1365 | elseif ($inNoBbc) |
||||
0 ignored issues
–
show
|
|||||
1366 | $parts[$i] .= '[/nobbc]'; |
||||
1367 | |||||
1368 | // Still inline tags left unclosed? Close them now, better late than never. |
||||
1369 | elseif (!empty($inlineElements)) |
||||
1370 | $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; |
||||
1371 | |||||
1372 | // Now close the block elements. |
||||
1373 | if (!empty($blockElements)) |
||||
1374 | $parts[$i] .= '[/' . implode('][/', array_reverse($blockElements)) . ']'; |
||||
1375 | |||||
1376 | $text = implode('', $parts); |
||||
1377 | } |
||||
1378 | |||||
1379 | // Final clean up of back to back tags. |
||||
1380 | $lastlen = 0; |
||||
1381 | while (strlen($text) !== $lastlen) |
||||
1382 | $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text)); |
||||
1383 | |||||
1384 | return $text; |
||||
1385 | } |
||||
1386 | |||||
1387 | /** |
||||
1388 | * Retrieves a list of message icons. |
||||
1389 | * - Based on the settings, the array will either contain a list of default |
||||
1390 | * message icons or a list of custom message icons retrieved from the database. |
||||
1391 | * - The board_id is needed for the custom message icons (which can be set for |
||||
1392 | * each board individually). |
||||
1393 | * |
||||
1394 | * @param int $board_id The ID of the board |
||||
1395 | * @return array An array of info about available icons |
||||
1396 | */ |
||||
1397 | function getMessageIcons($board_id) |
||||
1398 | { |
||||
1399 | global $modSettings, $txt, $settings, $smcFunc; |
||||
1400 | |||||
1401 | if (empty($modSettings['messageIcons_enable'])) |
||||
1402 | { |
||||
1403 | loadLanguage('Post'); |
||||
1404 | |||||
1405 | $icons = array( |
||||
1406 | array('value' => 'xx', 'name' => $txt['standard']), |
||||
1407 | array('value' => 'thumbup', 'name' => $txt['thumbs_up']), |
||||
1408 | array('value' => 'thumbdown', 'name' => $txt['thumbs_down']), |
||||
1409 | array('value' => 'exclamation', 'name' => $txt['exclamation_point']), |
||||
1410 | array('value' => 'question', 'name' => $txt['question_mark']), |
||||
1411 | array('value' => 'lamp', 'name' => $txt['lamp']), |
||||
1412 | array('value' => 'smiley', 'name' => $txt['icon_smiley']), |
||||
1413 | array('value' => 'angry', 'name' => $txt['icon_angry']), |
||||
1414 | array('value' => 'cheesy', 'name' => $txt['icon_cheesy']), |
||||
1415 | array('value' => 'grin', 'name' => $txt['icon_grin']), |
||||
1416 | array('value' => 'sad', 'name' => $txt['icon_sad']), |
||||
1417 | array('value' => 'wink', 'name' => $txt['icon_wink']), |
||||
1418 | array('value' => 'poll', 'name' => $txt['icon_poll']), |
||||
1419 | ); |
||||
1420 | |||||
1421 | foreach ($icons as $k => $dummy) |
||||
1422 | { |
||||
1423 | $icons[$k]['url'] = $settings['images_url'] . '/post/' . $dummy['value'] . '.png'; |
||||
1424 | $icons[$k]['is_last'] = false; |
||||
1425 | } |
||||
1426 | } |
||||
1427 | // Otherwise load the icons, and check we give the right image too... |
||||
1428 | else |
||||
1429 | { |
||||
1430 | if (($temp = cache_get_data('posting_icons-' . $board_id, 480)) == null) |
||||
1431 | { |
||||
1432 | $request = $smcFunc['db_query']('', ' |
||||
1433 | SELECT title, filename |
||||
1434 | FROM {db_prefix}message_icons |
||||
1435 | WHERE id_board IN (0, {int:board_id}) |
||||
1436 | ORDER BY icon_order', |
||||
1437 | array( |
||||
1438 | 'board_id' => $board_id, |
||||
1439 | ) |
||||
1440 | ); |
||||
1441 | $icon_data = array(); |
||||
1442 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
1443 | $icon_data[] = $row; |
||||
1444 | $smcFunc['db_free_result']($request); |
||||
1445 | |||||
1446 | $icons = array(); |
||||
1447 | foreach ($icon_data as $icon) |
||||
1448 | { |
||||
1449 | $icons[$icon['filename']] = array( |
||||
1450 | 'value' => $icon['filename'], |
||||
1451 | 'name' => $icon['title'], |
||||
1452 | 'url' => $settings[file_exists($settings['theme_dir'] . '/images/post/' . $icon['filename'] . '.png') ? 'images_url' : 'default_images_url'] . '/post/' . $icon['filename'] . '.png', |
||||
1453 | 'is_last' => false, |
||||
1454 | ); |
||||
1455 | } |
||||
1456 | |||||
1457 | cache_put_data('posting_icons-' . $board_id, $icons, 480); |
||||
1458 | } |
||||
1459 | else |
||||
1460 | $icons = $temp; |
||||
1461 | } |
||||
1462 | call_integration_hook('integrate_load_message_icons', array(&$icons)); |
||||
1463 | |||||
1464 | return array_values($icons); |
||||
1465 | } |
||||
1466 | |||||
1467 | /** |
||||
1468 | * Creates a box that can be used for richedit stuff like BBC, Smileys etc. |
||||
1469 | * |
||||
1470 | * @param array $editorOptions Various options for the editor |
||||
1471 | */ |
||||
1472 | function create_control_richedit($editorOptions) |
||||
1473 | { |
||||
1474 | global $txt, $modSettings, $options, $smcFunc, $editortxt; |
||||
1475 | global $context, $settings, $user_info, $scripturl; |
||||
1476 | |||||
1477 | // Load the Post language file... for the moment at least. |
||||
1478 | loadLanguage('Post'); |
||||
1479 | loadLanguage('Editor'); |
||||
1480 | loadLanguage('Drafts'); |
||||
1481 | |||||
1482 | $context['richedit_buttons'] = array( |
||||
1483 | 'save_draft' => array( |
||||
1484 | 'type' => 'submit', |
||||
1485 | 'value' => $txt['draft_save'], |
||||
1486 | 'onclick' => !empty($context['drafts_pm_save']) ? 'submitThisOnce(this);' : (!empty($context['drafts_save']) ? 'return confirm(' . JavaScriptEscape($txt['draft_save_note']) . ') && submitThisOnce(this);' : ''), |
||||
1487 | 'accessKey' => 'd', |
||||
1488 | 'show' => !empty($context['drafts_pm_save']) || !empty($context['drafts_save']) |
||||
1489 | ), |
||||
1490 | 'id_pm_draft' => array( |
||||
1491 | 'type' => 'hidden', |
||||
1492 | 'value' => empty($context['id_pm_draft']) ? 0 : $context['id_pm_draft'], |
||||
1493 | 'show' => !empty($context['drafts_pm_save']) |
||||
1494 | ), |
||||
1495 | 'id_draft' => array( |
||||
1496 | 'type' => 'hidden', |
||||
1497 | 'value' => empty($context['id_draft']) ? 0 : $context['id_draft'], |
||||
1498 | 'show' => !empty($context['drafts_save']) |
||||
1499 | ), |
||||
1500 | 'spell_check' => array( |
||||
1501 | 'type' => 'submit', |
||||
1502 | 'value' => $txt['spell_check'], |
||||
1503 | 'show' => !empty($context['show_spellchecking']) |
||||
1504 | ), |
||||
1505 | 'preview' => array( |
||||
1506 | 'type' => 'submit', |
||||
1507 | 'value' => $txt['preview'], |
||||
1508 | 'accessKey' => 'p' |
||||
1509 | ) |
||||
1510 | ); |
||||
1511 | |||||
1512 | // Every control must have a ID! |
||||
1513 | assert(isset($editorOptions['id'])); |
||||
1514 | assert(isset($editorOptions['value'])); |
||||
1515 | |||||
1516 | // Is this the first richedit - if so we need to ensure some template stuff is initialised. |
||||
1517 | if (empty($context['controls']['richedit'])) |
||||
1518 | { |
||||
1519 | // Some general stuff. |
||||
1520 | $settings['smileys_url'] = $modSettings['smileys_url'] . '/' . $user_info['smiley_set']; |
||||
1521 | if (!empty($context['drafts_autosave'])) |
||||
1522 | $context['drafts_autosave_frequency'] = empty($modSettings['drafts_autosave_frequency']) ? 60000 : $modSettings['drafts_autosave_frequency'] * 1000; |
||||
1523 | |||||
1524 | // This really has some WYSIWYG stuff. |
||||
1525 | loadCSSFile('jquery.sceditor.css', array('default_theme' => true, 'validate' => true), 'smf_jquery_sceditor'); |
||||
1526 | loadTemplate('GenericControls'); |
||||
1527 | |||||
1528 | /* |
||||
1529 | * THEME AUTHORS: |
||||
1530 | If you want to change or tweak the CSS for the editor, |
||||
1531 | include a file named 'jquery.sceditor.theme.css' in your theme. |
||||
1532 | */ |
||||
1533 | loadCSSFile('jquery.sceditor.theme.css', array('force_current' => true, 'validate' => true,), 'smf_jquery_sceditor_theme'); |
||||
1534 | |||||
1535 | // JS makes the editor go round |
||||
1536 | loadJavaScriptFile('editor.js', array('minimize' => true), 'smf_editor'); |
||||
1537 | loadJavaScriptFile('jquery.sceditor.bbcode.min.js', array(), 'smf_sceditor_bbcode'); |
||||
1538 | loadJavaScriptFile('jquery.sceditor.smf.js', array('minimize' => true), 'smf_sceditor_smf'); |
||||
1539 | |||||
1540 | $scExtraLangs = ' |
||||
1541 | $.sceditor.locale["' . $txt['lang_dictionary'] . '"] = { |
||||
1542 | "Width (optional):": "' . $editortxt['width'] . '", |
||||
1543 | "Height (optional):": "' . $editortxt['height'] . '", |
||||
1544 | "Insert": "' . $editortxt['insert'] . '", |
||||
1545 | "Description (optional):": "' . $editortxt['description'] . '", |
||||
1546 | "Rows:": "' . $editortxt['rows'] . '", |
||||
1547 | "Cols:": "' . $editortxt['cols'] . '", |
||||
1548 | "URL:": "' . $editortxt['url'] . '", |
||||
1549 | "E-mail:": "' . $editortxt['email'] . '", |
||||
1550 | "Video URL:": "' . $editortxt['video_url'] . '", |
||||
1551 | "More": "' . $editortxt['more'] . '", |
||||
1552 | "Close": "' . $editortxt['close'] . '", |
||||
1553 | dateFormat: "' . $editortxt['dateformat'] . '" |
||||
1554 | };'; |
||||
1555 | |||||
1556 | addInlineJavaScript($scExtraLangs, true); |
||||
1557 | |||||
1558 | addInlineJavaScript(' |
||||
1559 | var smf_smileys_url = \'' . $settings['smileys_url'] . '\'; |
||||
1560 | var bbc_quote_from = \'' . addcslashes($txt['quote_from'], "'") . '\'; |
||||
1561 | var bbc_quote = \'' . addcslashes($txt['quote'], "'") . '\'; |
||||
1562 | var bbc_search_on = \'' . addcslashes($txt['search_on'], "'") . '\';'); |
||||
1563 | |||||
1564 | $context['shortcuts_text'] = $txt['shortcuts' . (!empty($context['drafts_save']) ? '_drafts' : '') . (stripos($_SERVER['HTTP_USER_AGENT'], 'Macintosh') !== false ? '_mac' : (isBrowser('is_firefox') ? '_firefox' : ''))]; |
||||
1565 | |||||
1566 | if ($context['show_spellchecking']) |
||||
1567 | { |
||||
1568 | loadJavaScriptFile('spellcheck.js', array('minimize' => true), 'smf_spellcheck'); |
||||
1569 | |||||
1570 | // Some hidden information is needed in order to make the spell checking work. |
||||
1571 | if (!isset($_REQUEST['xml'])) |
||||
1572 | $context['insert_after_template'] .= ' |
||||
1573 | <form name="spell_form" id="spell_form" method="post" accept-charset="' . $context['character_set'] . '" target="spellWindow" action="' . $scripturl . '?action=spellcheck"> |
||||
1574 | <input type="hidden" name="spellstring" value=""> |
||||
1575 | </form>'; |
||||
1576 | } |
||||
1577 | } |
||||
1578 | |||||
1579 | // The [#] item code for creating list items causes issues with SCEditor, but [+] is a safe equivalent. |
||||
1580 | $editorOptions['value'] = str_replace('[#]', '[+]', $editorOptions['value']); |
||||
1581 | // Tabs are not shown in the SCEditor, replace with spaces. |
||||
1582 | $editorOptions['value'] = str_replace("\t", ' ', $editorOptions['value']); |
||||
1583 | |||||
1584 | // Start off the editor... |
||||
1585 | $context['controls']['richedit'][$editorOptions['id']] = array( |
||||
1586 | 'id' => $editorOptions['id'], |
||||
1587 | 'value' => $editorOptions['value'], |
||||
1588 | 'rich_value' => $editorOptions['value'], // 2.0 editor compatibility |
||||
1589 | 'rich_active' => empty($modSettings['disable_wysiwyg']) && (!empty($options['wysiwyg_default']) || !empty($editorOptions['force_rich']) || !empty($_REQUEST[$editorOptions['id'] . '_mode'])), |
||||
1590 | 'disable_smiley_box' => !empty($editorOptions['disable_smiley_box']), |
||||
1591 | 'columns' => isset($editorOptions['columns']) ? $editorOptions['columns'] : 60, |
||||
1592 | 'rows' => isset($editorOptions['rows']) ? $editorOptions['rows'] : 18, |
||||
1593 | 'width' => isset($editorOptions['width']) ? $editorOptions['width'] : '70%', |
||||
1594 | 'height' => isset($editorOptions['height']) ? $editorOptions['height'] : '175px', |
||||
1595 | 'form' => isset($editorOptions['form']) ? $editorOptions['form'] : 'postmodify', |
||||
1596 | 'bbc_level' => !empty($editorOptions['bbc_level']) ? $editorOptions['bbc_level'] : 'full', |
||||
1597 | 'preview_type' => isset($editorOptions['preview_type']) ? (int) $editorOptions['preview_type'] : 1, |
||||
1598 | 'labels' => !empty($editorOptions['labels']) ? $editorOptions['labels'] : array(), |
||||
1599 | 'locale' => !empty($txt['lang_dictionary']) && $txt['lang_dictionary'] != 'en' ? $txt['lang_dictionary'] : '', |
||||
1600 | 'required' => !empty($editorOptions['required']), |
||||
1601 | ); |
||||
1602 | |||||
1603 | if (empty($context['bbc_tags'])) |
||||
1604 | { |
||||
1605 | // The below array makes it dead easy to add images to this control. Add it to the array and everything else is done for you! |
||||
1606 | // Note: 'before' and 'after' are deprecated as of SMF 2.1. Instead, use a separate JS file to configure the functionality of your toolbar buttons. |
||||
1607 | /* |
||||
1608 | array( |
||||
1609 | 'code' => 'b', // Required |
||||
1610 | 'description' => $editortxt['bold'], // Required |
||||
1611 | 'image' => 'bold', // Optional |
||||
1612 | 'before' => '[b]', // Deprecated |
||||
1613 | 'after' => '[/b]', // Deprecated |
||||
1614 | ), |
||||
1615 | */ |
||||
1616 | $context['bbc_tags'] = array(); |
||||
1617 | $context['bbc_tags'][] = array( |
||||
1618 | array( |
||||
1619 | 'code' => 'bold', |
||||
1620 | 'description' => $editortxt['bold'], |
||||
1621 | ), |
||||
1622 | array( |
||||
1623 | 'code' => 'italic', |
||||
1624 | 'description' => $editortxt['italic'], |
||||
1625 | ), |
||||
1626 | array( |
||||
1627 | 'code' => 'underline', |
||||
1628 | 'description' => $editortxt['underline'] |
||||
1629 | ), |
||||
1630 | array( |
||||
1631 | 'code' => 'strike', |
||||
1632 | 'description' => $editortxt['strikethrough'] |
||||
1633 | ), |
||||
1634 | array( |
||||
1635 | 'code' => 'superscript', |
||||
1636 | 'description' => $editortxt['superscript'] |
||||
1637 | ), |
||||
1638 | array( |
||||
1639 | 'code' => 'subscript', |
||||
1640 | 'description' => $editortxt['subscript'] |
||||
1641 | ), |
||||
1642 | array(), |
||||
1643 | array( |
||||
1644 | 'code' => 'pre', |
||||
1645 | 'description' => $editortxt['preformatted_text'] |
||||
1646 | ), |
||||
1647 | array( |
||||
1648 | 'code' => 'left', |
||||
1649 | 'description' => $editortxt['align_left'] |
||||
1650 | ), |
||||
1651 | array( |
||||
1652 | 'code' => 'center', |
||||
1653 | 'description' => $editortxt['center'] |
||||
1654 | ), |
||||
1655 | array( |
||||
1656 | 'code' => 'right', |
||||
1657 | 'description' => $editortxt['align_right'] |
||||
1658 | ), |
||||
1659 | array( |
||||
1660 | 'code' => 'justify', |
||||
1661 | 'description' => $editortxt['justify'] |
||||
1662 | ), |
||||
1663 | array(), |
||||
1664 | array( |
||||
1665 | 'code' => 'font', |
||||
1666 | 'description' => $editortxt['font_name'] |
||||
1667 | ), |
||||
1668 | array( |
||||
1669 | 'code' => 'size', |
||||
1670 | 'description' => $editortxt['font_size'] |
||||
1671 | ), |
||||
1672 | array( |
||||
1673 | 'code' => 'color', |
||||
1674 | 'description' => $editortxt['font_color'] |
||||
1675 | ), |
||||
1676 | ); |
||||
1677 | if (empty($modSettings['disable_wysiwyg'])) |
||||
1678 | { |
||||
1679 | $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array( |
||||
1680 | 'code' => 'removeformat', |
||||
1681 | 'description' => $editortxt['remove_formatting'], |
||||
1682 | ); |
||||
1683 | } |
||||
1684 | $context['bbc_tags'][] = array( |
||||
1685 | array( |
||||
1686 | 'code' => 'floatleft', |
||||
1687 | 'description' => $editortxt['float_left'] |
||||
1688 | ), |
||||
1689 | array( |
||||
1690 | 'code' => 'floatright', |
||||
1691 | 'description' => $editortxt['float_right'] |
||||
1692 | ), |
||||
1693 | array(), |
||||
1694 | array( |
||||
1695 | 'code' => 'youtube', |
||||
1696 | 'description' => $editortxt['insert_youtube_video'] |
||||
1697 | ), |
||||
1698 | array( |
||||
1699 | 'code' => 'image', |
||||
1700 | 'description' => $editortxt['insert_image'] |
||||
1701 | ), |
||||
1702 | array( |
||||
1703 | 'code' => 'link', |
||||
1704 | 'description' => $editortxt['insert_link'] |
||||
1705 | ), |
||||
1706 | array( |
||||
1707 | 'code' => 'email', |
||||
1708 | 'description' => $editortxt['insert_email'] |
||||
1709 | ), |
||||
1710 | array(), |
||||
1711 | array( |
||||
1712 | 'code' => 'table', |
||||
1713 | 'description' => $editortxt['insert_table'] |
||||
1714 | ), |
||||
1715 | array( |
||||
1716 | 'code' => 'code', |
||||
1717 | 'description' => $editortxt['code'] |
||||
1718 | ), |
||||
1719 | array( |
||||
1720 | 'code' => 'quote', |
||||
1721 | 'description' => $editortxt['insert_quote'] |
||||
1722 | ), |
||||
1723 | array(), |
||||
1724 | array( |
||||
1725 | 'code' => 'bulletlist', |
||||
1726 | 'description' => $editortxt['bullet_list'] |
||||
1727 | ), |
||||
1728 | array( |
||||
1729 | 'code' => 'orderedlist', |
||||
1730 | 'description' => $editortxt['numbered_list'] |
||||
1731 | ), |
||||
1732 | array( |
||||
1733 | 'code' => 'horizontalrule', |
||||
1734 | 'description' => $editortxt['insert_horizontal_rule'] |
||||
1735 | ), |
||||
1736 | array(), |
||||
1737 | array( |
||||
1738 | 'code' => 'maximize', |
||||
1739 | 'description' => $editortxt['maximize'] |
||||
1740 | ), |
||||
1741 | ); |
||||
1742 | if (empty($modSettings['disable_wysiwyg'])) |
||||
1743 | { |
||||
1744 | $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array( |
||||
1745 | 'code' => 'source', |
||||
1746 | 'description' => $editortxt['view_source'], |
||||
1747 | ); |
||||
1748 | } |
||||
1749 | |||||
1750 | $editor_tag_map = array( |
||||
1751 | 'b' => 'bold', |
||||
1752 | 'i' => 'italic', |
||||
1753 | 'u' => 'underline', |
||||
1754 | 's' => 'strike', |
||||
1755 | 'img' => 'image', |
||||
1756 | 'url' => 'link', |
||||
1757 | 'sup' => 'superscript', |
||||
1758 | 'sub' => 'subscript', |
||||
1759 | 'hr' => 'horizontalrule', |
||||
1760 | ); |
||||
1761 | |||||
1762 | // Define this here so mods can add to it via the hook. |
||||
1763 | $context['disabled_tags'] = array(); |
||||
1764 | |||||
1765 | // Allow mods to modify BBC buttons. |
||||
1766 | // Note: passing the array here is not necessary and is deprecated, but it is kept for backward compatibility with 2.0 |
||||
1767 | call_integration_hook('integrate_bbc_buttons', array(&$context['bbc_tags'], &$editor_tag_map)); |
||||
1768 | |||||
1769 | // Generate a list of buttons that shouldn't be shown - this should be the fastest way to do this. |
||||
1770 | $disabled_tags = array(); |
||||
1771 | if (!empty($modSettings['disabledBBC'])) |
||||
1772 | $disabled_tags = explode(',', $modSettings['disabledBBC']); |
||||
1773 | |||||
1774 | foreach ($disabled_tags as $tag) |
||||
1775 | { |
||||
1776 | $tag = trim($tag); |
||||
1777 | |||||
1778 | if ($tag === 'list') |
||||
1779 | { |
||||
1780 | $context['disabled_tags']['bulletlist'] = true; |
||||
1781 | $context['disabled_tags']['orderedlist'] = true; |
||||
1782 | } |
||||
1783 | |||||
1784 | if ($tag === 'float') |
||||
1785 | { |
||||
1786 | $context['disabled_tags']['floatleft'] = true; |
||||
1787 | $context['disabled_tags']['floatright'] = true; |
||||
1788 | } |
||||
1789 | |||||
1790 | foreach ($editor_tag_map as $thisTag => $tagNameBBC) |
||||
1791 | if ($tag === $thisTag) |
||||
1792 | $context['disabled_tags'][$tagNameBBC] = true; |
||||
1793 | |||||
1794 | $context['disabled_tags'][$tag] = true; |
||||
1795 | } |
||||
1796 | |||||
1797 | $bbcodes_styles = ''; |
||||
1798 | $context['bbcodes_handlers'] = ''; |
||||
1799 | $context['bbc_toolbar'] = array(); |
||||
1800 | |||||
1801 | foreach ($context['bbc_tags'] as $row => $tagRow) |
||||
1802 | { |
||||
1803 | if (!isset($context['bbc_toolbar'][$row])) |
||||
1804 | $context['bbc_toolbar'][$row] = array(); |
||||
1805 | |||||
1806 | $tagsRow = array(); |
||||
1807 | |||||
1808 | foreach ($tagRow as $tag) |
||||
1809 | { |
||||
1810 | if (empty($tag['code'])) |
||||
1811 | { |
||||
1812 | $context['bbc_toolbar'][$row][] = implode(',', $tagsRow); |
||||
1813 | $tagsRow = array(); |
||||
1814 | } |
||||
1815 | elseif (empty($context['disabled_tags'][$tag['code']])) |
||||
1816 | { |
||||
1817 | $tagsRow[] = $tag['code']; |
||||
1818 | |||||
1819 | // If we have a custom button image, set it now. |
||||
1820 | if (isset($tag['image'])) |
||||
1821 | { |
||||
1822 | $bbcodes_styles .= ' |
||||
1823 | .sceditor-button-' . $tag['code'] . ' div { |
||||
1824 | background: url(\'' . $settings['default_theme_url'] . '/images/bbc/' . $tag['image'] . '.png\'); |
||||
1825 | }'; |
||||
1826 | } |
||||
1827 | |||||
1828 | // Set the tooltip and possibly the command info |
||||
1829 | $context['bbcodes_handlers'] .= ' |
||||
1830 | sceditor.command.set(' . JavaScriptEscape($tag['code']) . ', { |
||||
1831 | tooltip: ' . JavaScriptEscape(isset($tag['description']) ? $tag['description'] : $tag['code']); |
||||
1832 | |||||
1833 | // Legacy support for 2.0 BBC mods |
||||
1834 | if (isset($tag['before'])) |
||||
1835 | { |
||||
1836 | $context['bbcodes_handlers'] .= ', |
||||
1837 | exec: function () { |
||||
1838 | this.insertText(' . JavaScriptEscape($tag['before']) . (isset($tag['after']) ? ', ' . JavaScriptEscape($tag['after']) : '') . '); |
||||
1839 | }, |
||||
1840 | txtExec: [' . JavaScriptEscape($tag['before']) . (isset($tag['after']) ? ', ' . JavaScriptEscape($tag['after']) : '') . ']'; |
||||
1841 | } |
||||
1842 | |||||
1843 | $context['bbcodes_handlers'] .= ' |
||||
1844 | });'; |
||||
1845 | } |
||||
1846 | } |
||||
1847 | |||||
1848 | if (!empty($tagsRow)) |
||||
1849 | $context['bbc_toolbar'][$row][] = implode(',', $tagsRow); |
||||
1850 | } |
||||
1851 | |||||
1852 | if (!empty($bbcodes_styles)) |
||||
1853 | addInlineCss($bbcodes_styles); |
||||
1854 | } |
||||
1855 | |||||
1856 | // Initialize smiley array... if not loaded before. |
||||
1857 | if (empty($context['smileys']) && empty($editorOptions['disable_smiley_box'])) |
||||
1858 | { |
||||
1859 | $context['smileys'] = array( |
||||
1860 | 'postform' => array(), |
||||
1861 | 'popup' => array(), |
||||
1862 | ); |
||||
1863 | |||||
1864 | if ($user_info['smiley_set'] != 'none') |
||||
1865 | { |
||||
1866 | // Cache for longer when customized smiley codes aren't enabled |
||||
1867 | $cache_time = empty($modSettings['smiley_enable']) ? 7200 : 480; |
||||
1868 | |||||
1869 | if (($temp = cache_get_data('posting_smileys_' . $user_info['smiley_set'], $cache_time)) == null) |
||||
1870 | { |
||||
1871 | $request = $smcFunc['db_query']('', ' |
||||
1872 | SELECT s.code, f.filename, s.description, s.smiley_row, s.hidden |
||||
1873 | FROM {db_prefix}smileys AS s |
||||
1874 | JOIN {db_prefix}smiley_files AS f ON (s.id_smiley = f.id_smiley) |
||||
1875 | WHERE s.hidden IN (0, 2) |
||||
1876 | AND f.smiley_set = {string:smiley_set}' . (empty($modSettings['smiley_enable']) ? ' |
||||
1877 | AND s.code IN ({array_string:default_codes})' : '') . ' |
||||
1878 | ORDER BY s.smiley_row, s.smiley_order', |
||||
1879 | array( |
||||
1880 | 'default_codes' => array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', 'O:-)'), |
||||
1881 | 'smiley_set' => $user_info['smiley_set'], |
||||
1882 | ) |
||||
1883 | ); |
||||
1884 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
1885 | { |
||||
1886 | $row['description'] = !empty($txt['icon_' . strtolower($row['description'])]) ? $smcFunc['htmlspecialchars']($txt['icon_' . strtolower($row['description'])]) : $smcFunc['htmlspecialchars']($row['description']); |
||||
1887 | |||||
1888 | $context['smileys'][empty($row['hidden']) ? 'postform' : 'popup'][$row['smiley_row']]['smileys'][] = $row; |
||||
1889 | } |
||||
1890 | $smcFunc['db_free_result']($request); |
||||
1891 | |||||
1892 | foreach ($context['smileys'] as $section => $smileyRows) |
||||
1893 | { |
||||
1894 | foreach ($smileyRows as $rowIndex => $smileys) |
||||
1895 | $context['smileys'][$section][$rowIndex]['smileys'][count($smileys['smileys']) - 1]['isLast'] = true; |
||||
1896 | |||||
1897 | if (!empty($smileyRows)) |
||||
1898 | $context['smileys'][$section][count($smileyRows) - 1]['isLast'] = true; |
||||
1899 | } |
||||
1900 | |||||
1901 | cache_put_data('posting_smileys_' . $user_info['smiley_set'], $context['smileys'], $cache_time); |
||||
1902 | } |
||||
1903 | else |
||||
1904 | $context['smileys'] = $temp; |
||||
1905 | } |
||||
1906 | } |
||||
1907 | |||||
1908 | // Set up the SCEditor options |
||||
1909 | $sce_options = array( |
||||
1910 | 'width' => isset($editorOptions['width']) ? $editorOptions['width'] : '100%', |
||||
1911 | 'height' => isset($editorOptions['height']) ? $editorOptions['height'] : '175px', |
||||
1912 | 'style' => $settings[file_exists($settings['theme_dir'] . '/css/jquery.sceditor.default.css') ? 'theme_url' : 'default_theme_url'] . '/css/jquery.sceditor.default.css' . $context['browser_cache'], |
||||
1913 | 'emoticonsCompat' => true, |
||||
1914 | 'colors' => 'black,maroon,brown,green,navy,grey,red,orange,teal,blue,white,hotpink,yellow,limegreen,purple', |
||||
1915 | 'format' => 'bbcode', |
||||
1916 | 'plugins' => '', |
||||
1917 | 'bbcodeTrim' => false, |
||||
1918 | ); |
||||
1919 | if (!empty($context['controls']['richedit'][$editorOptions['id']]['locale'])) |
||||
1920 | $sce_options['locale'] = $context['controls']['richedit'][$editorOptions['id']]['locale']; |
||||
1921 | if (!empty($context['right_to_left'])) |
||||
1922 | $sce_options['rtl'] = true; |
||||
1923 | if ($editorOptions['id'] != 'quickReply') |
||||
1924 | $sce_options['autofocus'] = true; |
||||
1925 | |||||
1926 | $sce_options['emoticons'] = array(); |
||||
1927 | $sce_options['emoticonsDescriptions'] = array(); |
||||
1928 | $sce_options['emoticonsEnabled'] = false; |
||||
1929 | if ((!empty($context['smileys']['postform']) || !empty($context['smileys']['popup'])) && !$context['controls']['richedit'][$editorOptions['id']]['disable_smiley_box']) |
||||
1930 | { |
||||
1931 | $sce_options['emoticonsEnabled'] = true; |
||||
1932 | $sce_options['emoticons']['dropdown'] = array(); |
||||
1933 | $sce_options['emoticons']['popup'] = array(); |
||||
1934 | |||||
1935 | $countLocations = count($context['smileys']); |
||||
1936 | foreach ($context['smileys'] as $location => $smileyRows) |
||||
1937 | { |
||||
1938 | $countLocations--; |
||||
1939 | |||||
1940 | unset($smiley_location); |
||||
1941 | if ($location == 'postform') |
||||
1942 | $smiley_location = &$sce_options['emoticons']['dropdown']; |
||||
1943 | elseif ($location == 'popup') |
||||
1944 | $smiley_location = &$sce_options['emoticons']['popup']; |
||||
1945 | |||||
1946 | $numRows = count($smileyRows); |
||||
1947 | |||||
1948 | // This is needed because otherwise the editor will remove all the duplicate (empty) keys and leave only 1 additional line |
||||
1949 | $emptyPlaceholder = 0; |
||||
1950 | foreach ($smileyRows as $smileyRow) |
||||
1951 | { |
||||
1952 | foreach ($smileyRow['smileys'] as $smiley) |
||||
1953 | { |
||||
1954 | $smiley_location[$smiley['code']] = $settings['smileys_url'] . '/' . $smiley['filename']; |
||||
1955 | $sce_options['emoticonsDescriptions'][$smiley['code']] = $smiley['description']; |
||||
1956 | } |
||||
1957 | |||||
1958 | if (empty($smileyRow['isLast']) && $numRows != 1) |
||||
1959 | $smiley_location['-' . $emptyPlaceholder++] = ''; |
||||
1960 | } |
||||
1961 | } |
||||
1962 | } |
||||
1963 | |||||
1964 | $sce_options['toolbar'] = ''; |
||||
1965 | $sce_options['parserOptions']['txtVars'] = [ |
||||
1966 | 'code' => $txt['code'] |
||||
1967 | ]; |
||||
1968 | if (!empty($modSettings['enableBBC'])) |
||||
1969 | { |
||||
1970 | $count_tags = count($context['bbc_tags']); |
||||
1971 | foreach ($context['bbc_toolbar'] as $i => $buttonRow) |
||||
1972 | { |
||||
1973 | $sce_options['toolbar'] .= implode('|', $buttonRow); |
||||
1974 | |||||
1975 | $count_tags--; |
||||
1976 | |||||
1977 | if (!empty($count_tags)) |
||||
1978 | $sce_options['toolbar'] .= '||'; |
||||
1979 | } |
||||
1980 | } |
||||
1981 | |||||
1982 | // Allow mods to change $sce_options. Usful if, e.g., a mod wants to add an SCEditor plugin. |
||||
1983 | call_integration_hook('integrate_sceditor_options', array(&$sce_options)); |
||||
1984 | |||||
1985 | $context['controls']['richedit'][$editorOptions['id']]['sce_options'] = $sce_options; |
||||
1986 | } |
||||
1987 | |||||
1988 | /** |
||||
1989 | * Create a anti-bot verification control? |
||||
1990 | * |
||||
1991 | * @param array &$verificationOptions Options for the verification control |
||||
1992 | * @param bool $do_test Whether to check to see if the user entered the code correctly |
||||
1993 | * @return bool|array False if there's nothing to show, true if everything went well or an array containing error indicators if the test failed |
||||
1994 | */ |
||||
1995 | function create_control_verification(&$verificationOptions, $do_test = false) |
||||
1996 | { |
||||
1997 | global $modSettings, $smcFunc; |
||||
1998 | global $context, $user_info, $scripturl, $language; |
||||
1999 | |||||
2000 | // First verification means we need to set up some bits... |
||||
2001 | if (empty($context['controls']['verification'])) |
||||
2002 | { |
||||
2003 | // The template |
||||
2004 | loadTemplate('GenericControls'); |
||||
2005 | |||||
2006 | // Some javascript ma'am? |
||||
2007 | if (!empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual']))) |
||||
2008 | loadJavaScriptFile('captcha.js', array('minimize' => true), 'smf_captcha'); |
||||
2009 | |||||
2010 | $context['use_graphic_library'] = in_array('gd', get_loaded_extensions()); |
||||
2011 | |||||
2012 | // Skip I, J, L, O, Q, S and Z. |
||||
2013 | $context['standard_captcha_range'] = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y')); |
||||
2014 | } |
||||
2015 | |||||
2016 | // Always have an ID. |
||||
2017 | assert(isset($verificationOptions['id'])); |
||||
2018 | $isNew = !isset($context['controls']['verification'][$verificationOptions['id']]); |
||||
2019 | |||||
2020 | // Log this into our collection. |
||||
2021 | if ($isNew) |
||||
2022 | $context['controls']['verification'][$verificationOptions['id']] = array( |
||||
2023 | 'id' => $verificationOptions['id'], |
||||
2024 | 'empty_field' => empty($verificationOptions['no_empty_field']), |
||||
2025 | 'show_visual' => !empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual'])), |
||||
2026 | 'number_questions' => isset($verificationOptions['override_qs']) ? $verificationOptions['override_qs'] : (!empty($modSettings['qa_verification_number']) ? $modSettings['qa_verification_number'] : 0), |
||||
2027 | 'max_errors' => isset($verificationOptions['max_errors']) ? $verificationOptions['max_errors'] : 3, |
||||
2028 | 'image_href' => $scripturl . '?action=verificationcode;vid=' . $verificationOptions['id'] . ';rand=' . md5(mt_rand()), |
||||
2029 | 'text_value' => '', |
||||
2030 | 'questions' => array(), |
||||
2031 | 'can_recaptcha' => !empty($modSettings['recaptcha_enabled']) && !empty($modSettings['recaptcha_site_key']) && !empty($modSettings['recaptcha_secret_key']), |
||||
2032 | ); |
||||
2033 | $thisVerification = &$context['controls']['verification'][$verificationOptions['id']]; |
||||
2034 | |||||
2035 | // Add a verification hook, presetup. |
||||
2036 | call_integration_hook('integrate_create_control_verification_pre', array(&$verificationOptions, $do_test)); |
||||
2037 | |||||
2038 | // Is there actually going to be anything? |
||||
2039 | if (empty($thisVerification['show_visual']) && empty($thisVerification['number_questions']) && empty($thisVerification['can_recaptcha'])) |
||||
2040 | return false; |
||||
2041 | elseif (!$isNew && !$do_test) |
||||
2042 | return true; |
||||
2043 | |||||
2044 | // Sanitize reCAPTCHA fields? |
||||
2045 | if ($thisVerification['can_recaptcha']) |
||||
2046 | { |
||||
2047 | // Only allow 40 alphanumeric, underscore and dash characters. |
||||
2048 | $thisVerification['recaptcha_site_key'] = preg_replace('/(0-9a-zA-Z_){40}/', '$1', $modSettings['recaptcha_site_key']); |
||||
2049 | |||||
2050 | // Light or dark theme... |
||||
2051 | $thisVerification['recaptcha_theme'] = preg_replace('/(light|dark)/', '$1', $modSettings['recaptcha_theme']); |
||||
2052 | } |
||||
2053 | |||||
2054 | // Add javascript for the object. |
||||
2055 | if ($context['controls']['verification'][$verificationOptions['id']]['show_visual']) |
||||
2056 | $context['insert_after_template'] .= ' |
||||
2057 | <script> |
||||
2058 | var verification' . $verificationOptions['id'] . 'Handle = new smfCaptcha("' . $thisVerification['image_href'] . '", "' . $verificationOptions['id'] . '", ' . ($context['use_graphic_library'] ? 1 : 0) . '); |
||||
2059 | </script>'; |
||||
2060 | |||||
2061 | // If we want questions do we have a cache of all the IDs? |
||||
2062 | if (!empty($thisVerification['number_questions']) && empty($modSettings['question_id_cache'])) |
||||
2063 | { |
||||
2064 | if (($modSettings['question_id_cache'] = cache_get_data('verificationQuestions', 300)) == null) |
||||
2065 | { |
||||
2066 | $request = $smcFunc['db_query']('', ' |
||||
2067 | SELECT id_question, lngfile, question, answers |
||||
2068 | FROM {db_prefix}qanda', |
||||
2069 | array() |
||||
2070 | ); |
||||
2071 | $modSettings['question_id_cache'] = array( |
||||
2072 | 'questions' => array(), |
||||
2073 | 'langs' => array(), |
||||
2074 | ); |
||||
2075 | // This is like Captain Kirk climbing a mountain in some ways. This is L's fault, mkay? :P |
||||
2076 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
2077 | { |
||||
2078 | $id_question = $row['id_question']; |
||||
2079 | unset ($row['id_question']); |
||||
2080 | // Make them all lowercase. We can't directly use $smcFunc['strtolower'] with array_walk, so do it manually, eh? |
||||
2081 | $row['answers'] = (array) $smcFunc['json_decode']($row['answers'], true); |
||||
2082 | foreach ($row['answers'] as $k => $v) |
||||
2083 | $row['answers'][$k] = $smcFunc['strtolower']($v); |
||||
2084 | |||||
2085 | $modSettings['question_id_cache']['questions'][$id_question] = $row; |
||||
2086 | $modSettings['question_id_cache']['langs'][$row['lngfile']][] = $id_question; |
||||
2087 | } |
||||
2088 | $smcFunc['db_free_result']($request); |
||||
2089 | |||||
2090 | cache_put_data('verificationQuestions', $modSettings['question_id_cache'], 300); |
||||
2091 | } |
||||
2092 | } |
||||
2093 | |||||
2094 | if (!isset($_SESSION[$verificationOptions['id'] . '_vv'])) |
||||
2095 | $_SESSION[$verificationOptions['id'] . '_vv'] = array(); |
||||
2096 | |||||
2097 | // Do we need to refresh the verification? |
||||
2098 | if (!$do_test && (!empty($_SESSION[$verificationOptions['id'] . '_vv']['did_pass']) || empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) || $_SESSION[$verificationOptions['id'] . '_vv']['count'] > 3) && empty($verificationOptions['dont_refresh'])) |
||||
2099 | $force_refresh = true; |
||||
2100 | else |
||||
2101 | $force_refresh = false; |
||||
2102 | |||||
2103 | // This can also force a fresh, although unlikely. |
||||
2104 | if (($thisVerification['show_visual'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['code'])) || ($thisVerification['number_questions'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['q']))) |
||||
2105 | $force_refresh = true; |
||||
2106 | |||||
2107 | $verification_errors = array(); |
||||
2108 | // Start with any testing. |
||||
2109 | if ($do_test) |
||||
2110 | { |
||||
2111 | // This cannot happen! |
||||
2112 | if (!isset($_SESSION[$verificationOptions['id'] . '_vv']['count'])) |
||||
2113 | fatal_lang_error('no_access', false); |
||||
2114 | // ... nor this! |
||||
2115 | if ($thisVerification['number_questions'] && (!isset($_SESSION[$verificationOptions['id'] . '_vv']['q']) || !isset($_REQUEST[$verificationOptions['id'] . '_vv']['q']))) |
||||
2116 | fatal_lang_error('no_access', false); |
||||
2117 | // Hmm, it's requested but not actually declared. This shouldn't happen. |
||||
2118 | if ($thisVerification['empty_field'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['empty_field'])) |
||||
2119 | fatal_lang_error('no_access', false); |
||||
2120 | // While we're here, did the user do something bad? |
||||
2121 | if ($thisVerification['empty_field'] && !empty($_SESSION[$verificationOptions['id'] . '_vv']['empty_field']) && !empty($_REQUEST[$_SESSION[$verificationOptions['id'] . '_vv']['empty_field']])) |
||||
2122 | $verification_errors[] = 'wrong_verification_answer'; |
||||
2123 | |||||
2124 | if ($thisVerification['can_recaptcha']) |
||||
2125 | { |
||||
2126 | $reCaptcha = new \ReCaptcha\ReCaptcha($modSettings['recaptcha_secret_key'], new \ReCaptcha\RequestMethod\SocketPost()); |
||||
2127 | |||||
2128 | // Was there a reCAPTCHA response? |
||||
2129 | if (isset($_POST['g-recaptcha-response'])) |
||||
2130 | { |
||||
2131 | $resp = $reCaptcha->verify($_POST['g-recaptcha-response'], $user_info['ip']); |
||||
2132 | |||||
2133 | if (!$resp->isSuccess()) |
||||
2134 | $verification_errors[] = 'wrong_verification_recaptcha'; |
||||
2135 | } |
||||
2136 | else |
||||
2137 | $verification_errors[] = 'wrong_verification_code'; |
||||
2138 | } |
||||
2139 | if ($thisVerification['show_visual'] && (empty($_REQUEST[$verificationOptions['id'] . '_vv']['code']) || empty($_SESSION[$verificationOptions['id'] . '_vv']['code']) || strtoupper($_REQUEST[$verificationOptions['id'] . '_vv']['code']) !== $_SESSION[$verificationOptions['id'] . '_vv']['code'])) |
||||
2140 | $verification_errors[] = 'wrong_verification_code'; |
||||
2141 | if ($thisVerification['number_questions']) |
||||
2142 | { |
||||
2143 | $incorrectQuestions = array(); |
||||
2144 | foreach ($_SESSION[$verificationOptions['id'] . '_vv']['q'] as $q) |
||||
2145 | { |
||||
2146 | // We don't have this question any more, thus no answers. |
||||
2147 | if (!isset($modSettings['question_id_cache']['questions'][$q])) |
||||
2148 | continue; |
||||
2149 | // This is quite complex. We have our question but it might have multiple answers. |
||||
2150 | // First, did they actually answer this question? |
||||
2151 | if (!isset($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) || trim($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) == '') |
||||
2152 | { |
||||
2153 | $incorrectQuestions[] = $q; |
||||
2154 | continue; |
||||
2155 | } |
||||
2156 | // Second, is their answer in the list of possible answers? |
||||
2157 | else |
||||
2158 | { |
||||
2159 | $given_answer = trim($smcFunc['htmlspecialchars']($smcFunc['strtolower']($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]))); |
||||
2160 | if (!in_array($given_answer, $modSettings['question_id_cache']['questions'][$q]['answers'])) |
||||
2161 | $incorrectQuestions[] = $q; |
||||
2162 | } |
||||
2163 | } |
||||
2164 | |||||
2165 | if (!empty($incorrectQuestions)) |
||||
2166 | $verification_errors[] = 'wrong_verification_answer'; |
||||
2167 | } |
||||
2168 | |||||
2169 | // Hooks got anything to say about this verification? |
||||
2170 | call_integration_hook('integrate_create_control_verification_test', array($thisVerification, &$verification_errors)); |
||||
2171 | } |
||||
2172 | |||||
2173 | // Any errors means we refresh potentially. |
||||
2174 | if (!empty($verification_errors)) |
||||
2175 | { |
||||
2176 | if (empty($_SESSION[$verificationOptions['id'] . '_vv']['errors'])) |
||||
2177 | $_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; |
||||
2178 | // Too many errors? |
||||
2179 | elseif ($_SESSION[$verificationOptions['id'] . '_vv']['errors'] > $thisVerification['max_errors']) |
||||
2180 | $force_refresh = true; |
||||
2181 | |||||
2182 | // Keep a track of these. |
||||
2183 | $_SESSION[$verificationOptions['id'] . '_vv']['errors']++; |
||||
2184 | } |
||||
2185 | |||||
2186 | // Are we refreshing then? |
||||
2187 | if ($force_refresh) |
||||
2188 | { |
||||
2189 | // Assume nothing went before. |
||||
2190 | $_SESSION[$verificationOptions['id'] . '_vv']['count'] = 0; |
||||
2191 | $_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; |
||||
2192 | $_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = false; |
||||
2193 | $_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); |
||||
2194 | $_SESSION[$verificationOptions['id'] . '_vv']['code'] = ''; |
||||
2195 | |||||
2196 | // Make our magic empty field. |
||||
2197 | if ($thisVerification['empty_field']) |
||||
2198 | { |
||||
2199 | // We're building a field that lives in the template, that we hope to be empty later. But at least we give it a believable name. |
||||
2200 | $terms = array('gadget', 'device', 'uid', 'gid', 'guid', 'uuid', 'unique', 'identifier'); |
||||
2201 | $second_terms = array('hash', 'cipher', 'code', 'key', 'unlock', 'bit', 'value'); |
||||
2202 | $start = mt_rand(0, 27); |
||||
2203 | $hash = substr(md5(time()), $start, 4); |
||||
2204 | $_SESSION[$verificationOptions['id'] . '_vv']['empty_field'] = $terms[array_rand($terms)] . '-' . $second_terms[array_rand($second_terms)] . '-' . $hash; |
||||
2205 | } |
||||
2206 | |||||
2207 | // Generating a new image. |
||||
2208 | if ($thisVerification['show_visual']) |
||||
2209 | { |
||||
2210 | // Are we overriding the range? |
||||
2211 | $character_range = !empty($verificationOptions['override_range']) ? $verificationOptions['override_range'] : $context['standard_captcha_range']; |
||||
2212 | |||||
2213 | for ($i = 0; $i < 6; $i++) |
||||
2214 | $_SESSION[$verificationOptions['id'] . '_vv']['code'] .= $character_range[array_rand($character_range)]; |
||||
2215 | } |
||||
2216 | |||||
2217 | // Getting some new questions? |
||||
2218 | if ($thisVerification['number_questions']) |
||||
2219 | { |
||||
2220 | // Attempt to try the current page's language, followed by the user's preference, followed by the site default. |
||||
2221 | $possible_langs = array(); |
||||
2222 | if (isset($_SESSION['language'])) |
||||
2223 | $possible_langs[] = strtr($_SESSION['language'], array('-utf8' => '')); |
||||
2224 | if (!empty($user_info['language'])) |
||||
2225 | $possible_langs[] = $user_info['language']; |
||||
2226 | |||||
2227 | $possible_langs[] = $language; |
||||
2228 | |||||
2229 | $questionIDs = array(); |
||||
2230 | foreach ($possible_langs as $lang) |
||||
2231 | { |
||||
2232 | $lang = strtr($lang, array('-utf8' => '')); |
||||
2233 | if (isset($modSettings['question_id_cache']['langs'][$lang])) |
||||
2234 | { |
||||
2235 | // If we find questions for this, grab the ids from this language's ones, randomize the array and take just the number we need. |
||||
2236 | $questionIDs = $modSettings['question_id_cache']['langs'][$lang]; |
||||
2237 | shuffle($questionIDs); |
||||
2238 | $questionIDs = array_slice($questionIDs, 0, $thisVerification['number_questions']); |
||||
2239 | break; |
||||
2240 | } |
||||
2241 | } |
||||
2242 | } |
||||
2243 | |||||
2244 | // Hooks may need to know about this. |
||||
2245 | call_integration_hook('integrate_create_control_verification_refresh', array($thisVerification)); |
||||
2246 | } |
||||
2247 | else |
||||
2248 | { |
||||
2249 | // Same questions as before. |
||||
2250 | $questionIDs = !empty($_SESSION[$verificationOptions['id'] . '_vv']['q']) ? $_SESSION[$verificationOptions['id'] . '_vv']['q'] : array(); |
||||
2251 | $thisVerification['text_value'] = !empty($_REQUEST[$verificationOptions['id'] . '_vv']['code']) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['code']) : ''; |
||||
2252 | } |
||||
2253 | |||||
2254 | // If we do have an empty field, it would be nice to hide it from legitimate users who shouldn't be populating it anyway. |
||||
2255 | if (!empty($_SESSION[$verificationOptions['id'] . '_vv']['empty_field'])) |
||||
2256 | { |
||||
2257 | if (!isset($context['html_headers'])) |
||||
2258 | $context['html_headers'] = ''; |
||||
2259 | $context['html_headers'] .= '<style>.vv_special { display:none; }</style>'; |
||||
2260 | } |
||||
2261 | |||||
2262 | // Have we got some questions to load? |
||||
2263 | if (!empty($questionIDs)) |
||||
2264 | { |
||||
2265 | $_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); |
||||
2266 | foreach ($questionIDs as $q) |
||||
2267 | { |
||||
2268 | // Bit of a shortcut this. |
||||
2269 | $row = &$modSettings['question_id_cache']['questions'][$q]; |
||||
2270 | $thisVerification['questions'][] = array( |
||||
2271 | 'id' => $q, |
||||
2272 | 'q' => parse_bbc($row['question']), |
||||
2273 | 'is_error' => !empty($incorrectQuestions) && in_array($q, $incorrectQuestions), |
||||
2274 | // Remember a previous submission? |
||||
2275 | 'a' => isset($_REQUEST[$verificationOptions['id'] . '_vv'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) : '', |
||||
2276 | ); |
||||
2277 | $_SESSION[$verificationOptions['id'] . '_vv']['q'][] = $q; |
||||
2278 | } |
||||
2279 | } |
||||
2280 | |||||
2281 | $_SESSION[$verificationOptions['id'] . '_vv']['count'] = empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) ? 1 : $_SESSION[$verificationOptions['id'] . '_vv']['count'] + 1; |
||||
2282 | |||||
2283 | // Let our hooks know that we are done with the verification process. |
||||
2284 | call_integration_hook('integrate_create_control_verification_post', array(&$verification_errors, $do_test)); |
||||
2285 | |||||
2286 | // Return errors if we have them. |
||||
2287 | if (!empty($verification_errors)) |
||||
2288 | return $verification_errors; |
||||
2289 | // If we had a test that one, make a note. |
||||
2290 | elseif ($do_test) |
||||
2291 | $_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = true; |
||||
2292 | |||||
2293 | // Say that everything went well chaps. |
||||
2294 | return true; |
||||
2295 | } |
||||
2296 | |||||
2297 | /** |
||||
2298 | * This keeps track of all registered handling functions for auto suggest functionality and passes execution to them. |
||||
2299 | * |
||||
2300 | * @param bool $checkRegistered If set to something other than null, checks whether the callback function is registered |
||||
2301 | * @return void|bool Returns whether the callback function is registered if $checkRegistered isn't null |
||||
2302 | */ |
||||
2303 | function AutoSuggestHandler($checkRegistered = null) |
||||
2304 | { |
||||
2305 | global $smcFunc, $context; |
||||
2306 | |||||
2307 | // These are all registered types. |
||||
2308 | $searchTypes = array( |
||||
2309 | 'member' => 'Member', |
||||
2310 | 'membergroups' => 'MemberGroups', |
||||
2311 | 'versions' => 'SMFVersions', |
||||
2312 | ); |
||||
2313 | |||||
2314 | call_integration_hook('integrate_autosuggest', array(&$searchTypes)); |
||||
2315 | |||||
2316 | // If we're just checking the callback function is registered return true or false. |
||||
2317 | if ($checkRegistered != null) |
||||
2318 | return isset($searchTypes[$checkRegistered]) && function_exists('AutoSuggest_Search_' . $checkRegistered); |
||||
0 ignored issues
–
show
Are you sure
$checkRegistered of type true can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
2319 | |||||
2320 | checkSession('get'); |
||||
2321 | loadTemplate('Xml'); |
||||
2322 | |||||
2323 | // Any parameters? |
||||
2324 | $context['search_param'] = isset($_REQUEST['search_param']) ? $smcFunc['json_decode'](base64_decode($_REQUEST['search_param']), true) : array(); |
||||
2325 | |||||
2326 | if (isset($_REQUEST['suggest_type'], $_REQUEST['search']) && isset($searchTypes[$_REQUEST['suggest_type']])) |
||||
2327 | { |
||||
2328 | $function = 'AutoSuggest_Search_' . $searchTypes[$_REQUEST['suggest_type']]; |
||||
2329 | $context['sub_template'] = 'generic_xml'; |
||||
2330 | $context['xml_data'] = $function(); |
||||
2331 | } |
||||
2332 | } |
||||
2333 | |||||
2334 | /** |
||||
2335 | * Search for a member - by real_name or member_name by default. |
||||
2336 | * |
||||
2337 | * @return array An array of information for displaying the suggestions |
||||
2338 | */ |
||||
2339 | function AutoSuggest_Search_Member() |
||||
2340 | { |
||||
2341 | global $user_info, $smcFunc, $context; |
||||
2342 | |||||
2343 | $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])) . '*'; |
||||
2344 | $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); |
||||
2345 | |||||
2346 | // Find the member. |
||||
2347 | $request = $smcFunc['db_query']('', ' |
||||
2348 | SELECT id_member, real_name |
||||
2349 | FROM {db_prefix}members |
||||
2350 | WHERE {raw:real_name} LIKE {string:search}' . (!empty($context['search_param']['buddies']) ? ' |
||||
2351 | AND id_member IN ({array_int:buddy_list})' : '') . ' |
||||
2352 | AND is_activated IN (1, 11) |
||||
2353 | LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), |
||||
2354 | array( |
||||
2355 | 'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name', |
||||
2356 | 'buddy_list' => $user_info['buddies'], |
||||
2357 | 'search' => $_REQUEST['search'], |
||||
2358 | ) |
||||
2359 | ); |
||||
2360 | $xml_data = array( |
||||
2361 | 'items' => array( |
||||
2362 | 'identifier' => 'item', |
||||
2363 | 'children' => array(), |
||||
2364 | ), |
||||
2365 | ); |
||||
2366 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
2367 | { |
||||
2368 | $row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); |
||||
2369 | |||||
2370 | $xml_data['items']['children'][] = array( |
||||
2371 | 'attributes' => array( |
||||
2372 | 'id' => $row['id_member'], |
||||
2373 | ), |
||||
2374 | 'value' => $row['real_name'], |
||||
2375 | ); |
||||
2376 | } |
||||
2377 | $smcFunc['db_free_result']($request); |
||||
2378 | |||||
2379 | return $xml_data; |
||||
2380 | } |
||||
2381 | |||||
2382 | /** |
||||
2383 | * Search for a membergroup by name |
||||
2384 | * |
||||
2385 | * @return array An array of information for displaying the suggestions |
||||
2386 | */ |
||||
2387 | function AutoSuggest_Search_MemberGroups() |
||||
2388 | { |
||||
2389 | global $smcFunc; |
||||
2390 | |||||
2391 | $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])) . '*'; |
||||
2392 | $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); |
||||
2393 | |||||
2394 | // Find the group. |
||||
2395 | // Only return groups which are not post-based and not "Hidden", but not the "Administrators" or "Moderators" groups. |
||||
2396 | $request = $smcFunc['db_query']('', ' |
||||
2397 | SELECT id_group, group_name |
||||
2398 | FROM {db_prefix}membergroups |
||||
2399 | WHERE {raw:group_name} LIKE {string:search} |
||||
2400 | AND min_posts = {int:min_posts} |
||||
2401 | AND id_group NOT IN ({array_int:invalid_groups}) |
||||
2402 | AND hidden != {int:hidden}', |
||||
2403 | array( |
||||
2404 | 'group_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(group_name)' : 'group_name', |
||||
2405 | 'min_posts' => -1, |
||||
2406 | 'invalid_groups' => array(1, 3), |
||||
2407 | 'hidden' => 2, |
||||
2408 | 'search' => $_REQUEST['search'], |
||||
2409 | ) |
||||
2410 | ); |
||||
2411 | $xml_data = array( |
||||
2412 | 'items' => array( |
||||
2413 | 'identifier' => 'item', |
||||
2414 | 'children' => array(), |
||||
2415 | ), |
||||
2416 | ); |
||||
2417 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
2418 | { |
||||
2419 | $row['group_name'] = strtr($row['group_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); |
||||
2420 | |||||
2421 | $xml_data['items']['children'][] = array( |
||||
2422 | 'attributes' => array( |
||||
2423 | 'id' => $row['id_group'], |
||||
2424 | ), |
||||
2425 | 'value' => $row['group_name'], |
||||
2426 | ); |
||||
2427 | } |
||||
2428 | $smcFunc['db_free_result']($request); |
||||
2429 | |||||
2430 | return $xml_data; |
||||
2431 | } |
||||
2432 | |||||
2433 | /** |
||||
2434 | * Provides a list of possible SMF versions to use in emulation |
||||
2435 | * |
||||
2436 | * @return array An array of data for displaying the suggestions |
||||
2437 | */ |
||||
2438 | function AutoSuggest_Search_SMFVersions() |
||||
2439 | { |
||||
2440 | global $smcFunc; |
||||
2441 | |||||
2442 | $xml_data = array( |
||||
2443 | 'items' => array( |
||||
2444 | 'identifier' => 'item', |
||||
2445 | 'children' => array(), |
||||
2446 | ), |
||||
2447 | ); |
||||
2448 | |||||
2449 | // First try and get it from the database. |
||||
2450 | $versions = array(); |
||||
2451 | $request = $smcFunc['db_query']('', ' |
||||
2452 | SELECT data |
||||
2453 | FROM {db_prefix}admin_info_files |
||||
2454 | WHERE filename = {string:latest_versions} |
||||
2455 | AND path = {string:path}', |
||||
2456 | array( |
||||
2457 | 'latest_versions' => 'latest-versions.txt', |
||||
2458 | 'path' => '/smf/', |
||||
2459 | ) |
||||
2460 | ); |
||||
2461 | if (($smcFunc['db_num_rows']($request) > 0) && ($row = $smcFunc['db_fetch_assoc']($request)) && !empty($row['data'])) |
||||
2462 | { |
||||
2463 | // The file can be either Windows or Linux line endings, but let's ensure we clean it as best we can. |
||||
2464 | $possible_versions = explode("\n", $row['data']); |
||||
2465 | foreach ($possible_versions as $ver) |
||||
2466 | { |
||||
2467 | $ver = trim($ver); |
||||
2468 | if (strpos($ver, 'SMF') === 0) |
||||
2469 | $versions[] = $ver; |
||||
2470 | } |
||||
2471 | } |
||||
2472 | $smcFunc['db_free_result']($request); |
||||
2473 | |||||
2474 | // Just in case we don't have ANYthing. |
||||
2475 | if (empty($versions)) |
||||
2476 | $versions = array(SMF_FULL_VERSION); |
||||
2477 | |||||
2478 | foreach ($versions as $id => $version) |
||||
2479 | if (strpos($version, strtoupper($_REQUEST['search'])) !== false) |
||||
2480 | $xml_data['items']['children'][] = array( |
||||
2481 | 'attributes' => array( |
||||
2482 | 'id' => $id, |
||||
2483 | ), |
||||
2484 | 'value' => $version, |
||||
2485 | ); |
||||
2486 | |||||
2487 | return $xml_data; |
||||
2488 | } |
||||
2489 | |||||
2490 | ?> |