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