Mercurial > hg > vamp-website
comparison forum/Sources/Subs-Editor.php @ 76:e3e11437ecea website
Add forum code
author | Chris Cannam |
---|---|
date | Sun, 07 Jul 2013 11:25:48 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
75:72f59aa7e503 | 76:e3e11437ecea |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * Simple Machines Forum (SMF) | |
5 * | |
6 * @package SMF | |
7 * @author Simple Machines http://www.simplemachines.org | |
8 * @copyright 2011 Simple Machines | |
9 * @license http://www.simplemachines.org/about/smf/license.php BSD | |
10 * | |
11 * @version 2.0 | |
12 */ | |
13 | |
14 if (!defined('SMF')) | |
15 die('Hacking attempt...'); | |
16 | |
17 /* This file contains those functions specific to the editing box and is | |
18 generally used for WYSIWYG type functionality. Doing all this is the | |
19 following: | |
20 | |
21 void EditorMain() | |
22 // !! | |
23 | |
24 void bbc_to_html() | |
25 // !! | |
26 | |
27 void html_to_bbc() | |
28 // !! | |
29 | |
30 void theme_postbox(string message) | |
31 - for compatibility - passes right through to the template_control_richedit function. | |
32 | |
33 void create_control_richedit(&array editorOptions) | |
34 // !! | |
35 | |
36 void create_control_verification(&array suggestOptions) | |
37 // !! | |
38 | |
39 void fetchTagAttributes() | |
40 // !! | |
41 | |
42 array getMessageIcons(int board_id) | |
43 - retrieves a list of message icons. | |
44 - based on the settings, the array will either contain a list of default | |
45 message icons or a list of custom message icons retrieved from the | |
46 database. | |
47 - the board_id is needed for the custom message icons (which can be set for | |
48 each board individually). | |
49 | |
50 void AutoSuggestHandler(string checkRegistered = null) | |
51 // !!! | |
52 | |
53 void AutoSuggest_Search_Member() | |
54 // !!! | |
55 */ | |
56 | |
57 // At the moment this is only used for returning WYSIWYG data... | |
58 function EditorMain() | |
59 { | |
60 global $context, $smcFunc; | |
61 | |
62 checkSession('get'); | |
63 | |
64 if (!isset($_REQUEST['view']) || !isset($_REQUEST['message'])) | |
65 fatal_lang_error('no_access', false); | |
66 | |
67 $context['sub_template'] = 'sendbody'; | |
68 | |
69 $context['view'] = (int) $_REQUEST['view']; | |
70 | |
71 // Return the right thing for the mode. | |
72 if ($context['view']) | |
73 { | |
74 $_REQUEST['message'] = strtr($_REQUEST['message'], array('#smcol#' => ';', '#smlt#' => '<', '#smgt#' => '>', '#smamp#' => '&')); | |
75 $context['message'] = bbc_to_html($_REQUEST['message']); | |
76 } | |
77 else | |
78 { | |
79 $_REQUEST['message'] = un_htmlspecialchars($_REQUEST['message']); | |
80 $_REQUEST['message'] = strtr($_REQUEST['message'], array('#smcol#' => ';', '#smlt#' => '<', '#smgt#' => '>', '#smamp#' => '&')); | |
81 | |
82 $context['message'] = html_to_bbc($_REQUEST['message']); | |
83 } | |
84 | |
85 $context['message'] = $smcFunc['htmlspecialchars']($context['message']); | |
86 } | |
87 | |
88 // Convert only the BBC that can be edited in HTML mode for the editor. | |
89 function bbc_to_html($text) | |
90 { | |
91 global $modSettings, $smcFunc; | |
92 | |
93 // Turn line breaks back into br's. | |
94 $text = strtr($text, array("\r" => '', "\n" => '<br />')); | |
95 | |
96 // Prevent conversion of all bbcode inside these bbcodes. | |
97 // !!! Tie in with bbc permissions ? | |
98 foreach (array('code', 'php', 'nobbc') as $code) | |
99 { | |
100 if (strpos($text, '['. $code) !== false) | |
101 { | |
102 $parts = preg_split('~(\[/' . $code . '\]|\[' . $code . '(?:=[^\]]+)?\])~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); | |
103 | |
104 // Only mess with stuff inside tags. | |
105 for ($i = 0, $n = count($parts); $i < $n; $i++) | |
106 { | |
107 // Value of 2 means we're inside the tag. | |
108 if ($i % 4 == 2) | |
109 $parts[$i] = strtr($parts[$i], array('[' => '[', ']' => ']', "'" => "'")); | |
110 } | |
111 // Put our humpty dumpty message back together again. | |
112 $text = implode('', $parts); | |
113 } | |
114 } | |
115 | |
116 // What tags do we allow? | |
117 $allowed_tags = array('b', 'u', 'i', 's', 'hr', 'list', 'li', 'font', 'size', 'color', 'img', 'left', 'center', 'right', 'url', 'email', 'ftp', 'sub', 'sup'); | |
118 | |
119 $text = parse_bbc($text, true, '', $allowed_tags); | |
120 | |
121 // Fix for having a line break then a thingy. | |
122 $text = strtr($text, array('<br /><div' => '<div', "\n" => '', "\r" => '')); | |
123 | |
124 // Note that IE doesn't understand spans really - make them something "legacy" | |
125 $working_html = array( | |
126 '~<del>(.+?)</del>~i' => '<strike>$1</strike>', | |
127 '~<span\sclass="bbc_u">(.+?)</span>~i' => '<u>$1</u>', | |
128 '~<span\sstyle="color:\s*([#\d\w]+);" class="bbc_color">(.+?)</span>~i' => '<font color="$1">$2</font>', | |
129 '~<span\sstyle="font-family:\s*([#\d\w\s]+);" class="bbc_font">(.+?)</span>~i' => '<font face="$1">$2</font>', | |
130 '~<div\sstyle="text-align:\s*(left|right);">(.+?)</div>~i' => '<p align="$1">$2</p>', | |
131 ); | |
132 $text = preg_replace(array_keys($working_html), array_values($working_html), $text); | |
133 | |
134 // Parse unique ID's and disable javascript into the smileys - using the double space. | |
135 $i = 1; | |
136 $text = preg_replace('~(?:\s| )?<(img\ssrc="' . preg_quote($modSettings['smileys_url'], '~') . '/[^<>]+?/([^<>]+?)"\s*)[^<>]*?class="smiley" />~e', '\'<\' . ' . 'stripslashes(\'$1\') . \'alt="" title="" onresizestart="return false;" id="smiley_\' . ' . "\$" . 'i++ . \'_$2" style="padding: 0 3px 0 3px;" />\'', $text); | |
137 | |
138 return $text; | |
139 } | |
140 | |
141 // The harder one - wysiwyg to BBC! | |
142 function html_to_bbc($text) | |
143 { | |
144 global $modSettings, $smcFunc, $sourcedir, $scripturl, $context; | |
145 | |
146 // Replace newlines with spaces, as that's how browsers usually interpret them. | |
147 $text = preg_replace("~\s*[\r\n]+\s*~", ' ', $text); | |
148 | |
149 // Though some of us love paragraphs, the parser will do better with breaks. | |
150 $text = preg_replace('~</p>\s*?<p~i', '</p><br /><p', $text); | |
151 $text = preg_replace('~</p>\s*(?!<)~i', '</p><br />', $text); | |
152 | |
153 // Safari/webkit wraps lines in Wysiwyg in <div>'s. | |
154 if ($context['browser']['is_webkit']) | |
155 $text = preg_replace(array('~<div(?:\s(?:[^<>]*?))?' . '>~i', '</div>'), array('<br />', ''), $text); | |
156 | |
157 // If there's a trailing break get rid of it - Firefox tends to add one. | |
158 $text = preg_replace('~<br\s?/?' . '>$~i', '', $text); | |
159 | |
160 // Remove any formatting within code tags. | |
161 if (strpos($text, '[code') !== false) | |
162 { | |
163 $text = preg_replace('~<br\s?/?' . '>~i', '#smf_br_spec_grudge_cool!#', $text); | |
164 $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); | |
165 | |
166 // Only mess with stuff outside [code] tags. | |
167 for ($i = 0, $n = count($parts); $i < $n; $i++) | |
168 { | |
169 // Value of 2 means we're inside the tag. | |
170 if ($i % 4 == 2) | |
171 $parts[$i] = strip_tags($parts[$i]); | |
172 } | |
173 | |
174 $text = strtr(implode('', $parts), array('#smf_br_spec_grudge_cool!#' => '<br />')); | |
175 } | |
176 | |
177 // Remove scripts, style and comment blocks. | |
178 $text = preg_replace('~<script[^>]*[^/]?' . '>.*?</script>~i', '', $text); | |
179 $text = preg_replace('~<style[^>]*[^/]?' . '>.*?</style>~i', '', $text); | |
180 $text = preg_replace('~\\<\\!--.*?-->~i', '', $text); | |
181 $text = preg_replace('~\\<\\!\\[CDATA\\[.*?\\]\\]\\>~i', '', $text); | |
182 | |
183 // Do the smileys ultra first! | |
184 preg_match_all('~<img\s+[^<>]*?id="*smiley_\d+_([^<>]+?)[\s"/>]\s*[^<>]*?/*>(?:\s)?~i', $text, $matches); | |
185 if (!empty($matches[0])) | |
186 { | |
187 // Easy if it's not custom. | |
188 if (empty($modSettings['smiley_enable'])) | |
189 { | |
190 $smileysfrom = array('>:D', ':D', '::)', '>:(', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); | |
191 $smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif'); | |
192 | |
193 foreach ($matches[1] as $k => $file) | |
194 { | |
195 $found = array_search($file, $smileysto); | |
196 // Note the weirdness here is to stop double spaces between smileys. | |
197 if ($found) | |
198 $matches[1][$k] = '-[]-smf_smily_start#|#' . htmlspecialchars($smileysfrom[$found]) . '-[]-smf_smily_end#|#'; | |
199 else | |
200 $matches[1][$k] = ''; | |
201 } | |
202 } | |
203 else | |
204 { | |
205 // Load all the smileys. | |
206 $names = array(); | |
207 foreach ($matches[1] as $file) | |
208 $names[] = $file; | |
209 $names = array_unique($names); | |
210 | |
211 if (!empty($names)) | |
212 { | |
213 $request = $smcFunc['db_query']('', ' | |
214 SELECT code, filename | |
215 FROM {db_prefix}smileys | |
216 WHERE filename IN ({array_string:smiley_filenames})', | |
217 array( | |
218 'smiley_filenames' => $names, | |
219 ) | |
220 ); | |
221 $mappings = array(); | |
222 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
223 $mappings[$row['filename']] = htmlspecialchars($row['code']); | |
224 $smcFunc['db_free_result']($request); | |
225 | |
226 foreach ($matches[1] as $k => $file) | |
227 if (isset($mappings[$file])) | |
228 $matches[1][$k] = '-[]-smf_smily_start#|#' . $mappings[$file] . '-[]-smf_smily_end#|#'; | |
229 } | |
230 } | |
231 | |
232 // Replace the tags! | |
233 $text = str_replace($matches[0], $matches[1], $text); | |
234 | |
235 // Now sort out spaces | |
236 $text = str_replace(array('-[]-smf_smily_end#|#-[]-smf_smily_start#|#', '-[]-smf_smily_end#|#', '-[]-smf_smily_start#|#'), ' ', $text); | |
237 } | |
238 | |
239 // Only try to buy more time if the client didn't quit. | |
240 if (connection_aborted() && $context['server']['is_apache']) | |
241 @apache_reset_timeout(); | |
242 | |
243 $parts = preg_split('~(<[A-Za-z]+\s*[^<>]*?style="?[^<>"]+"?[^<>]*?(?:/?)>|</[A-Za-z]+>)~', $text, -1, PREG_SPLIT_DELIM_CAPTURE); | |
244 $replacement = ''; | |
245 $stack = array(); | |
246 | |
247 foreach ($parts as $part) | |
248 { | |
249 if (preg_match('~(<([A-Za-z]+)\s*[^<>]*?)style="?([^<>"]+)"?([^<>]*?(/?)>)~', $part, $matches) === 1) | |
250 { | |
251 // If it's being closed instantly, we can't deal with it...yet. | |
252 if ($matches[5] === '/') | |
253 continue; | |
254 else | |
255 { | |
256 // Get an array of styles that apply to this element. (The strtr is there to combat HTML generated by Word.) | |
257 $styles = explode(';', strtr($matches[3], array('"' => ''))); | |
258 $curElement = $matches[2]; | |
259 $precedingStyle = $matches[1]; | |
260 $afterStyle = $matches[4]; | |
261 $curCloseTags = ''; | |
262 $extra_attr = ''; | |
263 | |
264 foreach ($styles as $type_value_pair) | |
265 { | |
266 // Remove spaces and convert uppercase letters. | |
267 $clean_type_value_pair = strtolower(strtr(trim($type_value_pair), '=', ':')); | |
268 | |
269 // Something like 'font-weight: bold' is expected here. | |
270 if (strpos($clean_type_value_pair, ':') === false) | |
271 continue; | |
272 | |
273 // Capture the elements of a single style item (e.g. 'font-weight' and 'bold'). | |
274 list ($style_type, $style_value) = explode(':', $type_value_pair); | |
275 | |
276 $style_value = trim($style_value); | |
277 | |
278 switch (trim($style_type)) | |
279 { | |
280 case 'font-weight': | |
281 if ($style_value === 'bold') | |
282 { | |
283 $curCloseTags .= '[/b]'; | |
284 $replacement .= '[b]'; | |
285 } | |
286 break; | |
287 | |
288 case 'text-decoration': | |
289 if ($style_value == 'underline') | |
290 { | |
291 $curCloseTags .= '[/u]'; | |
292 $replacement .= '[u]'; | |
293 } | |
294 elseif ($style_value == 'line-through') | |
295 { | |
296 $curCloseTags .= '[/s]'; | |
297 $replacement .= '[s]'; | |
298 } | |
299 break; | |
300 | |
301 case 'text-align': | |
302 if ($style_value == 'left') | |
303 { | |
304 $curCloseTags .= '[/left]'; | |
305 $replacement .= '[left]'; | |
306 } | |
307 elseif ($style_value == 'center') | |
308 { | |
309 $curCloseTags .= '[/center]'; | |
310 $replacement .= '[center]'; | |
311 } | |
312 elseif ($style_value == 'right') | |
313 { | |
314 $curCloseTags .= '[/right]'; | |
315 $replacement .= '[right]'; | |
316 } | |
317 break; | |
318 | |
319 case 'font-style': | |
320 if ($style_value == 'italic') | |
321 { | |
322 $curCloseTags .= '[/i]'; | |
323 $replacement .= '[i]'; | |
324 } | |
325 break; | |
326 | |
327 case 'color': | |
328 $curCloseTags .= '[/color]'; | |
329 $replacement .= '[color=' . $style_value . ']'; | |
330 break; | |
331 | |
332 case 'font-size': | |
333 // Sometimes people put decimals where decimals should not be. | |
334 if (preg_match('~(\d)+\.\d+(p[xt])~i', $style_value, $dec_matches) === 1) | |
335 $style_value = $dec_matches[1] . $dec_matches[2]; | |
336 | |
337 $curCloseTags .= '[/size]'; | |
338 $replacement .= '[size=' . $style_value . ']'; | |
339 break; | |
340 | |
341 case 'font-family': | |
342 // Only get the first freaking font if there's a list! | |
343 if (strpos($style_value, ',') !== false) | |
344 $style_value = substr($style_value, 0, strpos($style_value, ',')); | |
345 | |
346 $curCloseTags .= '[/font]'; | |
347 $replacement .= '[font=' . strtr($style_value, array("'" => '')) . ']'; | |
348 break; | |
349 | |
350 // This is a hack for images with dimensions embedded. | |
351 case 'width': | |
352 case 'height': | |
353 if (preg_match('~[1-9]\d*~i', $style_value, $dimension) === 1) | |
354 $extra_attr .= ' ' . $style_type . '="' . $dimension[0] . '"'; | |
355 break; | |
356 | |
357 case 'list-style-type': | |
358 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) | |
359 $extra_attr .= ' listtype="' . $listType[0] . '"'; | |
360 break; | |
361 } | |
362 } | |
363 | |
364 // Preserve some tags stripping the styling. | |
365 if (in_array($matches[2], array('a', 'font'))) | |
366 { | |
367 $replacement .= $precedingStyle . $afterStyle; | |
368 $curCloseTags = '</' . $matches[2] . '>' . $curCloseTags; | |
369 } | |
370 | |
371 // If there's something that still needs closing, push it to the stack. | |
372 if (!empty($curCloseTags)) | |
373 array_push($stack, array( | |
374 'element' => strtolower($curElement), | |
375 'closeTags' => $curCloseTags | |
376 ) | |
377 ); | |
378 elseif (!empty($extra_attr)) | |
379 $replacement .= $precedingStyle . $extra_attr . $afterStyle; | |
380 } | |
381 } | |
382 | |
383 elseif (preg_match('~</([A-Za-z]+)>~', $part, $matches) === 1) | |
384 { | |
385 // Is this the element that we've been waiting for to be closed? | |
386 if (!empty($stack) && strtolower($matches[1]) === $stack[count($stack) - 1]['element']) | |
387 { | |
388 $byebyeTag = array_pop($stack); | |
389 $replacement .= $byebyeTag['closeTags']; | |
390 } | |
391 | |
392 // Must've been something else. | |
393 else | |
394 $replacement .= $part; | |
395 } | |
396 // In all other cases, just add the part to the replacement. | |
397 else | |
398 $replacement .= $part; | |
399 } | |
400 | |
401 // Now put back the replacement in the text. | |
402 $text = $replacement; | |
403 | |
404 // We are not finished yet, request more time. | |
405 if (connection_aborted() && $context['server']['is_apache']) | |
406 @apache_reset_timeout(); | |
407 | |
408 // Let's pull out any legacy alignments. | |
409 while (preg_match('~<([A-Za-z]+)\s+[^<>]*?(align="*(left|center|right)"*)[^<>]*?(/?)>~i', $text, $matches) === 1) | |
410 { | |
411 // Find the position in the text of this tag over again. | |
412 $start_pos = strpos($text, $matches[0]); | |
413 if ($start_pos === false) | |
414 break; | |
415 | |
416 // End tag? | |
417 if ($matches[4] != '/' && strpos($text, '</' . $matches[1] . '>', $start_pos) !== false) | |
418 { | |
419 $end_length = strlen('</' . $matches[1] . '>'); | |
420 $end_pos = strpos($text, '</' . $matches[1] . '>', $start_pos); | |
421 | |
422 // Remove the align from that tag so it's never checked again. | |
423 $tag = substr($text, $start_pos, strlen($matches[0])); | |
424 $content = substr($text, $start_pos + strlen($matches[0]), $end_pos - $start_pos - strlen($matches[0])); | |
425 $tag = str_replace($matches[2], '', $tag); | |
426 | |
427 // Put the tags back into the body. | |
428 $text = substr($text, 0, $start_pos) . $tag . '[' . $matches[3] . ']' . $content . '[/' . $matches[3] . ']' . substr($text, $end_pos); | |
429 } | |
430 else | |
431 { | |
432 // Just get rid of this evil tag. | |
433 $text = substr($text, 0, $start_pos) . substr($text, $start_pos + strlen($matches[0])); | |
434 } | |
435 } | |
436 | |
437 // Let's do some special stuff for fonts - cause we all love fonts. | |
438 while (preg_match('~<font\s+([^<>]*)>~i', $text, $matches) === 1) | |
439 { | |
440 // Find the position of this again. | |
441 $start_pos = strpos($text, $matches[0]); | |
442 $end_pos = false; | |
443 if ($start_pos === false) | |
444 break; | |
445 | |
446 // This must have an end tag - and we must find the right one. | |
447 $lower_text = strtolower($text); | |
448 | |
449 $start_pos_test = $start_pos + 4; | |
450 // How many starting tags must we find closing ones for first? | |
451 $start_font_tag_stack = 0; | |
452 while ($start_pos_test < strlen($text)) | |
453 { | |
454 // Where is the next starting font? | |
455 $next_start_pos = strpos($lower_text, '<font', $start_pos_test); | |
456 $next_end_pos = strpos($lower_text, '</font>', $start_pos_test); | |
457 | |
458 // Did we past another starting tag before an end one? | |
459 if ($next_start_pos !== false && $next_start_pos < $next_end_pos) | |
460 { | |
461 $start_font_tag_stack++; | |
462 $start_pos_test = $next_start_pos + 4; | |
463 } | |
464 // Otherwise we have an end tag but not the right one? | |
465 elseif ($start_font_tag_stack) | |
466 { | |
467 $start_font_tag_stack--; | |
468 $start_pos_test = $next_end_pos + 4; | |
469 } | |
470 // Otherwise we're there! | |
471 else | |
472 { | |
473 $end_pos = $next_end_pos; | |
474 break; | |
475 } | |
476 } | |
477 if ($end_pos === false) | |
478 break; | |
479 | |
480 // Now work out what the attributes are. | |
481 $attribs = fetchTagAttributes($matches[1]); | |
482 $tags = array(); | |
483 foreach ($attribs as $s => $v) | |
484 { | |
485 if ($s == 'size') | |
486 $tags[] = array('[size=' . (int) trim($v) . ']', '[/size]'); | |
487 elseif ($s == 'face') | |
488 $tags[] = array('[font=' . trim(strtolower($v)) . ']', '[/font]'); | |
489 elseif ($s == 'color') | |
490 $tags[] = array('[color=' . trim(strtolower($v)) . ']', '[/color]'); | |
491 } | |
492 | |
493 // As before add in our tags. | |
494 $before = $after = ''; | |
495 foreach ($tags as $tag) | |
496 { | |
497 $before .= $tag[0]; | |
498 if (isset($tag[1])) | |
499 $after = $tag[1] . $after; | |
500 } | |
501 | |
502 // Remove the tag so it's never checked again. | |
503 $content = substr($text, $start_pos + strlen($matches[0]), $end_pos - $start_pos - strlen($matches[0])); | |
504 | |
505 // Put the tags back into the body. | |
506 $text = substr($text, 0, $start_pos) . $before . $content . $after . substr($text, $end_pos + 7); | |
507 } | |
508 | |
509 // Almost there, just a little more time. | |
510 if (connection_aborted() && $context['server']['is_apache']) | |
511 @apache_reset_timeout(); | |
512 | |
513 if (count($parts = preg_split('~<(/?)(li|ol|ul)([^>]*)>~i', $text, null, PREG_SPLIT_DELIM_CAPTURE)) > 1) | |
514 { | |
515 // A toggle that dermines whether we're directly under a <ol> or <ul>. | |
516 $inList = false; | |
517 | |
518 // Keep track of the number of nested list levels. | |
519 $listDepth = 0; | |
520 | |
521 // Map what we can expect from the HTML to what is supported by SMF. | |
522 $listTypeMapping = array( | |
523 '1' => 'decimal', | |
524 'A' => 'upper-alpha', | |
525 'a' => 'lower-alpha', | |
526 'I' => 'upper-roman', | |
527 'i' => 'lower-roman', | |
528 'disc' => 'disc', | |
529 'square' => 'square', | |
530 'circle' => 'circle', | |
531 ); | |
532 | |
533 // $i: text, $i + 1: '/', $i + 2: tag, $i + 3: tail. | |
534 for ($i = 0, $numParts = count($parts) - 1; $i < $numParts; $i += 4) | |
535 { | |
536 $tag = strtolower($parts[$i + 2]); | |
537 $isOpeningTag = $parts[$i + 1] === ''; | |
538 | |
539 if ($isOpeningTag) | |
540 { | |
541 switch ($tag) | |
542 { | |
543 case 'ol': | |
544 case 'ul': | |
545 | |
546 // We have a problem, we're already in a list. | |
547 if ($inList) | |
548 { | |
549 // Inject a list opener, we'll deal with the ol/ul next loop. | |
550 array_splice($parts, $i, 0, array( | |
551 '', | |
552 '', | |
553 str_repeat("\t", $listDepth) . '[li]', | |
554 '', | |
555 )); | |
556 $numParts = count($parts) - 1; | |
557 | |
558 // The inlist status changes a bit. | |
559 $inList = false; | |
560 } | |
561 | |
562 // Just starting a new list. | |
563 else | |
564 { | |
565 $inList = true; | |
566 | |
567 if ($tag === 'ol') | |
568 $listType = 'decimal'; | |
569 elseif (preg_match('~type="?(' . implode('|', array_keys($listTypeMapping)) . ')"?~', $parts[$i + 3], $match) === 1) | |
570 $listType = $listTypeMapping[$match[1]]; | |
571 else | |
572 $listType = null; | |
573 | |
574 $listDepth++; | |
575 | |
576 $parts[$i + 2] = '[list' . ($listType === null ? '' : ' type=' . $listType) . ']' . "\n"; | |
577 $parts[$i + 3] = ''; | |
578 } | |
579 break; | |
580 | |
581 case 'li': | |
582 | |
583 // This is how it should be: a list item inside the list. | |
584 if ($inList) | |
585 { | |
586 $parts[$i + 2] = str_repeat("\t", $listDepth) . '[li]'; | |
587 $parts[$i + 3] = ''; | |
588 | |
589 // Within a list item, it's almost as if you're outside. | |
590 $inList = false; | |
591 } | |
592 | |
593 // The li is no direct child of a list. | |
594 else | |
595 { | |
596 // We are apparently in a list item. | |
597 if ($listDepth > 0) | |
598 { | |
599 $parts[$i + 2] = '[/li]' . "\n" . str_repeat("\t", $listDepth) . '[li]'; | |
600 $parts[$i + 3] = ''; | |
601 } | |
602 | |
603 // We're not even near a list. | |
604 else | |
605 { | |
606 // Quickly create a list with an item. | |
607 $listDepth++; | |
608 | |
609 $parts[$i + 2] = '[list]' . "\n\t" . '[li]'; | |
610 $parts[$i + 3] = ''; | |
611 } | |
612 } | |
613 | |
614 break; | |
615 } | |
616 } | |
617 | |
618 // Handle all the closing tags. | |
619 else | |
620 { | |
621 switch ($tag) | |
622 { | |
623 case 'ol': | |
624 case 'ul': | |
625 | |
626 // As we expected it, closing the list while we're in it. | |
627 if ($inList) | |
628 { | |
629 $inList = false; | |
630 | |
631 $listDepth--; | |
632 | |
633 $parts[$i + 1] = ''; | |
634 $parts[$i + 2] = str_repeat("\t", $listDepth) . '[/list]'; | |
635 $parts[$i + 3] = ''; | |
636 } | |
637 | |
638 else | |
639 { | |
640 // We're in a list item. | |
641 if ($listDepth > 0) | |
642 { | |
643 // Inject closure for this list item first. | |
644 // The content of $parts[$i] is left as is! | |
645 array_splice($parts, $i + 1, 0, array( | |
646 '', // $i + 1 | |
647 '[/li]' . "\n", // $i + 2 | |
648 '', // $i + 3 | |
649 '', // $i + 4 | |
650 )); | |
651 $numParts = count($parts) - 1; | |
652 | |
653 // Now that we've closed the li, we're in list space. | |
654 $inList = true; | |
655 } | |
656 | |
657 // We're not even in a list, ignore | |
658 else | |
659 { | |
660 $parts[$i + 1] = ''; | |
661 $parts[$i + 2] = ''; | |
662 $parts[$i + 3] = ''; | |
663 } | |
664 } | |
665 break; | |
666 | |
667 case 'li': | |
668 | |
669 if ($inList) | |
670 { | |
671 // There's no use for a </li> after <ol> or <ul>, ignore. | |
672 $parts[$i + 1] = ''; | |
673 $parts[$i + 2] = ''; | |
674 $parts[$i + 3] = ''; | |
675 } | |
676 | |
677 else | |
678 { | |
679 // Remove the trailing breaks from the list item. | |
680 $parts[$i] = preg_replace('~\s*<br\s*' . '/?' . '>\s*$~', '', $parts[$i]); | |
681 $parts[$i + 1] = ''; | |
682 $parts[$i + 2] = '[/li]' . "\n"; | |
683 $parts[$i + 3] = ''; | |
684 | |
685 // And we're back in the [list] space. | |
686 $inList = true; | |
687 } | |
688 | |
689 break; | |
690 } | |
691 } | |
692 | |
693 // If we're in the [list] space, no content is allowed. | |
694 if ($inList && trim(preg_replace('~\s*<br\s*' . '/?' . '>\s*~', '', $parts[$i + 4])) !== '') | |
695 { | |
696 // Fix it by injecting an extra list item. | |
697 array_splice($parts, $i + 4, 0, array( | |
698 '', // No content. | |
699 '', // Opening tag. | |
700 'li', // It's a <li>. | |
701 '', // No tail. | |
702 )); | |
703 $numParts = count($parts) - 1; | |
704 } | |
705 } | |
706 | |
707 $text = implode('', $parts); | |
708 | |
709 if ($inList) | |
710 { | |
711 $listDepth--; | |
712 $text .= str_repeat("\t", $listDepth) . '[/list]'; | |
713 } | |
714 | |
715 for ($i = $listDepth; $i > 0; $i--) | |
716 $text .= '[/li]' . "\n" . str_repeat("\t", $i - 1) . '[/list]'; | |
717 | |
718 } | |
719 | |
720 // I love my own image... | |
721 while (preg_match('~<img\s+([^<>]*)/*>~i', $text, $matches) === 1) | |
722 { | |
723 // Find the position of the image. | |
724 $start_pos = strpos($text, $matches[0]); | |
725 if ($start_pos === false) | |
726 break; | |
727 $end_pos = $start_pos + strlen($matches[0]); | |
728 | |
729 $params = ''; | |
730 $had_params = array(); | |
731 $src = ''; | |
732 | |
733 $attrs = fetchTagAttributes($matches[1]); | |
734 foreach ($attrs as $attrib => $value) | |
735 { | |
736 if (in_array($attrib, array('width', 'height'))) | |
737 $params .= ' ' . $attrib . '=' . (int) $value; | |
738 elseif ($attrib == 'alt' && trim($value) != '') | |
739 $params .= ' alt=' . trim($value); | |
740 elseif ($attrib == 'src') | |
741 $src = trim($value); | |
742 } | |
743 | |
744 $tag = ''; | |
745 if (!empty($src)) | |
746 { | |
747 // Attempt to fix the path in case it's not present. | |
748 if (preg_match('~^https?://~i', $src) === 0 && is_array($parsedURL = parse_url($scripturl)) && isset($parsedURL['host'])) | |
749 { | |
750 $baseURL = (isset($parsedURL['scheme']) ? $parsedURL['scheme'] : 'http') . '://' . $parsedURL['host'] . (empty($parsedURL['port']) ? '' : ':' . $parsedURL['port']); | |
751 | |
752 if (substr($src, 0, 1) === '/') | |
753 $src = $baseURL . $src; | |
754 else | |
755 $src = $baseURL . (empty($parsedURL['path']) ? '/' : preg_replace('~/(?:index\\.php)?$~', '', $parsedURL['path'])) . '/' . $src; | |
756 } | |
757 | |
758 $tag = '[img' . $params . ']' . $src . '[/img]'; | |
759 } | |
760 | |
761 // Replace the tag | |
762 $text = substr($text, 0, $start_pos) . $tag . substr($text, $end_pos); | |
763 } | |
764 | |
765 // The final bits are the easy ones - tags which map to tags which map to tags - etc etc. | |
766 $tags = array( | |
767 '~<b(\s(.)*?)*?' . '>~i' => '[b]', | |
768 '~</b>~i' => '[/b]', | |
769 '~<i(\s(.)*?)*?' . '>~i' => '[i]', | |
770 '~</i>~i' => '[/i]', | |
771 '~<u(\s(.)*?)*?' . '>~i' => '[u]', | |
772 '~</u>~i' => '[/u]', | |
773 '~<strong(\s(.)*?)*?' . '>~i' => '[b]', | |
774 '~</strong>~i' => '[/b]', | |
775 '~<em(\s(.)*?)*?' . '>~i' => '[i]', | |
776 '~</em>~i' => '[/i]', | |
777 '~<s(\s(.)*?)*?' . '>~i' => "[s]", | |
778 '~</s>~i' => "[/s]", | |
779 '~<strike(\s(.)*?)*?' . '>~i' => '[s]', | |
780 '~</strike>~i' => '[/s]', | |
781 '~<del(\s(.)*?)*?' . '>~i' => '[s]', | |
782 '~</del>~i' => '[/s]', | |
783 '~<center(\s(.)*?)*?' . '>~i' => '[center]', | |
784 '~</center>~i' => '[/center]', | |
785 '~<pre(\s(.)*?)*?' . '>~i' => '[pre]', | |
786 '~</pre>~i' => '[/pre]', | |
787 '~<sub(\s(.)*?)*?' . '>~i' => '[sub]', | |
788 '~</sub>~i' => '[/sub]', | |
789 '~<sup(\s(.)*?)*?' . '>~i' => '[sup]', | |
790 '~</sup>~i' => '[/sup]', | |
791 '~<tt(\s(.)*?)*?' . '>~i' => '[tt]', | |
792 '~</tt>~i' => '[/tt]', | |
793 '~<table(\s(.)*?)*?' . '>~i' => '[table]', | |
794 '~</table>~i' => '[/table]', | |
795 '~<tr(\s(.)*?)*?' . '>~i' => '[tr]', | |
796 '~</tr>~i' => '[/tr]', | |
797 '~<(td|th)\s[^<>]*?colspan="?(\d{1,2})"?.*?' . '>~ie' => 'str_repeat(\'[td][/td]\', $2 - 1) . \'[td]\'', | |
798 '~<(td|th)(\s(.)*?)*?' . '>~i' => '[td]', | |
799 '~</(td|th)>~i' => '[/td]', | |
800 '~<br(?:\s[^<>]*?)?' . '>~i' => "\n", | |
801 '~<hr[^<>]*>(\n)?~i' => "[hr]\n$1", | |
802 '~(\n)?\\[hr\\]~i' => "\n[hr]", | |
803 '~^\n\\[hr\\]~i' => "[hr]", | |
804 '~<blockquote(\s(.)*?)*?' . '>~i' => "<blockquote>", | |
805 '~</blockquote>~i' => "</blockquote>", | |
806 '~<ins(\s(.)*?)*?' . '>~i' => "<ins>", | |
807 '~</ins>~i' => "</ins>", | |
808 ); | |
809 $text = preg_replace(array_keys($tags), array_values($tags), $text); | |
810 | |
811 // Please give us just a little more time. | |
812 if (connection_aborted() && $context['server']['is_apache']) | |
813 @apache_reset_timeout(); | |
814 | |
815 // What about URL's - the pain in the ass of the tag world. | |
816 while (preg_match('~<a\s+([^<>]*)>([^<>]*)</a>~i', $text, $matches) === 1) | |
817 { | |
818 // Find the position of the URL. | |
819 $start_pos = strpos($text, $matches[0]); | |
820 if ($start_pos === false) | |
821 break; | |
822 $end_pos = $start_pos + strlen($matches[0]); | |
823 | |
824 $tag_type = 'url'; | |
825 $href = ''; | |
826 | |
827 $attrs = fetchTagAttributes($matches[1]); | |
828 foreach ($attrs as $attrib => $value) | |
829 { | |
830 if ($attrib == 'href') | |
831 { | |
832 $href = trim($value); | |
833 | |
834 // Are we dealing with an FTP link? | |
835 if (preg_match('~^ftps?://~', $href) === 1) | |
836 $tag_type = 'ftp'; | |
837 | |
838 // Or is this a link to an email address? | |
839 elseif (substr($href, 0, 7) == 'mailto:') | |
840 { | |
841 $tag_type = 'email'; | |
842 $href = substr($href, 7); | |
843 } | |
844 | |
845 // No http(s), so attempt to fix this potential relative URL. | |
846 elseif (preg_match('~^https?://~i', $href) === 0 && is_array($parsedURL = parse_url($scripturl)) && isset($parsedURL['host'])) | |
847 { | |
848 $baseURL = (isset($parsedURL['scheme']) ? $parsedURL['scheme'] : 'http') . '://' . $parsedURL['host'] . (empty($parsedURL['port']) ? '' : ':' . $parsedURL['port']); | |
849 | |
850 if (substr($href, 0, 1) === '/') | |
851 $href = $baseURL . $href; | |
852 else | |
853 $href = $baseURL . (empty($parsedURL['path']) ? '/' : preg_replace('~/(?:index\\.php)?$~', '', $parsedURL['path'])) . '/' . $href; | |
854 } | |
855 } | |
856 | |
857 // External URL? | |
858 if ($attrib == 'target' && $tag_type == 'url') | |
859 { | |
860 if (trim($value) == '_blank') | |
861 $tag_type == 'iurl'; | |
862 } | |
863 } | |
864 | |
865 $tag = ''; | |
866 if ($href != '') | |
867 { | |
868 if ($matches[2] == $href) | |
869 $tag = '[' . $tag_type . ']' . $href . '[/' . $tag_type . ']'; | |
870 else | |
871 $tag = '[' . $tag_type . '=' . $href . ']' . $matches[2] . '[/' . $tag_type . ']'; | |
872 } | |
873 | |
874 // Replace the tag | |
875 $text = substr($text, 0, $start_pos) . $tag . substr($text, $end_pos); | |
876 } | |
877 | |
878 $text = strip_tags($text); | |
879 | |
880 // Some tags often end up as just dummy tags - remove those. | |
881 $text = preg_replace('~\[[bisu]\]\s*\[/[bisu]\]~', '', $text); | |
882 | |
883 // Fix up entities. | |
884 $text = preg_replace('~&~i', '&#38;', $text); | |
885 | |
886 $text = legalise_bbc($text); | |
887 | |
888 return $text; | |
889 } | |
890 | |
891 // Returns an array of attributes associated with a tag. | |
892 function fetchTagAttributes($text) | |
893 { | |
894 $attribs = array(); | |
895 $key = $value = ''; | |
896 $strpos = 0; | |
897 $tag_state = 0; // 0 = key, 1 = attribute with no string, 2 = attribute with string | |
898 for ($i = 0; $i < strlen($text); $i++) | |
899 { | |
900 // We're either moving from the key to the attribute or we're in a string and this is fine. | |
901 if ($text{$i} == '=') | |
902 { | |
903 if ($tag_state == 0) | |
904 $tag_state = 1; | |
905 elseif ($tag_state == 2) | |
906 $value .= '='; | |
907 } | |
908 // A space is either moving from an attribute back to a potential key or in a string is fine. | |
909 elseif ($text{$i} == ' ') | |
910 { | |
911 if ($tag_state == 2) | |
912 $value .= ' '; | |
913 elseif ($tag_state == 1) | |
914 { | |
915 $attribs[$key] = $value; | |
916 $key = $value = ''; | |
917 $tag_state = 0; | |
918 } | |
919 } | |
920 // A quote? | |
921 elseif ($text{$i} == '"') | |
922 { | |
923 // Must be either going into or out of a string. | |
924 if ($tag_state == 1) | |
925 $tag_state = 2; | |
926 else | |
927 $tag_state = 1; | |
928 } | |
929 // Otherwise it's fine. | |
930 else | |
931 { | |
932 if ($tag_state == 0) | |
933 $key .= $text{$i}; | |
934 else | |
935 $value .= $text{$i}; | |
936 } | |
937 } | |
938 | |
939 // Anything left? | |
940 if ($key != '' && $value != '') | |
941 $attribs[$key] = $value; | |
942 | |
943 return $attribs; | |
944 } | |
945 | |
946 function getMessageIcons($board_id) | |
947 { | |
948 global $modSettings, $context, $txt, $settings, $smcFunc; | |
949 | |
950 if (empty($modSettings['messageIcons_enable'])) | |
951 { | |
952 loadLanguage('Post'); | |
953 | |
954 $icons = array( | |
955 array('value' => 'xx', 'name' => $txt['standard']), | |
956 array('value' => 'thumbup', 'name' => $txt['thumbs_up']), | |
957 array('value' => 'thumbdown', 'name' => $txt['thumbs_down']), | |
958 array('value' => 'exclamation', 'name' => $txt['excamation_point']), | |
959 array('value' => 'question', 'name' => $txt['question_mark']), | |
960 array('value' => 'lamp', 'name' => $txt['lamp']), | |
961 array('value' => 'smiley', 'name' => $txt['icon_smiley']), | |
962 array('value' => 'angry', 'name' => $txt['icon_angry']), | |
963 array('value' => 'cheesy', 'name' => $txt['icon_cheesy']), | |
964 array('value' => 'grin', 'name' => $txt['icon_grin']), | |
965 array('value' => 'sad', 'name' => $txt['icon_sad']), | |
966 array('value' => 'wink', 'name' => $txt['icon_wink']) | |
967 ); | |
968 | |
969 foreach ($icons as $k => $dummy) | |
970 { | |
971 $icons[$k]['url'] = $settings['images_url'] . '/post/' . $dummy['value'] . '.gif'; | |
972 $icons[$k]['is_last'] = false; | |
973 } | |
974 } | |
975 // Otherwise load the icons, and check we give the right image too... | |
976 else | |
977 { | |
978 if (($temp = cache_get_data('posting_icons-' . $board_id, 480)) == null) | |
979 { | |
980 $request = $smcFunc['db_query']('select_message_icons', ' | |
981 SELECT title, filename | |
982 FROM {db_prefix}message_icons | |
983 WHERE id_board IN (0, {int:board_id})', | |
984 array( | |
985 'board_id' => $board_id, | |
986 ) | |
987 ); | |
988 $icon_data = array(); | |
989 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
990 $icon_data[] = $row; | |
991 $smcFunc['db_free_result']($request); | |
992 | |
993 cache_put_data('posting_icons-' . $board_id, $icon_data, 480); | |
994 } | |
995 else | |
996 $icon_data = $temp; | |
997 | |
998 $icons = array(); | |
999 foreach ($icon_data as $icon) | |
1000 { | |
1001 $icons[$icon['filename']] = array( | |
1002 'value' => $icon['filename'], | |
1003 'name' => $icon['title'], | |
1004 'url' => $settings[file_exists($settings['theme_dir'] . '/images/post/' . $icon['filename'] . '.gif') ? 'images_url' : 'default_images_url'] . '/post/' . $icon['filename'] . '.gif', | |
1005 'is_last' => false, | |
1006 ); | |
1007 } | |
1008 } | |
1009 | |
1010 return array_values($icons); | |
1011 } | |
1012 | |
1013 // This is an important yet frustrating function - it attempts to clean up illegal BBC caused by browsers like Opera which don't obey the rules!!! | |
1014 function legalise_bbc($text) | |
1015 { | |
1016 global $modSettings; | |
1017 | |
1018 // Don't care about the texts that are too short. | |
1019 if (strlen($text) < 3) | |
1020 return $text; | |
1021 | |
1022 // We are going to cycle through the BBC and keep track of tags as they arise - in order. If get to a block level tag we're going to make sure it's not in a non-block level tag! | |
1023 // This will keep the order of tags that are open. | |
1024 $current_tags = array(); | |
1025 | |
1026 // This will quickly let us see if the tag is active. | |
1027 $active_tags = array(); | |
1028 | |
1029 // A list of tags that's disabled by the admin. | |
1030 $disabled = empty($modSettings['disabledBBC']) ? array() : array_flip(explode(',', strtolower($modSettings['disabledBBC']))); | |
1031 | |
1032 // Add flash if it's disabled as embedded tag. | |
1033 if (empty($modSettings['enableEmbeddedFlash'])) | |
1034 $disabled['flash'] = true; | |
1035 | |
1036 // Get a list of all the tags that are not disabled. | |
1037 $all_tags = parse_bbc(false); | |
1038 $valid_tags = array(); | |
1039 $self_closing_tags = array(); | |
1040 foreach ($all_tags as $tag) | |
1041 { | |
1042 if (!isset($disabled[$tag['tag']])) | |
1043 $valid_tags[$tag['tag']] = !empty($tag['block_level']); | |
1044 if (isset($tag['type']) && $tag['type'] == 'closed') | |
1045 $self_closing_tags[] = $tag['tag']; | |
1046 } | |
1047 | |
1048 // Don't worry if we're in a code/nobbc. | |
1049 $in_code_nobbc = false; | |
1050 | |
1051 // 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! | |
1052 $align_tags = array('left', 'center', 'right', 'pre'); | |
1053 | |
1054 // Remove those align tags that are not valid. | |
1055 $align_tags = array_intersect($align_tags, array_keys($valid_tags)); | |
1056 | |
1057 // These keep track of where we are! | |
1058 if (!empty($align_tags) && count($matches = preg_split('~(\\[/?(?:' . implode('|', $align_tags) . ')\\])~', $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) | |
1059 { | |
1060 // The first one is never a tag. | |
1061 $isTag = false; | |
1062 | |
1063 // By default we're not inside a tag too. | |
1064 $insideTag = null; | |
1065 | |
1066 foreach ($matches as $i => $match) | |
1067 { | |
1068 // We're only interested in tags, not text. | |
1069 if ($isTag) | |
1070 { | |
1071 $isClosingTag = substr($match, 1, 1) === '/'; | |
1072 $tagName = substr($match, $isClosingTag ? 2 : 1, -1); | |
1073 | |
1074 // We're closing the exact same tag that we opened. | |
1075 if ($isClosingTag && $insideTag === $tagName) | |
1076 $insideTag = null; | |
1077 | |
1078 // We're opening a tag and we're not yet inside one either | |
1079 elseif (!$isClosingTag && $insideTag === null) | |
1080 $insideTag = $tagName; | |
1081 | |
1082 // In all other cases, this tag must be invalid | |
1083 else | |
1084 unset($matches[$i]); | |
1085 } | |
1086 | |
1087 // The next one is gonna be the other one. | |
1088 $isTag = !$isTag; | |
1089 } | |
1090 | |
1091 // We're still inside a tag and had no chance for closure? | |
1092 if ($insideTag !== null) | |
1093 $matches[] = '[/' . $insideTag . ']'; | |
1094 | |
1095 // And a complete text string again. | |
1096 $text = implode('', $matches); | |
1097 } | |
1098 | |
1099 // Quickly remove any tags which are back to back. | |
1100 $backToBackPattern = '~\\[(' . implode('|', array_diff(array_keys($valid_tags), array('td', 'anchor'))) . ')[^<>\\[\\]]*\\]\s*\\[/\\1\\]~'; | |
1101 $lastlen = 0; | |
1102 while (strlen($text) !== $lastlen) | |
1103 $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text)); | |
1104 | |
1105 // Need to sort the tags my name length. | |
1106 uksort($valid_tags, 'sort_array_length'); | |
1107 | |
1108 // These inline tags can compete with each other regarding style. | |
1109 $competing_tags = array( | |
1110 'color', | |
1111 'size', | |
1112 ); | |
1113 | |
1114 // In case things changed above set these back to normal. | |
1115 $in_code_nobbc = false; | |
1116 $new_text_offset = 0; | |
1117 | |
1118 // These keep track of where we are! | |
1119 if (count($parts = preg_split(sprintf('~(\\[)(/?)(%1$s)((?:[\\s=][^\\]\\[]*)?\\])~', implode('|', array_keys($valid_tags))), $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1) | |
1120 { | |
1121 // Start with just text. | |
1122 $isTag = false; | |
1123 | |
1124 // Start outside [nobbc] or [code] blocks. | |
1125 $inCode = false; | |
1126 $inNoBbc = false; | |
1127 | |
1128 // A buffer containing all opened inline elements. | |
1129 $inlineElements = array(); | |
1130 | |
1131 // A buffer containing all opened block elements. | |
1132 $blockElements = array(); | |
1133 | |
1134 // A buffer containing the opened inline elements that might compete. | |
1135 $competingElements = array(); | |
1136 | |
1137 // $i: text, $i + 1: '[', $i + 2: '/', $i + 3: tag, $i + 4: tag tail. | |
1138 for ($i = 0, $n = count($parts) - 1; $i < $n; $i += 5) | |
1139 { | |
1140 $tag = $parts[$i + 3]; | |
1141 $isOpeningTag = $parts[$i + 2] === ''; | |
1142 $isClosingTag = $parts[$i + 2] === '/'; | |
1143 $isBlockLevelTag = isset($valid_tags[$tag]) && $valid_tags[$tag] && !in_array($tag, $self_closing_tags); | |
1144 $isCompetingTag = in_array($tag, $competing_tags); | |
1145 | |
1146 // Check if this might be one of those cleaned out tags. | |
1147 if ($tag === '') | |
1148 continue; | |
1149 | |
1150 // Special case: inside [code] blocks any code is left untouched. | |
1151 elseif ($tag === 'code') | |
1152 { | |
1153 // We're inside a code block and closing it. | |
1154 if ($inCode && $isClosingTag) | |
1155 { | |
1156 $inCode = false; | |
1157 | |
1158 // Reopen tags that were closed before the code block. | |
1159 if (!empty($inlineElements)) | |
1160 $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']'; | |
1161 } | |
1162 | |
1163 // We're outside a coding and nobbc block and opening it. | |
1164 elseif (!$inCode && !$inNoBbc && $isOpeningTag) | |
1165 { | |
1166 // If there are still inline elements left open, close them now. | |
1167 if (!empty($inlineElements)) | |
1168 { | |
1169 $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; | |
1170 //$inlineElements = array(); | |
1171 } | |
1172 | |
1173 $inCode = true; | |
1174 } | |
1175 | |
1176 // Nothing further to do. | |
1177 continue; | |
1178 } | |
1179 | |
1180 // Special case: inside [nobbc] blocks any BBC is left untouched. | |
1181 elseif ($tag === 'nobbc') | |
1182 { | |
1183 // We're inside a nobbc block and closing it. | |
1184 if ($inNoBbc && $isClosingTag) | |
1185 { | |
1186 $inNoBbc = false; | |
1187 | |
1188 // Some inline elements might've been closed that need reopening. | |
1189 if (!empty($inlineElements)) | |
1190 $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']'; | |
1191 } | |
1192 | |
1193 // We're outside a nobbc and coding block and opening it. | |
1194 elseif (!$inNoBbc && !$inCode && $isOpeningTag) | |
1195 { | |
1196 // Can't have inline elements still opened. | |
1197 if (!empty($inlineElements)) | |
1198 { | |
1199 $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; | |
1200 //$inlineElements = array(); | |
1201 } | |
1202 | |
1203 $inNoBbc = true; | |
1204 } | |
1205 | |
1206 continue; | |
1207 } | |
1208 | |
1209 // So, we're inside one of the special blocks: ignore any tag. | |
1210 elseif ($inCode || $inNoBbc) | |
1211 continue; | |
1212 | |
1213 // We're dealing with an opening tag. | |
1214 if ($isOpeningTag) | |
1215 { | |
1216 // Everyting inside the square brackets of the opening tag. | |
1217 $elementContent = $parts[$i + 3] . substr($parts[$i + 4], 0, -1); | |
1218 | |
1219 // A block level opening tag. | |
1220 if ($isBlockLevelTag) | |
1221 { | |
1222 // Are there inline elements still open? | |
1223 if (!empty($inlineElements)) | |
1224 { | |
1225 // Close all the inline tags, a block tag is coming... | |
1226 $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; | |
1227 | |
1228 // Now open them again, we're inside the block tag now. | |
1229 $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5]; | |
1230 } | |
1231 | |
1232 $blockElements[] = $tag; | |
1233 } | |
1234 | |
1235 // Inline opening tag. | |
1236 elseif (!in_array($tag, $self_closing_tags)) | |
1237 { | |
1238 // Can't have two opening elements with the same contents! | |
1239 if (isset($inlineElements[$elementContent])) | |
1240 { | |
1241 // Get rid of this tag. | |
1242 $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; | |
1243 | |
1244 // Now try to find the corresponding closing tag. | |
1245 $curLevel = 1; | |
1246 for ($j = $i + 5, $m = count($parts) - 1; $j < $m; $j += 5) | |
1247 { | |
1248 // Find the tags with the same tagname | |
1249 if ($parts[$j + 3] === $tag) | |
1250 { | |
1251 // If it's an opening tag, increase the level. | |
1252 if ($parts[$j + 2] === '') | |
1253 $curLevel++; | |
1254 | |
1255 // A closing tag, decrease the level. | |
1256 else | |
1257 { | |
1258 $curLevel--; | |
1259 | |
1260 // Gotcha! Clean out this closing tag gone rogue. | |
1261 if ($curLevel === 0) | |
1262 { | |
1263 $parts[$j + 1] = $parts[$j + 2] = $parts[$j + 3] = $parts[$j + 4] = ''; | |
1264 break; | |
1265 } | |
1266 } | |
1267 } | |
1268 } | |
1269 } | |
1270 | |
1271 // Otherwise, add this one to the list. | |
1272 else | |
1273 { | |
1274 if ($isCompetingTag) | |
1275 { | |
1276 if (!isset($competingElements[$tag])) | |
1277 $competingElements[$tag] = array(); | |
1278 | |
1279 $competingElements[$tag][] = $parts[$i + 4]; | |
1280 | |
1281 if (count($competingElements[$tag]) > 1) | |
1282 $parts[$i] .= '[/' . $tag . ']'; | |
1283 } | |
1284 | |
1285 $inlineElements[$elementContent] = $tag; | |
1286 } | |
1287 } | |
1288 | |
1289 } | |
1290 | |
1291 // Closing tag. | |
1292 else | |
1293 { | |
1294 // Closing the block tag. | |
1295 if ($isBlockLevelTag) | |
1296 { | |
1297 // Close the elements that should've been closed by closing this tag. | |
1298 if (!empty($blockElements)) | |
1299 { | |
1300 $addClosingTags = array(); | |
1301 while ($element = array_pop($blockElements)) | |
1302 { | |
1303 if ($element === $tag) | |
1304 break; | |
1305 | |
1306 // Still a block tag was open not equal to this tag. | |
1307 $addClosingTags[] = $element['type']; | |
1308 } | |
1309 | |
1310 if (!empty($addClosingTags)) | |
1311 $parts[$i + 1] = '[/' . implode('][/', array_reverse($addClosingTags)) . ']' . $parts[$i + 1]; | |
1312 | |
1313 // Apparently the closing tag was not found on the stack. | |
1314 if (!is_string($element) || $element !== $tag) | |
1315 { | |
1316 // Get rid of this particular closing tag, it was never opened. | |
1317 $parts[$i + 1] = substr($parts[$i + 1], 0, -1); | |
1318 $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; | |
1319 continue; | |
1320 } | |
1321 } | |
1322 else | |
1323 { | |
1324 // Get rid of this closing tag! | |
1325 $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; | |
1326 continue; | |
1327 } | |
1328 | |
1329 // Inline elements are still left opened? | |
1330 if (!empty($inlineElements)) | |
1331 { | |
1332 // Close them first.. | |
1333 $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; | |
1334 | |
1335 // Then reopen them. | |
1336 $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5]; | |
1337 } | |
1338 } | |
1339 // Inline tag. | |
1340 else | |
1341 { | |
1342 // Are we expecting this tag to end? | |
1343 if (in_array($tag, $inlineElements)) | |
1344 { | |
1345 foreach (array_reverse($inlineElements, true) as $tagContentToBeClosed => $tagToBeClosed) | |
1346 { | |
1347 // Closing it one way or the other. | |
1348 unset($inlineElements[$tagContentToBeClosed]); | |
1349 | |
1350 // Was this the tag we were looking for? | |
1351 if ($tagToBeClosed === $tag) | |
1352 break; | |
1353 | |
1354 // Nope, close it and look further! | |
1355 else | |
1356 $parts[$i] .= '[/' . $tagToBeClosed . ']'; | |
1357 } | |
1358 | |
1359 if ($isCompetingTag && !empty($competingElements[$tag])) | |
1360 { | |
1361 array_pop($competingElements[$tag]); | |
1362 | |
1363 if (count($competingElements[$tag]) > 0) | |
1364 $parts[$i + 5] = '[' . $tag . $competingElements[$tag][count($competingElements[$tag]) - 1] . $parts[$i + 5]; | |
1365 } | |
1366 } | |
1367 | |
1368 // Unexpected closing tag, ex-ter-mi-nate. | |
1369 else | |
1370 $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = ''; | |
1371 } | |
1372 } | |
1373 } | |
1374 | |
1375 // Close the code tags. | |
1376 if ($inCode) | |
1377 $parts[$i] .= '[/code]'; | |
1378 | |
1379 // The same for nobbc tags. | |
1380 elseif ($inNoBbc) | |
1381 $parts[$i] .= '[/nobbc]'; | |
1382 | |
1383 // Still inline tags left unclosed? Close them now, better late than never. | |
1384 elseif (!empty($inlineElements)) | |
1385 $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']'; | |
1386 | |
1387 // Now close the block elements. | |
1388 if (!empty($blockElements)) | |
1389 $parts[$i] .= '[/' . implode('][/', array_reverse($blockElements)) . ']'; | |
1390 | |
1391 $text = implode('', $parts); | |
1392 } | |
1393 | |
1394 // Final clean up of back to back tags. | |
1395 $lastlen = 0; | |
1396 while (strlen($text) !== $lastlen) | |
1397 $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text)); | |
1398 | |
1399 return $text; | |
1400 } | |
1401 | |
1402 // A help function for legalise_bbc for sorting arrays based on length. | |
1403 function sort_array_length($a, $b) | |
1404 { | |
1405 return strlen($a) < strlen($b) ? 1 : -1; | |
1406 } | |
1407 | |
1408 // Compatibility function - used in 1.1 for showing a post box. | |
1409 function theme_postbox($msg) | |
1410 { | |
1411 global $context; | |
1412 | |
1413 return template_control_richedit($context['post_box_name']); | |
1414 } | |
1415 | |
1416 // Creates a box that can be used for richedit stuff like BBC, Smileys etc. | |
1417 function create_control_richedit($editorOptions) | |
1418 { | |
1419 global $txt, $modSettings, $options, $smcFunc; | |
1420 global $context, $settings, $user_info, $sourcedir, $scripturl; | |
1421 | |
1422 // Load the Post language file... for the moment at least. | |
1423 loadLanguage('Post'); | |
1424 | |
1425 // Every control must have a ID! | |
1426 assert(isset($editorOptions['id'])); | |
1427 assert(isset($editorOptions['value'])); | |
1428 | |
1429 // Is this the first richedit - if so we need to ensure some template stuff is initialised. | |
1430 if (empty($context['controls']['richedit'])) | |
1431 { | |
1432 // Some general stuff. | |
1433 $settings['smileys_url'] = $modSettings['smileys_url'] . '/' . $user_info['smiley_set']; | |
1434 | |
1435 // This really has some WYSIWYG stuff. | |
1436 loadTemplate('GenericControls', $context['browser']['is_ie'] ? 'editor_ie' : 'editor'); | |
1437 $context['html_headers'] .= ' | |
1438 <script type="text/javascript"><!-- // --><![CDATA[ | |
1439 var smf_smileys_url = \'' . $settings['smileys_url'] . '\'; | |
1440 var oEditorStrings= { | |
1441 wont_work: \'' . addcslashes($txt['rich_edit_wont_work'], "'") . '\', | |
1442 func_disabled: \'' . addcslashes($txt['rich_edit_function_disabled'], "'") . '\', | |
1443 prompt_text_email: \'' . addcslashes($txt['prompt_text_email'], "'") . '\', | |
1444 prompt_text_ftp: \'' . addcslashes($txt['prompt_text_ftp'], "'") . '\', | |
1445 prompt_text_url: \'' . addcslashes($txt['prompt_text_url'], "'") . '\', | |
1446 prompt_text_img: \'' . addcslashes($txt['prompt_text_img'], "'") . '\' | |
1447 } | |
1448 // ]]></script> | |
1449 <script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/editor.js?fin20"></script>'; | |
1450 | |
1451 $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new'); | |
1452 if ($context['show_spellchecking']) | |
1453 { | |
1454 $context['html_headers'] .= ' | |
1455 <script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/spellcheck.js"></script>'; | |
1456 | |
1457 // Some hidden information is needed in order to make the spell checking work. | |
1458 if (!isset($_REQUEST['xml'])) | |
1459 $context['insert_after_template'] .= ' | |
1460 <form name="spell_form" id="spell_form" method="post" accept-charset="' . $context['character_set'] . '" target="spellWindow" action="' . $scripturl . '?action=spellcheck"> | |
1461 <input type="hidden" name="spellstring" value="" /> | |
1462 </form>'; | |
1463 | |
1464 // Also make sure that spell check works with rich edit. | |
1465 $context['html_headers'] .= ' | |
1466 <script type="text/javascript"><!-- // --><![CDATA[ | |
1467 function spellCheckDone() | |
1468 { | |
1469 for (i = 0; i < smf_editorArray.length; i++) | |
1470 setTimeout("smf_editorArray[" + i + "].spellCheckEnd()", 150); | |
1471 } | |
1472 // ]]></script>'; | |
1473 } | |
1474 } | |
1475 | |
1476 // Start off the editor... | |
1477 $context['controls']['richedit'][$editorOptions['id']] = array( | |
1478 'id' => $editorOptions['id'], | |
1479 'value' => $editorOptions['value'], | |
1480 'rich_value' => bbc_to_html($editorOptions['value']), | |
1481 'rich_active' => empty($modSettings['disable_wysiwyg']) && (!empty($options['wysiwyg_default']) || !empty($editorOptions['force_rich']) || !empty($_REQUEST[$editorOptions['id'] . '_mode'])), | |
1482 'disable_smiley_box' => !empty($editorOptions['disable_smiley_box']), | |
1483 'columns' => isset($editorOptions['columns']) ? $editorOptions['columns'] : 60, | |
1484 'rows' => isset($editorOptions['rows']) ? $editorOptions['rows'] : 12, | |
1485 'width' => isset($editorOptions['width']) ? $editorOptions['width'] : '70%', | |
1486 'height' => isset($editorOptions['height']) ? $editorOptions['height'] : '150px', | |
1487 'form' => isset($editorOptions['form']) ? $editorOptions['form'] : 'postmodify', | |
1488 'bbc_level' => !empty($editorOptions['bbc_level']) ? $editorOptions['bbc_level'] : 'full', | |
1489 'preview_type' => isset($editorOptions['preview_type']) ? (int) $editorOptions['preview_type'] : 1, | |
1490 'labels' => !empty($editorOptions['labels']) ? $editorOptions['labels'] : array(), | |
1491 ); | |
1492 | |
1493 // Switch between default images and back... mostly in case you don't have an PersonalMessage template, but do have a Post template. | |
1494 if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) | |
1495 { | |
1496 $temp1 = $settings['theme_url']; | |
1497 $settings['theme_url'] = $settings['default_theme_url']; | |
1498 | |
1499 $temp2 = $settings['images_url']; | |
1500 $settings['images_url'] = $settings['default_images_url']; | |
1501 | |
1502 $temp3 = $settings['theme_dir']; | |
1503 $settings['theme_dir'] = $settings['default_theme_dir']; | |
1504 } | |
1505 | |
1506 if (empty($context['bbc_tags'])) | |
1507 { | |
1508 // 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! | |
1509 $context['bbc_tags'] = array(); | |
1510 $context['bbc_tags'][] = array( | |
1511 array( | |
1512 'image' => 'bold', | |
1513 'code' => 'b', | |
1514 'before' => '[b]', | |
1515 'after' => '[/b]', | |
1516 'description' => $txt['bold'], | |
1517 ), | |
1518 array( | |
1519 'image' => 'italicize', | |
1520 'code' => 'i', | |
1521 'before' => '[i]', | |
1522 'after' => '[/i]', | |
1523 'description' => $txt['italic'], | |
1524 ), | |
1525 array( | |
1526 'image' => 'underline', | |
1527 'code' => 'u', | |
1528 'before' => '[u]', | |
1529 'after' => '[/u]', | |
1530 'description' => $txt['underline'] | |
1531 ), | |
1532 array( | |
1533 'image' => 'strike', | |
1534 'code' => 's', | |
1535 'before' => '[s]', | |
1536 'after' => '[/s]', | |
1537 'description' => $txt['strike'] | |
1538 ), | |
1539 array(), | |
1540 array( | |
1541 'image' => 'pre', | |
1542 'code' => 'pre', | |
1543 'before' => '[pre]', | |
1544 'after' => '[/pre]', | |
1545 'description' => $txt['preformatted'] | |
1546 ), | |
1547 array( | |
1548 'image' => 'left', | |
1549 'code' => 'left', | |
1550 'before' => '[left]', | |
1551 'after' => '[/left]', | |
1552 'description' => $txt['left_align'] | |
1553 ), | |
1554 array( | |
1555 'image' => 'center', | |
1556 'code' => 'center', | |
1557 'before' => '[center]', | |
1558 'after' => '[/center]', | |
1559 'description' => $txt['center'] | |
1560 ), | |
1561 array( | |
1562 'image' => 'right', | |
1563 'code' => 'right', | |
1564 'before' => '[right]', | |
1565 'after' => '[/right]', | |
1566 'description' => $txt['right_align'] | |
1567 ), | |
1568 ); | |
1569 $context['bbc_tags'][] = array( | |
1570 array( | |
1571 'image' => 'flash', | |
1572 'code' => 'flash', | |
1573 'before' => '[flash=200,200]', | |
1574 'after' => '[/flash]', | |
1575 'description' => $txt['flash'] | |
1576 ), | |
1577 array( | |
1578 'image' => 'img', | |
1579 'code' => 'img', | |
1580 'before' => '[img]', | |
1581 'after' => '[/img]', | |
1582 'description' => $txt['image'] | |
1583 ), | |
1584 array( | |
1585 'image' => 'url', | |
1586 'code' => 'url', | |
1587 'before' => '[url]', | |
1588 'after' => '[/url]', | |
1589 'description' => $txt['hyperlink'] | |
1590 ), | |
1591 array( | |
1592 'image' => 'email', | |
1593 'code' => 'email', | |
1594 'before' => '[email]', | |
1595 'after' => '[/email]', | |
1596 'description' => $txt['insert_email'] | |
1597 ), | |
1598 array( | |
1599 'image' => 'ftp', | |
1600 'code' => 'ftp', | |
1601 'before' => '[ftp]', | |
1602 'after' => '[/ftp]', | |
1603 'description' => $txt['ftp'] | |
1604 ), | |
1605 array(), | |
1606 array( | |
1607 'image' => 'glow', | |
1608 'code' => 'glow', | |
1609 'before' => '[glow=red,2,300]', | |
1610 'after' => '[/glow]', | |
1611 'description' => $txt['glow'] | |
1612 ), | |
1613 array( | |
1614 'image' => 'shadow', | |
1615 'code' => 'shadow', | |
1616 'before' => '[shadow=red,left]', | |
1617 'after' => '[/shadow]', | |
1618 'description' => $txt['shadow'] | |
1619 ), | |
1620 array( | |
1621 'image' => 'move', | |
1622 'code' => 'move', | |
1623 'before' => '[move]', | |
1624 'after' => '[/move]', | |
1625 'description' => $txt['marquee'] | |
1626 ), | |
1627 array(), | |
1628 array( | |
1629 'image' => 'sup', | |
1630 'code' => 'sup', | |
1631 'before' => '[sup]', | |
1632 'after' => '[/sup]', | |
1633 'description' => $txt['superscript'] | |
1634 ), | |
1635 array( | |
1636 'image' => 'sub', | |
1637 'code' => 'sub', | |
1638 'before' => '[sub]', | |
1639 'after' => '[/sub]', | |
1640 'description' => $txt['subscript'] | |
1641 ), | |
1642 array( | |
1643 'image' => 'tele', | |
1644 'code' => 'tt', | |
1645 'before' => '[tt]', | |
1646 'after' => '[/tt]', | |
1647 'description' => $txt['teletype'] | |
1648 ), | |
1649 array(), | |
1650 array( | |
1651 'image' => 'table', | |
1652 'code' => 'table', | |
1653 'before' => '[table]\n[tr]\n[td]', | |
1654 'after' => '[/td]\n[/tr]\n[/table]', | |
1655 'description' => $txt['table'] | |
1656 ), | |
1657 array( | |
1658 'image' => 'code', | |
1659 'code' => 'code', | |
1660 'before' => '[code]', | |
1661 'after' => '[/code]', | |
1662 'description' => $txt['bbc_code'] | |
1663 ), | |
1664 array( | |
1665 'image' => 'quote', | |
1666 'code' => 'quote', | |
1667 'before' => '[quote]', | |
1668 'after' => '[/quote]', | |
1669 'description' => $txt['bbc_quote'] | |
1670 ), | |
1671 array(), | |
1672 array( | |
1673 'image' => 'list', | |
1674 'code' => 'list', | |
1675 'before' => '[list]\n[li]', | |
1676 'after' => '[/li]\n[li][/li]\n[/list]', | |
1677 'description' => $txt['list_unordered'] | |
1678 ), | |
1679 array( | |
1680 'image' => 'orderlist', | |
1681 'code' => 'orderlist', | |
1682 'before' => '[list type=decimal]\n[li]', | |
1683 'after' => '[/li]\n[li][/li]\n[/list]', | |
1684 'description' => $txt['list_ordered'] | |
1685 ), | |
1686 array( | |
1687 'image' => 'hr', | |
1688 'code' => 'hr', | |
1689 'before' => '[hr]', | |
1690 'description' => $txt['horizontal_rule'] | |
1691 ), | |
1692 ); | |
1693 | |
1694 // Allow mods to modify BBC buttons. | |
1695 call_integration_hook('integrate_bbc_buttons', array(&$context['bbc_tags'])); | |
1696 | |
1697 // Show the toggle? | |
1698 if (empty($modSettings['disable_wysiwyg'])) | |
1699 { | |
1700 $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array(); | |
1701 $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array( | |
1702 'image' => 'unformat', | |
1703 'code' => 'unformat', | |
1704 'before' => '', | |
1705 'description' => $txt['unformat_text'], | |
1706 ); | |
1707 $context['bbc_tags'][count($context['bbc_tags']) - 1][] = array( | |
1708 'image' => 'toggle', | |
1709 'code' => 'toggle', | |
1710 'before' => '', | |
1711 'description' => $txt['toggle_view'], | |
1712 ); | |
1713 } | |
1714 | |
1715 foreach ($context['bbc_tags'] as $row => $tagRow) | |
1716 $context['bbc_tags'][$row][count($tagRow) - 1]['isLast'] = true; | |
1717 } | |
1718 | |
1719 // Initialize smiley array... if not loaded before. | |
1720 if (empty($context['smileys']) && empty($editorOptions['disable_smiley_box'])) | |
1721 { | |
1722 $context['smileys'] = array( | |
1723 'postform' => array(), | |
1724 'popup' => array(), | |
1725 ); | |
1726 | |
1727 // Load smileys - don't bother to run a query if we're not using the database's ones anyhow. | |
1728 if (empty($modSettings['smiley_enable']) && $user_info['smiley_set'] != 'none') | |
1729 $context['smileys']['postform'][] = array( | |
1730 'smileys' => array( | |
1731 array( | |
1732 'code' => ':)', | |
1733 'filename' => 'smiley.gif', | |
1734 'description' => $txt['icon_smiley'], | |
1735 ), | |
1736 array( | |
1737 'code' => ';)', | |
1738 'filename' => 'wink.gif', | |
1739 'description' => $txt['icon_wink'], | |
1740 ), | |
1741 array( | |
1742 'code' => ':D', | |
1743 'filename' => 'cheesy.gif', | |
1744 'description' => $txt['icon_cheesy'], | |
1745 ), | |
1746 array( | |
1747 'code' => ';D', | |
1748 'filename' => 'grin.gif', | |
1749 'description' => $txt['icon_grin'] | |
1750 ), | |
1751 array( | |
1752 'code' => '>:(', | |
1753 'filename' => 'angry.gif', | |
1754 'description' => $txt['icon_angry'], | |
1755 ), | |
1756 array( | |
1757 'code' => ':(', | |
1758 'filename' => 'sad.gif', | |
1759 'description' => $txt['icon_sad'], | |
1760 ), | |
1761 array( | |
1762 'code' => ':o', | |
1763 'filename' => 'shocked.gif', | |
1764 'description' => $txt['icon_shocked'], | |
1765 ), | |
1766 array( | |
1767 'code' => '8)', | |
1768 'filename' => 'cool.gif', | |
1769 'description' => $txt['icon_cool'], | |
1770 ), | |
1771 array( | |
1772 'code' => '???', | |
1773 'filename' => 'huh.gif', | |
1774 'description' => $txt['icon_huh'], | |
1775 ), | |
1776 array( | |
1777 'code' => '::)', | |
1778 'filename' => 'rolleyes.gif', | |
1779 'description' => $txt['icon_rolleyes'], | |
1780 ), | |
1781 array( | |
1782 'code' => ':P', | |
1783 'filename' => 'tongue.gif', | |
1784 'description' => $txt['icon_tongue'], | |
1785 ), | |
1786 array( | |
1787 'code' => ':-[', | |
1788 'filename' => 'embarrassed.gif', | |
1789 'description' => $txt['icon_embarrassed'], | |
1790 ), | |
1791 array( | |
1792 'code' => ':-X', | |
1793 'filename' => 'lipsrsealed.gif', | |
1794 'description' => $txt['icon_lips'], | |
1795 ), | |
1796 array( | |
1797 'code' => ':-\\', | |
1798 'filename' => 'undecided.gif', | |
1799 'description' => $txt['icon_undecided'], | |
1800 ), | |
1801 array( | |
1802 'code' => ':-*', | |
1803 'filename' => 'kiss.gif', | |
1804 'description' => $txt['icon_kiss'], | |
1805 ), | |
1806 array( | |
1807 'code' => ':\'(', | |
1808 'filename' => 'cry.gif', | |
1809 'description' => $txt['icon_cry'], | |
1810 'isLast' => true, | |
1811 ), | |
1812 ), | |
1813 'isLast' => true, | |
1814 ); | |
1815 elseif ($user_info['smiley_set'] != 'none') | |
1816 { | |
1817 if (($temp = cache_get_data('posting_smileys', 480)) == null) | |
1818 { | |
1819 $request = $smcFunc['db_query']('', ' | |
1820 SELECT code, filename, description, smiley_row, hidden | |
1821 FROM {db_prefix}smileys | |
1822 WHERE hidden IN (0, 2) | |
1823 ORDER BY smiley_row, smiley_order', | |
1824 array( | |
1825 ) | |
1826 ); | |
1827 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
1828 { | |
1829 $row['filename'] = htmlspecialchars($row['filename']); | |
1830 $row['description'] = htmlspecialchars($row['description']); | |
1831 | |
1832 $context['smileys'][empty($row['hidden']) ? 'postform' : 'popup'][$row['smiley_row']]['smileys'][] = $row; | |
1833 } | |
1834 $smcFunc['db_free_result']($request); | |
1835 | |
1836 foreach ($context['smileys'] as $section => $smileyRows) | |
1837 { | |
1838 foreach ($smileyRows as $rowIndex => $smileys) | |
1839 $context['smileys'][$section][$rowIndex]['smileys'][count($smileys['smileys']) - 1]['isLast'] = true; | |
1840 | |
1841 if (!empty($smileyRows)) | |
1842 $context['smileys'][$section][count($smileyRows) - 1]['isLast'] = true; | |
1843 } | |
1844 | |
1845 cache_put_data('posting_smileys', $context['smileys'], 480); | |
1846 } | |
1847 else | |
1848 $context['smileys'] = $temp; | |
1849 } | |
1850 } | |
1851 | |
1852 // Set a flag so the sub template knows what to do... | |
1853 $context['show_bbc'] = !empty($modSettings['enableBBC']) && !empty($settings['show_bbc']); | |
1854 | |
1855 // Generate a list of buttons that shouldn't be shown - this should be the fastest way to do this. | |
1856 $disabled_tags = array(); | |
1857 if (!empty($modSettings['disabledBBC'])) | |
1858 $disabled_tags = explode(',', $modSettings['disabledBBC']); | |
1859 if (empty($modSettings['enableEmbeddedFlash'])) | |
1860 $disabled_tags[] = 'flash'; | |
1861 | |
1862 foreach ($disabled_tags as $tag) | |
1863 { | |
1864 if ($tag == 'list') | |
1865 $context['disabled_tags']['orderlist'] = true; | |
1866 | |
1867 $context['disabled_tags'][trim($tag)] = true; | |
1868 } | |
1869 | |
1870 // Switch the URLs back... now we're back to whatever the main sub template is. (like folder in PersonalMessage.) | |
1871 if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template'])) | |
1872 { | |
1873 $settings['theme_url'] = $temp1; | |
1874 $settings['images_url'] = $temp2; | |
1875 $settings['theme_dir'] = $temp3; | |
1876 } | |
1877 } | |
1878 | |
1879 // Create a anti-bot verification control? | |
1880 function create_control_verification(&$verificationOptions, $do_test = false) | |
1881 { | |
1882 global $txt, $modSettings, $options, $smcFunc; | |
1883 global $context, $settings, $user_info, $sourcedir, $scripturl; | |
1884 | |
1885 // First verification means we need to set up some bits... | |
1886 if (empty($context['controls']['verification'])) | |
1887 { | |
1888 // The template | |
1889 loadTemplate('GenericControls'); | |
1890 | |
1891 // Some javascript ma'am? | |
1892 if (!empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual']))) | |
1893 $context['html_headers'] .= ' | |
1894 <script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/captcha.js"></script>'; | |
1895 | |
1896 $context['use_graphic_library'] = in_array('gd', get_loaded_extensions()); | |
1897 | |
1898 // Skip I, J, L, O, Q, S and Z. | |
1899 $context['standard_captcha_range'] = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y')); | |
1900 } | |
1901 | |
1902 // Always have an ID. | |
1903 assert(isset($verificationOptions['id'])); | |
1904 $isNew = !isset($context['controls']['verification'][$verificationOptions['id']]); | |
1905 | |
1906 // Log this into our collection. | |
1907 if ($isNew) | |
1908 $context['controls']['verification'][$verificationOptions['id']] = array( | |
1909 'id' => $verificationOptions['id'], | |
1910 'show_visual' => !empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual'])), | |
1911 'number_questions' => isset($verificationOptions['override_qs']) ? $verificationOptions['override_qs'] : (!empty($modSettings['qa_verification_number']) ? $modSettings['qa_verification_number'] : 0), | |
1912 'max_errors' => isset($verificationOptions['max_errors']) ? $verificationOptions['max_errors'] : 3, | |
1913 'image_href' => $scripturl . '?action=verificationcode;vid=' . $verificationOptions['id'] . ';rand=' . md5(mt_rand()), | |
1914 'text_value' => '', | |
1915 'questions' => array(), | |
1916 ); | |
1917 $thisVerification = &$context['controls']['verification'][$verificationOptions['id']]; | |
1918 | |
1919 // Add javascript for the object. | |
1920 if ($context['controls']['verification'][$verificationOptions['id']]['show_visual'] && !WIRELESS) | |
1921 $context['insert_after_template'] .= ' | |
1922 <script type="text/javascript"><!-- // --><![CDATA[ | |
1923 var verification' . $verificationOptions['id'] . 'Handle = new smfCaptcha("' . $thisVerification['image_href'] . '", "' . $verificationOptions['id'] . '", ' . ($context['use_graphic_library'] ? 1 : 0) . '); | |
1924 // ]]></script>'; | |
1925 | |
1926 // Is there actually going to be anything? | |
1927 if (empty($thisVerification['show_visual']) && empty($thisVerification['number_questions'])) | |
1928 return false; | |
1929 elseif (!$isNew && !$do_test) | |
1930 return true; | |
1931 | |
1932 // If we want questions do we have a cache of all the IDs? | |
1933 if (!empty($thisVerification['number_questions']) && empty($modSettings['question_id_cache'])) | |
1934 { | |
1935 if (($modSettings['question_id_cache'] = cache_get_data('verificationQuestionIds', 300)) == null) | |
1936 { | |
1937 $request = $smcFunc['db_query']('', ' | |
1938 SELECT id_comment | |
1939 FROM {db_prefix}log_comments | |
1940 WHERE comment_type = {string:ver_test}', | |
1941 array( | |
1942 'ver_test' => 'ver_test', | |
1943 ) | |
1944 ); | |
1945 $modSettings['question_id_cache'] = array(); | |
1946 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
1947 $modSettings['question_id_cache'][] = $row['id_comment']; | |
1948 $smcFunc['db_free_result']($request); | |
1949 | |
1950 if (!empty($modSettings['cache_enable'])) | |
1951 cache_put_data('verificationQuestionIds', $modSettings['question_id_cache'], 300); | |
1952 } | |
1953 } | |
1954 | |
1955 if (!isset($_SESSION[$verificationOptions['id'] . '_vv'])) | |
1956 $_SESSION[$verificationOptions['id'] . '_vv'] = array(); | |
1957 | |
1958 // Do we need to refresh the verification? | |
1959 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'])) | |
1960 $force_refresh = true; | |
1961 else | |
1962 $force_refresh = false; | |
1963 | |
1964 // This can also force a fresh, although unlikely. | |
1965 if (($thisVerification['show_visual'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['code'])) || ($thisVerification['number_questions'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['q']))) | |
1966 $force_refresh = true; | |
1967 | |
1968 $verification_errors = array(); | |
1969 | |
1970 // Start with any testing. | |
1971 if ($do_test) | |
1972 { | |
1973 // This cannot happen! | |
1974 if (!isset($_SESSION[$verificationOptions['id'] . '_vv']['count'])) | |
1975 fatal_lang_error('no_access', false); | |
1976 // ... nor this! | |
1977 if ($thisVerification['number_questions'] && (!isset($_SESSION[$verificationOptions['id'] . '_vv']['q']) || !isset($_REQUEST[$verificationOptions['id'] . '_vv']['q']))) | |
1978 fatal_lang_error('no_access', false); | |
1979 | |
1980 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'])) | |
1981 $verification_errors[] = 'wrong_verification_code'; | |
1982 if ($thisVerification['number_questions']) | |
1983 { | |
1984 // Get the answers and see if they are all right! | |
1985 $request = $smcFunc['db_query']('', ' | |
1986 SELECT id_comment, recipient_name AS answer | |
1987 FROM {db_prefix}log_comments | |
1988 WHERE comment_type = {string:ver_test} | |
1989 AND id_comment IN ({array_int:comment_ids})', | |
1990 array( | |
1991 'ver_test' => 'ver_test', | |
1992 'comment_ids' => $_SESSION[$verificationOptions['id'] . '_vv']['q'], | |
1993 ) | |
1994 ); | |
1995 $incorrectQuestions = array(); | |
1996 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
1997 { | |
1998 if (empty($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) || trim($smcFunc['htmlspecialchars'](strtolower($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]))) != strtolower($row['answer'])) | |
1999 $incorrectQuestions[] = $row['id_comment']; | |
2000 } | |
2001 $smcFunc['db_free_result']($request); | |
2002 | |
2003 if (!empty($incorrectQuestions)) | |
2004 $verification_errors[] = 'wrong_verification_answer'; | |
2005 } | |
2006 } | |
2007 | |
2008 // Any errors means we refresh potentially. | |
2009 if (!empty($verification_errors)) | |
2010 { | |
2011 if (empty($_SESSION[$verificationOptions['id'] . '_vv']['errors'])) | |
2012 $_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; | |
2013 // Too many errors? | |
2014 elseif ($_SESSION[$verificationOptions['id'] . '_vv']['errors'] > $thisVerification['max_errors']) | |
2015 $force_refresh = true; | |
2016 | |
2017 // Keep a track of these. | |
2018 $_SESSION[$verificationOptions['id'] . '_vv']['errors']++; | |
2019 } | |
2020 | |
2021 // Are we refreshing then? | |
2022 if ($force_refresh) | |
2023 { | |
2024 // Assume nothing went before. | |
2025 $_SESSION[$verificationOptions['id'] . '_vv']['count'] = 0; | |
2026 $_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; | |
2027 $_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = false; | |
2028 $_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); | |
2029 $_SESSION[$verificationOptions['id'] . '_vv']['code'] = ''; | |
2030 | |
2031 // Generating a new image. | |
2032 if ($thisVerification['show_visual']) | |
2033 { | |
2034 // Are we overriding the range? | |
2035 $character_range = !empty($verificationOptions['override_range']) ? $verificationOptions['override_range'] : $context['standard_captcha_range']; | |
2036 | |
2037 for ($i = 0; $i < 6; $i++) | |
2038 $_SESSION[$verificationOptions['id'] . '_vv']['code'] .= $character_range[array_rand($character_range)]; | |
2039 } | |
2040 | |
2041 // Getting some new questions? | |
2042 if ($thisVerification['number_questions']) | |
2043 { | |
2044 // Pick some random IDs | |
2045 $questionIDs = array(); | |
2046 if ($thisVerification['number_questions'] == 1) | |
2047 $questionIDs[] = $modSettings['question_id_cache'][array_rand($modSettings['question_id_cache'], $thisVerification['number_questions'])]; | |
2048 else | |
2049 foreach (array_rand($modSettings['question_id_cache'], $thisVerification['number_questions']) as $index) | |
2050 $questionIDs[] = $modSettings['question_id_cache'][$index]; | |
2051 } | |
2052 } | |
2053 else | |
2054 { | |
2055 // Same questions as before. | |
2056 $questionIDs = !empty($_SESSION[$verificationOptions['id'] . '_vv']['q']) ? $_SESSION[$verificationOptions['id'] . '_vv']['q'] : array(); | |
2057 $thisVerification['text_value'] = !empty($_REQUEST[$verificationOptions['id'] . '_vv']['code']) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['code']) : ''; | |
2058 } | |
2059 | |
2060 // Have we got some questions to load? | |
2061 if (!empty($questionIDs)) | |
2062 { | |
2063 $request = $smcFunc['db_query']('', ' | |
2064 SELECT id_comment, body AS question | |
2065 FROM {db_prefix}log_comments | |
2066 WHERE comment_type = {string:ver_test} | |
2067 AND id_comment IN ({array_int:comment_ids})', | |
2068 array( | |
2069 'ver_test' => 'ver_test', | |
2070 'comment_ids' => $questionIDs, | |
2071 ) | |
2072 ); | |
2073 $_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); | |
2074 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
2075 { | |
2076 $thisVerification['questions'][] = array( | |
2077 'id' => $row['id_comment'], | |
2078 'q' => parse_bbc($row['question']), | |
2079 'is_error' => !empty($incorrectQuestions) && in_array($row['id_comment'], $incorrectQuestions), | |
2080 // Remember a previous submission? | |
2081 'a' => isset($_REQUEST[$verificationOptions['id'] . '_vv'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'], $_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$row['id_comment']]) : '', | |
2082 ); | |
2083 $_SESSION[$verificationOptions['id'] . '_vv']['q'][] = $row['id_comment']; | |
2084 } | |
2085 $smcFunc['db_free_result']($request); | |
2086 } | |
2087 | |
2088 $_SESSION[$verificationOptions['id'] . '_vv']['count'] = empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) ? 1 : $_SESSION[$verificationOptions['id'] . '_vv']['count'] + 1; | |
2089 | |
2090 // Return errors if we have them. | |
2091 if (!empty($verification_errors)) | |
2092 return $verification_errors; | |
2093 // If we had a test that one, make a note. | |
2094 elseif ($do_test) | |
2095 $_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = true; | |
2096 | |
2097 // Say that everything went well chaps. | |
2098 return true; | |
2099 } | |
2100 | |
2101 // This keeps track of all registered handling functions for auto suggest functionality and passes execution to them. | |
2102 function AutoSuggestHandler($checkRegistered = null) | |
2103 { | |
2104 global $context; | |
2105 | |
2106 // These are all registered types. | |
2107 $searchTypes = array( | |
2108 'member' => 'Member', | |
2109 ); | |
2110 | |
2111 // If we're just checking the callback function is registered return true or false. | |
2112 if ($checkRegistered != null) | |
2113 return isset($searchTypes[$checkRegistered]) && function_exists('AutoSuggest_Search_' . $checkRegistered); | |
2114 | |
2115 checkSession('get'); | |
2116 loadTemplate('Xml'); | |
2117 | |
2118 // Any parameters? | |
2119 $context['search_param'] = isset($_REQUEST['search_param']) ? unserialize(base64_decode($_REQUEST['search_param'])) : array(); | |
2120 | |
2121 if (isset($_REQUEST['suggest_type'], $_REQUEST['search']) && isset($searchTypes[$_REQUEST['suggest_type']])) | |
2122 { | |
2123 $function = 'AutoSuggest_Search_' . $searchTypes[$_REQUEST['suggest_type']]; | |
2124 $context['sub_template'] = 'generic_xml'; | |
2125 $context['xml_data'] = $function(); | |
2126 } | |
2127 } | |
2128 | |
2129 // Search for a member - by real_name or member_name by default. | |
2130 function AutoSuggest_Search_Member() | |
2131 { | |
2132 global $user_info, $txt, $smcFunc, $context; | |
2133 | |
2134 $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])) . '*'; | |
2135 $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); | |
2136 | |
2137 // Find the member. | |
2138 $request = $smcFunc['db_query']('', ' | |
2139 SELECT id_member, real_name | |
2140 FROM {db_prefix}members | |
2141 WHERE real_name LIKE {string:search}' . (!empty($context['search_param']['buddies']) ? ' | |
2142 AND id_member IN ({array_int:buddy_list})' : '') . ' | |
2143 AND is_activated IN (1, 11) | |
2144 LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), | |
2145 array( | |
2146 'buddy_list' => $user_info['buddies'], | |
2147 'search' => $_REQUEST['search'], | |
2148 ) | |
2149 ); | |
2150 $xml_data = array( | |
2151 'items' => array( | |
2152 'identifier' => 'item', | |
2153 'children' => array(), | |
2154 ), | |
2155 ); | |
2156 while ($row = $smcFunc['db_fetch_assoc']($request)) | |
2157 { | |
2158 $row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); | |
2159 | |
2160 $xml_data['items']['children'][] = array( | |
2161 'attributes' => array( | |
2162 'id' => $row['id_member'], | |
2163 ), | |
2164 'value' => $row['real_name'], | |
2165 ); | |
2166 } | |
2167 $smcFunc['db_free_result']($request); | |
2168 | |
2169 return $xml_data; | |
2170 } | |
2171 | |
2172 ?> |