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