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