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 http://www.simplemachines.org |
11
|
|
|
* @copyright 2019 Simple Machines and individual contributors |
12
|
|
|
* @license http://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++) |
|
|
|
|
49
|
|
|
{ |
50
|
|
|
// Value of 2 means we're inside the tag. |
51
|
|
|
if ($i % 4 == 2) |
52
|
|
|
$parts[$i] = strtr($parts[$i], array('[' => '[', ']' => ']', "'" => "'")); |
53
|
|
|
} |
54
|
|
|
// Put our humpty dumpty message back together again. |
55
|
|
|
$text = implode('', $parts); |
|
|
|
|
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
// What tags do we allow? |
60
|
|
|
$allowed_tags = array('b', 'u', 'i', 's', 'hr', 'list', 'li', 'font', 'size', 'color', 'img', 'left', 'center', 'right', 'url', 'email', 'ftp', 'sub', 'sup'); |
61
|
|
|
|
62
|
|
|
$text = parse_bbc($text, true, '', $allowed_tags); |
63
|
|
|
|
64
|
|
|
// Fix for having a line break then a thingy. |
65
|
|
|
$text = strtr($text, array('<br><div' => '<div', "\n" => '', "\r" => '')); |
66
|
|
|
|
67
|
|
|
// Note that IE doesn't understand spans really - make them something "legacy" |
68
|
|
|
$working_html = array( |
69
|
|
|
'~<del>(.+?)</del>~i' => '<strike>$1</strike>', |
70
|
|
|
'~<span\sclass="bbc_u">(.+?)</span>~i' => '<u>$1</u>', |
71
|
|
|
'~<span\sstyle="color:\s*([#\d\w]+);" class="bbc_color">(.+?)</span>~i' => '<font color="$1">$2</font>', |
72
|
|
|
'~<span\sstyle="font-family:\s*([#\d\w\s]+);" class="bbc_font">(.+?)</span>~i' => '<font face="$1">$2</font>', |
73
|
|
|
'~<div\sstyle="text-align:\s*(left|right);">(.+?)</div>~i' => '<p align="$1">$2</p>', |
74
|
|
|
); |
75
|
|
|
$text = preg_replace(array_keys($working_html), array_values($working_html), $text); |
76
|
|
|
|
77
|
|
|
// Parse unique ID's and disable javascript into the smileys - using the double space. |
78
|
|
|
$i = 1; |
79
|
|
|
$text = preg_replace_callback('~(?:\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++) |
|
|
|
|
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>')); |
|
|
|
|
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 != '') |
|
|
|
|
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) |
|
|
|
|
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) |
|
|
|
|
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; |
|
|
|
|
1073
|
|
|
} |
1074
|
|
|
|
1075
|
|
|
// We're still inside a tag and had no chance for closure? |
1076
|
|
|
if ($insideTag !== null) |
|
|
|
|
1077
|
|
|
$matches[] = '[/' . $insideTag . ']'; |
1078
|
|
|
|
1079
|
|
|
// And a complete text string again. |
1080
|
|
|
$text = implode('', $matches); |
|
|
|
|
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) |
|
|
|
|
1356
|
|
|
$parts[$i] .= '[/code]'; |
1357
|
|
|
|
1358
|
|
|
// The same for nobbc tags. |
1359
|
|
|
elseif ($inNoBbc) |
|
|
|
|
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(); |
|
|
|
|
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) |
|
|
|
|
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'] : '250px', |
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) |
|
|
|
|
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 a flag so the sub template knows what to do... |
1903
|
|
|
$context['show_bbc'] = !empty($modSettings['enableBBC']); |
1904
|
|
|
|
1905
|
|
|
// Set up the SCEditor options |
1906
|
|
|
$sce_options = array( |
1907
|
|
|
'style' => $settings[file_exists($settings['theme_dir'] . '/css/jquery.sceditor.default.css') ? 'theme_url' : 'default_theme_url'] . '/css/jquery.sceditor.default.css', |
1908
|
|
|
'emoticonsCompat' => true, |
1909
|
|
|
'colors' => 'black,maroon,brown,green,navy,grey,red,orange,teal,blue,white,hotpink,yellow,limegreen,purple', |
1910
|
|
|
'format' => 'bbcode', |
1911
|
|
|
'plugins' => '', |
1912
|
|
|
'bbcodeTrim' => true, |
1913
|
|
|
); |
1914
|
|
|
if (!empty($context['controls']['richedit'][$editorOptions['id']]['locale'])) |
1915
|
|
|
$sce_options['locale'] = $context['controls']['richedit'][$editorOptions['id']]['locale']; |
1916
|
|
|
if (!empty($context['right_to_left'])) |
1917
|
|
|
$sce_options['rtl'] = true; |
1918
|
|
|
if ($editorOptions['id'] != 'quickReply') |
1919
|
|
|
$sce_options['autofocus'] = true; |
1920
|
|
|
|
1921
|
|
|
$sce_options['emoticons'] = array(); |
1922
|
|
|
$sce_options['emoticonsDescriptions'] = array(); |
1923
|
|
|
$sce_options['emoticonsEnabled'] = false; |
1924
|
|
|
if ((!empty($context['smileys']['postform']) || !empty($context['smileys']['popup'])) && !$context['controls']['richedit'][$editorOptions['id']]['disable_smiley_box']) |
1925
|
|
|
{ |
1926
|
|
|
$sce_options['emoticonsEnabled'] = true; |
1927
|
|
|
$sce_options['emoticons']['dropdown'] = array(); |
1928
|
|
|
$sce_options['emoticons']['popup'] = array(); |
1929
|
|
|
|
1930
|
|
|
$countLocations = count($context['smileys']); |
1931
|
|
|
foreach ($context['smileys'] as $location => $smileyRows) |
1932
|
|
|
{ |
1933
|
|
|
$countLocations--; |
1934
|
|
|
|
1935
|
|
|
unset($smiley_location); |
1936
|
|
|
if ($location == 'postform') |
1937
|
|
|
$smiley_location = &$sce_options['emoticons']['dropdown']; |
1938
|
|
|
elseif ($location == 'popup') |
1939
|
|
|
$smiley_location = &$sce_options['emoticons']['popup']; |
1940
|
|
|
|
1941
|
|
|
$numRows = count($smileyRows); |
1942
|
|
|
|
1943
|
|
|
// This is needed because otherwise the editor will remove all the duplicate (empty) keys and leave only 1 additional line |
1944
|
|
|
$emptyPlaceholder = 0; |
1945
|
|
|
foreach ($smileyRows as $smileyRow) |
1946
|
|
|
{ |
1947
|
|
|
foreach ($smileyRow['smileys'] as $smiley) |
1948
|
|
|
{ |
1949
|
|
|
$smiley_location[$smiley['code']] = $settings['smileys_url'] . '/' . $smiley['filename']; |
1950
|
|
|
$sce_options['emoticonsDescriptions'][$smiley['code']] = $smiley['description']; |
1951
|
|
|
} |
1952
|
|
|
|
1953
|
|
|
if (empty($smileyRow['isLast']) && $numRows != 1) |
1954
|
|
|
$smiley_location['-' . $emptyPlaceholder++] = ''; |
1955
|
|
|
} |
1956
|
|
|
} |
1957
|
|
|
} |
1958
|
|
|
|
1959
|
|
|
$sce_options['toolbar'] = ''; |
1960
|
|
|
if ($context['show_bbc']) |
1961
|
|
|
{ |
1962
|
|
|
$count_tags = count($context['bbc_tags']); |
1963
|
|
|
foreach ($context['bbc_toolbar'] as $i => $buttonRow) |
1964
|
|
|
{ |
1965
|
|
|
$sce_options['toolbar'] .= implode('|', $buttonRow); |
1966
|
|
|
|
1967
|
|
|
$count_tags--; |
1968
|
|
|
|
1969
|
|
|
if (!empty($count_tags)) |
1970
|
|
|
$sce_options['toolbar'] .= '||'; |
1971
|
|
|
} |
1972
|
|
|
} |
1973
|
|
|
|
1974
|
|
|
// Allow mods to change $sce_options. Usful if, e.g., a mod wants to add an SCEditor plugin. |
1975
|
|
|
call_integration_hook('integrate_sceditor_options', array(&$sce_options)); |
1976
|
|
|
|
1977
|
|
|
$context['controls']['richedit'][$editorOptions['id']]['sce_options'] = $sce_options; |
1978
|
|
|
} |
1979
|
|
|
|
1980
|
|
|
/** |
1981
|
|
|
* Create a anti-bot verification control? |
1982
|
|
|
* |
1983
|
|
|
* @param array &$verificationOptions Options for the verification control |
1984
|
|
|
* @param bool $do_test Whether to check to see if the user entered the code correctly |
1985
|
|
|
* @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 |
1986
|
|
|
*/ |
1987
|
|
|
function create_control_verification(&$verificationOptions, $do_test = false) |
1988
|
|
|
{ |
1989
|
|
|
global $modSettings, $smcFunc; |
1990
|
|
|
global $context, $user_info, $scripturl, $language; |
1991
|
|
|
|
1992
|
|
|
// First verification means we need to set up some bits... |
1993
|
|
|
if (empty($context['controls']['verification'])) |
1994
|
|
|
{ |
1995
|
|
|
// The template |
1996
|
|
|
loadTemplate('GenericControls'); |
1997
|
|
|
|
1998
|
|
|
// Some javascript ma'am? |
1999
|
|
|
if (!empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual']))) |
2000
|
|
|
loadJavaScriptFile('captcha.js', array('minimize' => true), 'smf_captcha'); |
2001
|
|
|
|
2002
|
|
|
$context['use_graphic_library'] = in_array('gd', get_loaded_extensions()); |
2003
|
|
|
|
2004
|
|
|
// Skip I, J, L, O, Q, S and Z. |
2005
|
|
|
$context['standard_captcha_range'] = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y')); |
2006
|
|
|
} |
2007
|
|
|
|
2008
|
|
|
// Always have an ID. |
2009
|
|
|
assert(isset($verificationOptions['id'])); |
2010
|
|
|
$isNew = !isset($context['controls']['verification'][$verificationOptions['id']]); |
2011
|
|
|
|
2012
|
|
|
// Log this into our collection. |
2013
|
|
|
if ($isNew) |
2014
|
|
|
$context['controls']['verification'][$verificationOptions['id']] = array( |
2015
|
|
|
'id' => $verificationOptions['id'], |
2016
|
|
|
'empty_field' => empty($verificationOptions['no_empty_field']), |
2017
|
|
|
'show_visual' => !empty($verificationOptions['override_visual']) || (!empty($modSettings['visual_verification_type']) && !isset($verificationOptions['override_visual'])), |
2018
|
|
|
'number_questions' => isset($verificationOptions['override_qs']) ? $verificationOptions['override_qs'] : (!empty($modSettings['qa_verification_number']) ? $modSettings['qa_verification_number'] : 0), |
2019
|
|
|
'max_errors' => isset($verificationOptions['max_errors']) ? $verificationOptions['max_errors'] : 3, |
2020
|
|
|
'image_href' => $scripturl . '?action=verificationcode;vid=' . $verificationOptions['id'] . ';rand=' . md5(mt_rand()), |
2021
|
|
|
'text_value' => '', |
2022
|
|
|
'questions' => array(), |
2023
|
|
|
'can_recaptcha' => !empty($modSettings['recaptcha_enabled']) && !empty($modSettings['recaptcha_site_key']) && !empty($modSettings['recaptcha_secret_key']), |
2024
|
|
|
); |
2025
|
|
|
$thisVerification = &$context['controls']['verification'][$verificationOptions['id']]; |
2026
|
|
|
|
2027
|
|
|
// Is there actually going to be anything? |
2028
|
|
|
if (empty($thisVerification['show_visual']) && empty($thisVerification['number_questions']) && empty($thisVerification['can_recaptcha'])) |
2029
|
|
|
return false; |
2030
|
|
|
elseif (!$isNew && !$do_test) |
2031
|
|
|
return true; |
2032
|
|
|
|
2033
|
|
|
// Sanitize reCAPTCHA fields? |
2034
|
|
|
if ($thisVerification['can_recaptcha']) |
2035
|
|
|
{ |
2036
|
|
|
// Only allow 40 alphanumeric, underscore and dash characters. |
2037
|
|
|
$thisVerification['recaptcha_site_key'] = preg_replace('/(0-9a-zA-Z_){40}/', '$1', $modSettings['recaptcha_site_key']); |
2038
|
|
|
|
2039
|
|
|
// Light or dark theme... |
2040
|
|
|
$thisVerification['recaptcha_theme'] = preg_replace('/(light|dark)/', '$1', $modSettings['recaptcha_theme']); |
2041
|
|
|
} |
2042
|
|
|
|
2043
|
|
|
// Add javascript for the object. |
2044
|
|
|
if ($context['controls']['verification'][$verificationOptions['id']]['show_visual']) |
2045
|
|
|
$context['insert_after_template'] .= ' |
2046
|
|
|
<script> |
2047
|
|
|
var verification' . $verificationOptions['id'] . 'Handle = new smfCaptcha("' . $thisVerification['image_href'] . '", "' . $verificationOptions['id'] . '", ' . ($context['use_graphic_library'] ? 1 : 0) . '); |
2048
|
|
|
</script>'; |
2049
|
|
|
|
2050
|
|
|
// If we want questions do we have a cache of all the IDs? |
2051
|
|
|
if (!empty($thisVerification['number_questions']) && empty($modSettings['question_id_cache'])) |
2052
|
|
|
{ |
2053
|
|
|
if (($modSettings['question_id_cache'] = cache_get_data('verificationQuestions', 300)) == null) |
|
|
|
|
2054
|
|
|
{ |
2055
|
|
|
$request = $smcFunc['db_query']('', ' |
2056
|
|
|
SELECT id_question, lngfile, question, answers |
2057
|
|
|
FROM {db_prefix}qanda', |
2058
|
|
|
array() |
2059
|
|
|
); |
2060
|
|
|
$modSettings['question_id_cache'] = array( |
2061
|
|
|
'questions' => array(), |
2062
|
|
|
'langs' => array(), |
2063
|
|
|
); |
2064
|
|
|
// This is like Captain Kirk climbing a mountain in some ways. This is L's fault, mkay? :P |
2065
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
2066
|
|
|
{ |
2067
|
|
|
$id_question = $row['id_question']; |
2068
|
|
|
unset ($row['id_question']); |
2069
|
|
|
// Make them all lowercase. We can't directly use $smcFunc['strtolower'] with array_walk, so do it manually, eh? |
2070
|
|
|
$row['answers'] = $smcFunc['json_decode']($row['answers'], true); |
2071
|
|
|
foreach ($row['answers'] as $k => $v) |
2072
|
|
|
$row['answers'][$k] = $smcFunc['strtolower']($v); |
2073
|
|
|
|
2074
|
|
|
$modSettings['question_id_cache']['questions'][$id_question] = $row; |
2075
|
|
|
$modSettings['question_id_cache']['langs'][$row['lngfile']][] = $id_question; |
2076
|
|
|
} |
2077
|
|
|
$smcFunc['db_free_result']($request); |
2078
|
|
|
|
2079
|
|
|
cache_put_data('verificationQuestions', $modSettings['question_id_cache'], 300); |
2080
|
|
|
} |
2081
|
|
|
} |
2082
|
|
|
|
2083
|
|
|
if (!isset($_SESSION[$verificationOptions['id'] . '_vv'])) |
2084
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv'] = array(); |
2085
|
|
|
|
2086
|
|
|
// Do we need to refresh the verification? |
2087
|
|
|
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'])) |
2088
|
|
|
$force_refresh = true; |
2089
|
|
|
else |
2090
|
|
|
$force_refresh = false; |
2091
|
|
|
|
2092
|
|
|
// This can also force a fresh, although unlikely. |
2093
|
|
|
if (($thisVerification['show_visual'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['code'])) || ($thisVerification['number_questions'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['q']))) |
2094
|
|
|
$force_refresh = true; |
2095
|
|
|
|
2096
|
|
|
$verification_errors = array(); |
2097
|
|
|
// Start with any testing. |
2098
|
|
|
if ($do_test) |
2099
|
|
|
{ |
2100
|
|
|
// This cannot happen! |
2101
|
|
|
if (!isset($_SESSION[$verificationOptions['id'] . '_vv']['count'])) |
2102
|
|
|
fatal_lang_error('no_access', false); |
2103
|
|
|
// ... nor this! |
2104
|
|
|
if ($thisVerification['number_questions'] && (!isset($_SESSION[$verificationOptions['id'] . '_vv']['q']) || !isset($_REQUEST[$verificationOptions['id'] . '_vv']['q']))) |
2105
|
|
|
fatal_lang_error('no_access', false); |
2106
|
|
|
// Hmm, it's requested but not actually declared. This shouldn't happen. |
2107
|
|
|
if ($thisVerification['empty_field'] && empty($_SESSION[$verificationOptions['id'] . '_vv']['empty_field'])) |
2108
|
|
|
fatal_lang_error('no_access', false); |
2109
|
|
|
// While we're here, did the user do something bad? |
2110
|
|
|
if ($thisVerification['empty_field'] && !empty($_SESSION[$verificationOptions['id'] . '_vv']['empty_field']) && !empty($_REQUEST[$_SESSION[$verificationOptions['id'] . '_vv']['empty_field']])) |
2111
|
|
|
$verification_errors[] = 'wrong_verification_answer'; |
2112
|
|
|
|
2113
|
|
|
if ($thisVerification['can_recaptcha']) |
2114
|
|
|
{ |
2115
|
|
|
$reCaptcha = new \ReCaptcha\ReCaptcha($modSettings['recaptcha_secret_key'], new \ReCaptcha\RequestMethod\SocketPost()); |
|
|
|
|
2116
|
|
|
|
2117
|
|
|
// Was there a reCAPTCHA response? |
2118
|
|
|
if (isset($_POST['g-recaptcha-response'])) |
2119
|
|
|
{ |
2120
|
|
|
$resp = $reCaptcha->verify($_POST['g-recaptcha-response'], $user_info['ip']); |
2121
|
|
|
|
2122
|
|
|
if (!$resp->isSuccess()) |
2123
|
|
|
$verification_errors[] = 'wrong_verification_recaptcha'; |
2124
|
|
|
} |
2125
|
|
|
else |
2126
|
|
|
$verification_errors[] = 'wrong_verification_code'; |
2127
|
|
|
} |
2128
|
|
|
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'])) |
2129
|
|
|
$verification_errors[] = 'wrong_verification_code'; |
2130
|
|
|
if ($thisVerification['number_questions']) |
2131
|
|
|
{ |
2132
|
|
|
$incorrectQuestions = array(); |
2133
|
|
|
foreach ($_SESSION[$verificationOptions['id'] . '_vv']['q'] as $q) |
2134
|
|
|
{ |
2135
|
|
|
// We don't have this question any more, thus no answers. |
2136
|
|
|
if (!isset($modSettings['question_id_cache']['questions'][$q])) |
2137
|
|
|
continue; |
2138
|
|
|
// This is quite complex. We have our question but it might have multiple answers. |
2139
|
|
|
// First, did they actually answer this question? |
2140
|
|
|
if (!isset($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) || trim($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]) == '') |
2141
|
|
|
{ |
2142
|
|
|
$incorrectQuestions[] = $q; |
2143
|
|
|
continue; |
2144
|
|
|
} |
2145
|
|
|
// Second, is their answer in the list of possible answers? |
2146
|
|
|
else |
2147
|
|
|
{ |
2148
|
|
|
$given_answer = trim($smcFunc['htmlspecialchars'](strtolower($_REQUEST[$verificationOptions['id'] . '_vv']['q'][$q]))); |
2149
|
|
|
if (!in_array($given_answer, $modSettings['question_id_cache']['questions'][$q]['answers'])) |
2150
|
|
|
$incorrectQuestions[] = $q; |
2151
|
|
|
} |
2152
|
|
|
} |
2153
|
|
|
|
2154
|
|
|
if (!empty($incorrectQuestions)) |
2155
|
|
|
$verification_errors[] = 'wrong_verification_answer'; |
2156
|
|
|
} |
2157
|
|
|
} |
2158
|
|
|
|
2159
|
|
|
// Any errors means we refresh potentially. |
2160
|
|
|
if (!empty($verification_errors)) |
2161
|
|
|
{ |
2162
|
|
|
if (empty($_SESSION[$verificationOptions['id'] . '_vv']['errors'])) |
2163
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; |
2164
|
|
|
// Too many errors? |
2165
|
|
|
elseif ($_SESSION[$verificationOptions['id'] . '_vv']['errors'] > $thisVerification['max_errors']) |
2166
|
|
|
$force_refresh = true; |
2167
|
|
|
|
2168
|
|
|
// Keep a track of these. |
2169
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['errors']++; |
2170
|
|
|
} |
2171
|
|
|
|
2172
|
|
|
// Are we refreshing then? |
2173
|
|
|
if ($force_refresh) |
2174
|
|
|
{ |
2175
|
|
|
// Assume nothing went before. |
2176
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['count'] = 0; |
2177
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['errors'] = 0; |
2178
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = false; |
2179
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); |
2180
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['code'] = ''; |
2181
|
|
|
|
2182
|
|
|
// Make our magic empty field. |
2183
|
|
|
if ($thisVerification['empty_field']) |
2184
|
|
|
{ |
2185
|
|
|
// 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. |
2186
|
|
|
$terms = array('gadget', 'device', 'uid', 'gid', 'guid', 'uuid', 'unique', 'identifier'); |
2187
|
|
|
$second_terms = array('hash', 'cipher', 'code', 'key', 'unlock', 'bit', 'value'); |
2188
|
|
|
$start = mt_rand(0, 27); |
2189
|
|
|
$hash = substr(md5(time()), $start, 4); |
2190
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['empty_field'] = $terms[array_rand($terms)] . '-' . $second_terms[array_rand($second_terms)] . '-' . $hash; |
2191
|
|
|
} |
2192
|
|
|
|
2193
|
|
|
// Generating a new image. |
2194
|
|
|
if ($thisVerification['show_visual']) |
2195
|
|
|
{ |
2196
|
|
|
// Are we overriding the range? |
2197
|
|
|
$character_range = !empty($verificationOptions['override_range']) ? $verificationOptions['override_range'] : $context['standard_captcha_range']; |
2198
|
|
|
|
2199
|
|
|
for ($i = 0; $i < 6; $i++) |
2200
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['code'] .= $character_range[array_rand($character_range)]; |
2201
|
|
|
} |
2202
|
|
|
|
2203
|
|
|
// Getting some new questions? |
2204
|
|
|
if ($thisVerification['number_questions']) |
2205
|
|
|
{ |
2206
|
|
|
// Attempt to try the current page's language, followed by the user's preference, followed by the site default. |
2207
|
|
|
$possible_langs = array(); |
2208
|
|
|
if (isset($_SESSION['language'])) |
2209
|
|
|
$possible_langs[] = strtr($_SESSION['language'], array('-utf8' => '')); |
2210
|
|
|
if (!empty($user_info['language'])) |
2211
|
|
|
$possible_langs[] = $user_info['language']; |
2212
|
|
|
|
2213
|
|
|
$possible_langs[] = $language; |
2214
|
|
|
|
2215
|
|
|
$questionIDs = array(); |
2216
|
|
|
foreach ($possible_langs as $lang) |
2217
|
|
|
{ |
2218
|
|
|
$lang = strtr($lang, array('-utf8' => '')); |
2219
|
|
|
if (isset($modSettings['question_id_cache']['langs'][$lang])) |
2220
|
|
|
{ |
2221
|
|
|
// If we find questions for this, grab the ids from this language's ones, randomize the array and take just the number we need. |
2222
|
|
|
$questionIDs = $modSettings['question_id_cache']['langs'][$lang]; |
2223
|
|
|
shuffle($questionIDs); |
2224
|
|
|
$questionIDs = array_slice($questionIDs, 0, $thisVerification['number_questions']); |
2225
|
|
|
break; |
2226
|
|
|
} |
2227
|
|
|
} |
2228
|
|
|
} |
2229
|
|
|
} |
2230
|
|
|
else |
2231
|
|
|
{ |
2232
|
|
|
// Same questions as before. |
2233
|
|
|
$questionIDs = !empty($_SESSION[$verificationOptions['id'] . '_vv']['q']) ? $_SESSION[$verificationOptions['id'] . '_vv']['q'] : array(); |
2234
|
|
|
$thisVerification['text_value'] = !empty($_REQUEST[$verificationOptions['id'] . '_vv']['code']) ? $smcFunc['htmlspecialchars']($_REQUEST[$verificationOptions['id'] . '_vv']['code']) : ''; |
2235
|
|
|
} |
2236
|
|
|
|
2237
|
|
|
// If we do have an empty field, it would be nice to hide it from legitimate users who shouldn't be populating it anyway. |
2238
|
|
|
if (!empty($_SESSION[$verificationOptions['id'] . '_vv']['empty_field'])) |
2239
|
|
|
{ |
2240
|
|
|
if (!isset($context['html_headers'])) |
2241
|
|
|
$context['html_headers'] = ''; |
2242
|
|
|
$context['html_headers'] .= '<style>.vv_special { display:none; }</style>'; |
2243
|
|
|
} |
2244
|
|
|
|
2245
|
|
|
// Have we got some questions to load? |
2246
|
|
|
if (!empty($questionIDs)) |
2247
|
|
|
{ |
2248
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['q'] = array(); |
2249
|
|
|
foreach ($questionIDs as $q) |
2250
|
|
|
{ |
2251
|
|
|
// Bit of a shortcut this. |
2252
|
|
|
$row = &$modSettings['question_id_cache']['questions'][$q]; |
2253
|
|
|
$thisVerification['questions'][] = array( |
2254
|
|
|
'id' => $q, |
2255
|
|
|
'q' => parse_bbc($row['question']), |
2256
|
|
|
'is_error' => !empty($incorrectQuestions) && in_array($q, $incorrectQuestions), |
2257
|
|
|
// Remember a previous submission? |
2258
|
|
|
'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]) : '', |
2259
|
|
|
); |
2260
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['q'][] = $q; |
2261
|
|
|
} |
2262
|
|
|
} |
2263
|
|
|
|
2264
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['count'] = empty($_SESSION[$verificationOptions['id'] . '_vv']['count']) ? 1 : $_SESSION[$verificationOptions['id'] . '_vv']['count'] + 1; |
2265
|
|
|
|
2266
|
|
|
// Return errors if we have them. |
2267
|
|
|
if (!empty($verification_errors)) |
2268
|
|
|
return $verification_errors; |
2269
|
|
|
// If we had a test that one, make a note. |
2270
|
|
|
elseif ($do_test) |
2271
|
|
|
$_SESSION[$verificationOptions['id'] . '_vv']['did_pass'] = true; |
2272
|
|
|
|
2273
|
|
|
// Say that everything went well chaps. |
2274
|
|
|
return true; |
2275
|
|
|
} |
2276
|
|
|
|
2277
|
|
|
/** |
2278
|
|
|
* This keeps track of all registered handling functions for auto suggest functionality and passes execution to them. |
2279
|
|
|
* |
2280
|
|
|
* @param bool $checkRegistered If set to something other than null, checks whether the callback function is registered |
2281
|
|
|
* @return void|bool Returns whether the callback function is registered if $checkRegistered isn't null |
2282
|
|
|
*/ |
2283
|
|
|
function AutoSuggestHandler($checkRegistered = null) |
2284
|
|
|
{ |
2285
|
|
|
global $smcFunc, $context; |
2286
|
|
|
|
2287
|
|
|
// These are all registered types. |
2288
|
|
|
$searchTypes = array( |
2289
|
|
|
'member' => 'Member', |
2290
|
|
|
'membergroups' => 'MemberGroups', |
2291
|
|
|
'versions' => 'SMFVersions', |
2292
|
|
|
); |
2293
|
|
|
|
2294
|
|
|
call_integration_hook('integrate_autosuggest', array(&$searchTypes)); |
2295
|
|
|
|
2296
|
|
|
// If we're just checking the callback function is registered return true or false. |
2297
|
|
|
if ($checkRegistered != null) |
2298
|
|
|
return isset($searchTypes[$checkRegistered]) && function_exists('AutoSuggest_Search_' . $checkRegistered); |
|
|
|
|
2299
|
|
|
|
2300
|
|
|
checkSession('get'); |
2301
|
|
|
loadTemplate('Xml'); |
2302
|
|
|
|
2303
|
|
|
// Any parameters? |
2304
|
|
|
$context['search_param'] = isset($_REQUEST['search_param']) ? $smcFunc['json_decode'](base64_decode($_REQUEST['search_param']), true) : array(); |
2305
|
|
|
|
2306
|
|
|
if (isset($_REQUEST['suggest_type'], $_REQUEST['search']) && isset($searchTypes[$_REQUEST['suggest_type']])) |
2307
|
|
|
{ |
2308
|
|
|
$function = 'AutoSuggest_Search_' . $searchTypes[$_REQUEST['suggest_type']]; |
2309
|
|
|
$context['sub_template'] = 'generic_xml'; |
2310
|
|
|
$context['xml_data'] = $function(); |
2311
|
|
|
} |
2312
|
|
|
} |
2313
|
|
|
|
2314
|
|
|
/** |
2315
|
|
|
* Search for a member - by real_name or member_name by default. |
2316
|
|
|
* |
2317
|
|
|
* @return array An array of information for displaying the suggestions |
2318
|
|
|
*/ |
2319
|
|
|
function AutoSuggest_Search_Member() |
2320
|
|
|
{ |
2321
|
|
|
global $user_info, $smcFunc, $context; |
2322
|
|
|
|
2323
|
|
|
$_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])) . '*'; |
2324
|
|
|
$_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); |
2325
|
|
|
|
2326
|
|
|
// Find the member. |
2327
|
|
|
$request = $smcFunc['db_query']('', ' |
2328
|
|
|
SELECT id_member, real_name |
2329
|
|
|
FROM {db_prefix}members |
2330
|
|
|
WHERE {raw:real_name} LIKE {string:search}' . (!empty($context['search_param']['buddies']) ? ' |
2331
|
|
|
AND id_member IN ({array_int:buddy_list})' : '') . ' |
2332
|
|
|
AND is_activated IN (1, 11) |
2333
|
|
|
LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), |
2334
|
|
|
array( |
2335
|
|
|
'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name', |
2336
|
|
|
'buddy_list' => $user_info['buddies'], |
2337
|
|
|
'search' => $_REQUEST['search'], |
2338
|
|
|
) |
2339
|
|
|
); |
2340
|
|
|
$xml_data = array( |
2341
|
|
|
'items' => array( |
2342
|
|
|
'identifier' => 'item', |
2343
|
|
|
'children' => array(), |
2344
|
|
|
), |
2345
|
|
|
); |
2346
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
2347
|
|
|
{ |
2348
|
|
|
$row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); |
2349
|
|
|
|
2350
|
|
|
$xml_data['items']['children'][] = array( |
2351
|
|
|
'attributes' => array( |
2352
|
|
|
'id' => $row['id_member'], |
2353
|
|
|
), |
2354
|
|
|
'value' => $row['real_name'], |
2355
|
|
|
); |
2356
|
|
|
} |
2357
|
|
|
$smcFunc['db_free_result']($request); |
2358
|
|
|
|
2359
|
|
|
return $xml_data; |
2360
|
|
|
} |
2361
|
|
|
|
2362
|
|
|
/** |
2363
|
|
|
* Search for a membergroup by name |
2364
|
|
|
* |
2365
|
|
|
* @return array An array of information for displaying the suggestions |
2366
|
|
|
*/ |
2367
|
|
|
function AutoSuggest_Search_MemberGroups() |
2368
|
|
|
{ |
2369
|
|
|
global $smcFunc; |
2370
|
|
|
|
2371
|
|
|
$_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])) . '*'; |
2372
|
|
|
$_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); |
2373
|
|
|
|
2374
|
|
|
// Find the group. |
2375
|
|
|
// Only return groups which are not post-based and not "Hidden", but not the "Administrators" or "Moderators" groups. |
2376
|
|
|
$request = $smcFunc['db_query']('', ' |
2377
|
|
|
SELECT id_group, group_name |
2378
|
|
|
FROM {db_prefix}membergroups |
2379
|
|
|
WHERE {raw:group_name} LIKE {string:search} |
2380
|
|
|
AND min_posts = {int:min_posts} |
2381
|
|
|
AND id_group NOT IN ({array_int:invalid_groups}) |
2382
|
|
|
AND hidden != {int:hidden}', |
2383
|
|
|
array( |
2384
|
|
|
'group_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(group_name}' : 'group_name', |
2385
|
|
|
'min_posts' => -1, |
2386
|
|
|
'invalid_groups' => array(1, 3), |
2387
|
|
|
'hidden' => 2, |
2388
|
|
|
'search' => $_REQUEST['search'], |
2389
|
|
|
) |
2390
|
|
|
); |
2391
|
|
|
$xml_data = array( |
2392
|
|
|
'items' => array( |
2393
|
|
|
'identifier' => 'item', |
2394
|
|
|
'children' => array(), |
2395
|
|
|
), |
2396
|
|
|
); |
2397
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
2398
|
|
|
{ |
2399
|
|
|
$row['group_name'] = strtr($row['group_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); |
2400
|
|
|
|
2401
|
|
|
$xml_data['items']['children'][] = array( |
2402
|
|
|
'attributes' => array( |
2403
|
|
|
'id' => $row['id_group'], |
2404
|
|
|
), |
2405
|
|
|
'value' => $row['group_name'], |
2406
|
|
|
); |
2407
|
|
|
} |
2408
|
|
|
$smcFunc['db_free_result']($request); |
2409
|
|
|
|
2410
|
|
|
return $xml_data; |
2411
|
|
|
} |
2412
|
|
|
|
2413
|
|
|
/** |
2414
|
|
|
* Provides a list of possible SMF versions to use in emulation |
2415
|
|
|
* |
2416
|
|
|
* @return array An array of data for displaying the suggestions |
2417
|
|
|
*/ |
2418
|
|
|
function AutoSuggest_Search_SMFVersions() |
2419
|
|
|
{ |
2420
|
|
|
global $smcFunc; |
2421
|
|
|
|
2422
|
|
|
$xml_data = array( |
2423
|
|
|
'items' => array( |
2424
|
|
|
'identifier' => 'item', |
2425
|
|
|
'children' => array(), |
2426
|
|
|
), |
2427
|
|
|
); |
2428
|
|
|
|
2429
|
|
|
// First try and get it from the database. |
2430
|
|
|
$versions = array(); |
2431
|
|
|
$request = $smcFunc['db_query']('', ' |
2432
|
|
|
SELECT data |
2433
|
|
|
FROM {db_prefix}admin_info_files |
2434
|
|
|
WHERE filename = {string:latest_versions} |
2435
|
|
|
AND path = {string:path}', |
2436
|
|
|
array( |
2437
|
|
|
'latest_versions' => 'latest-versions.txt', |
2438
|
|
|
'path' => '/smf/', |
2439
|
|
|
) |
2440
|
|
|
); |
2441
|
|
|
if (($smcFunc['db_num_rows']($request) > 0) && ($row = $smcFunc['db_fetch_assoc']($request)) && !empty($row['data'])) |
2442
|
|
|
{ |
2443
|
|
|
// The file can be either Windows or Linux line endings, but let's ensure we clean it as best we can. |
2444
|
|
|
$possible_versions = explode("\n", $row['data']); |
2445
|
|
|
foreach ($possible_versions as $ver) |
2446
|
|
|
{ |
2447
|
|
|
$ver = trim($ver); |
2448
|
|
|
if (strpos($ver, 'SMF') === 0) |
2449
|
|
|
$versions[] = $ver; |
2450
|
|
|
} |
2451
|
|
|
} |
2452
|
|
|
$smcFunc['db_free_result']($request); |
2453
|
|
|
|
2454
|
|
|
// Just in case we don't have ANYthing. |
2455
|
|
|
if (empty($versions)) |
2456
|
|
|
$versions = array('SMF 2.0'); |
2457
|
|
|
|
2458
|
|
|
foreach ($versions as $id => $version) |
2459
|
|
|
if (strpos($version, strtoupper($_REQUEST['search'])) !== false) |
2460
|
|
|
$xml_data['items']['children'][] = array( |
2461
|
|
|
'attributes' => array( |
2462
|
|
|
'id' => $id, |
2463
|
|
|
), |
2464
|
|
|
'value' => $version, |
2465
|
|
|
); |
2466
|
|
|
|
2467
|
|
|
return $xml_data; |
2468
|
|
|
} |
2469
|
|
|
|
2470
|
|
|
?> |