Merge changes made in revisions #r9405 to #r9467
[phpbb.git] / phpBB / includes / bbcode.php
blob22aa43431e7895258d1e1aa862f2de734a3f85b7
1 18.04.2009<?php
2 /**
4 * @package phpBB3
5 * @version $Id$
6 * @copyright (c) 2005 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
9 */
11 /**
12 * @ignore
14 if (!defined('IN_PHPBB'))
16 exit;
19 /**
20 * BBCode class
21 * @package phpBB3
23 class bbcode
25 var $bbcode_uid = '';
26 var $bbcode_bitfield = '';
27 var $bbcode_cache = array();
28 var $bbcode_template = array();
30 var $bbcodes = array();
32 var $template_bitfield;
33 var $template_filename = '';
35 /**
36 * Constructor
37 * Init bbcode cache entries if bitfield is specified
39 function __construct($bitfield = '')
41 if ($bitfield)
43 $this->bbcode_bitfield = $bitfield;
44 $this->bbcode_cache_init();
48 /**
49 * Second pass bbcodes
51 function bbcode_second_pass(&$message, $bbcode_uid = '', $bbcode_bitfield = false)
53 if ($bbcode_uid)
55 $this->bbcode_uid = $bbcode_uid;
58 if ($bbcode_bitfield !== false)
60 $this->bbcode_bitfield = $bbcode_bitfield;
62 // Init those added with a new bbcode_bitfield (already stored codes will not get parsed again)
63 $this->bbcode_cache_init();
66 if (!$this->bbcode_bitfield)
68 // Remove the uid from tags that have not been transformed into HTML
69 if ($this->bbcode_uid)
71 $message = str_replace(':' . $this->bbcode_uid, '', $message);
74 return;
77 $str = array('search' => array(), 'replace' => array());
78 $preg = array('search' => array(), 'replace' => array());
80 $bitfield = new bitfield($this->bbcode_bitfield);
81 $bbcodes_set = $bitfield->get_all_set();
83 $undid_bbcode_specialchars = false;
84 foreach ($bbcodes_set as $bbcode_id)
86 if (!empty($this->bbcode_cache[$bbcode_id]))
88 foreach ($this->bbcode_cache[$bbcode_id] as $type => $array)
90 foreach ($array as $search => $replace)
92 ${$type}['search'][] = str_replace('$uid', $this->bbcode_uid, $search);
93 ${$type}['replace'][] = $replace;
96 if (sizeof($str['search']))
98 $message = str_replace($str['search'], $str['replace'], $message);
99 $str = array('search' => array(), 'replace' => array());
102 if (sizeof($preg['search']))
104 // we need to turn the entities back into their original form to allow the
105 // search patterns to work properly
106 if (!$undid_bbcode_specialchars)
108 $message = str_replace(array('&#58;', '&#46;'), array(':', '.'), $message);
109 $undid_bbcode_specialchars = true;
112 $message = preg_replace($preg['search'], $preg['replace'], $message);
113 $preg = array('search' => array(), 'replace' => array());
119 // Remove the uid from tags that have not been transformed into HTML
120 $message = str_replace(':' . $this->bbcode_uid, '', $message);
124 * Init bbcode cache
126 * requires: $this->bbcode_bitfield
127 * sets: $this->bbcode_cache with bbcode templates needed for bbcode_bitfield
129 function bbcode_cache_init()
131 if (empty($this->template_filename))
133 $this->template_bitfield = new bitfield(phpbb::$user->theme['bbcode_bitfield']);
134 $this->template_filename = PHPBB_ROOT_PATH . 'styles/' . phpbb::$user->theme['template_path'] . '/template/bbcode.html';
136 if (!@file_exists($this->template_filename))
138 trigger_error('The file ' . $this->template_filename . ' is missing.', E_USER_ERROR);
142 $bbcode_ids = $rowset = $sql = array();
144 $bitfield = new bitfield($this->bbcode_bitfield);
145 $bbcodes_set = $bitfield->get_all_set();
147 foreach ($bbcodes_set as $bbcode_id)
149 if (isset($this->bbcode_cache[$bbcode_id]))
151 // do not try to re-cache it if it's already in
152 continue;
154 $bbcode_ids[] = $bbcode_id;
156 if ($bbcode_id > NUM_CORE_BBCODES)
158 $sql[] = $bbcode_id;
162 if (sizeof($sql))
164 $sql = 'SELECT *
165 FROM ' . BBCODES_TABLE . '
166 WHERE ' . phpbb::$db->sql_in_set('bbcode_id', $sql);
167 $result = phpbb::$db->sql_query($sql, 3600);
169 while ($row = phpbb::$db->sql_fetchrow($result))
171 // To circumvent replacing newlines with <br /> for the generated html,
172 // we use carriage returns here. They are later changed back to newlines
173 $row['bbcode_tpl'] = str_replace("\n", "\r", $row['bbcode_tpl']);
174 $row['second_pass_replace'] = str_replace("\n", "\r", $row['second_pass_replace']);
176 $rowset[$row['bbcode_id']] = $row;
178 phpbb::$db->sql_freeresult($result);
181 foreach ($bbcode_ids as $bbcode_id)
183 switch ($bbcode_id)
185 case 0:
186 $this->bbcode_cache[$bbcode_id] = array(
187 'str' => array(
188 '[/quote:$uid]' => $this->bbcode_tpl('quote_close', $bbcode_id)
190 'preg' => array(
191 '#\[quote(?:=&quot;(.*?)&quot;)?:$uid\]((?!\[quote(?:=&quot;.*?&quot;)?:$uid\]).)?#ise' => "\$this->bbcode_second_pass_quote('\$1', '\$2')"
194 break;
196 case 1:
197 $this->bbcode_cache[$bbcode_id] = array(
198 'str' => array(
199 '[b:$uid]' => $this->bbcode_tpl('b_open', $bbcode_id),
200 '[/b:$uid]' => $this->bbcode_tpl('b_close', $bbcode_id),
203 break;
205 case 2:
206 $this->bbcode_cache[$bbcode_id] = array(
207 'str' => array(
208 '[i:$uid]' => $this->bbcode_tpl('i_open', $bbcode_id),
209 '[/i:$uid]' => $this->bbcode_tpl('i_close', $bbcode_id),
212 break;
214 case 3:
215 $this->bbcode_cache[$bbcode_id] = array(
216 'preg' => array(
217 '#\[url:$uid\]((.*?))\[/url:$uid\]#s' => $this->bbcode_tpl('url', $bbcode_id),
218 '#\[url=([^\[]+?):$uid\](.*?)\[/url:$uid\]#s' => $this->bbcode_tpl('url', $bbcode_id),
221 break;
223 case 4:
224 if (phpbb::$user->optionget('viewimg'))
226 $this->bbcode_cache[$bbcode_id] = array(
227 'preg' => array(
228 '#\[img:$uid\](.*?)\[/img:$uid\]#s' => $this->bbcode_tpl('img', $bbcode_id),
232 else
234 $this->bbcode_cache[$bbcode_id] = array(
235 'preg' => array(
236 '#\[img:$uid\](.*?)\[/img:$uid\]#s' => str_replace('$2', '[ img ]', $this->bbcode_tpl('url', $bbcode_id, true)),
240 break;
242 case 5:
243 $this->bbcode_cache[$bbcode_id] = array(
244 'preg' => array(
245 '#\[size=([\-\+]?\d+):$uid\](.*?)\[/size:$uid\]#s' => $this->bbcode_tpl('size', $bbcode_id),
248 break;
250 case 6:
251 $this->bbcode_cache[$bbcode_id] = array(
252 'preg' => array(
253 '!\[color=(#[0-9a-f]{6}|[a-z\-]+):$uid\](.*?)\[/color:$uid\]!is' => $this->bbcode_tpl('color', $bbcode_id),
256 break;
258 case 7:
259 $this->bbcode_cache[$bbcode_id] = array(
260 'str' => array(
261 '[u:$uid]' => $this->bbcode_tpl('u_open', $bbcode_id),
262 '[/u:$uid]' => $this->bbcode_tpl('u_close', $bbcode_id),
265 break;
267 case 8:
268 $this->bbcode_cache[$bbcode_id] = array(
269 'preg' => array(
270 '#\[code(?:=([a-z]+))?:$uid\](.*?)\[/code:$uid\]#ise' => "\$this->bbcode_second_pass_code('\$1', '\$2')",
273 break;
275 case 9:
276 $this->bbcode_cache[$bbcode_id] = array(
277 'preg' => array(
278 '#(\[\/?(list|\*):[mou]?:?$uid\])[\n]{1}#' => "\$1",
279 '#(\[list=([^\[]+):$uid\])[\n]{1}#' => "\$1",
280 '#\[list=([^\[]+):$uid\]#e' => "\$this->bbcode_list('\$1')",
282 'str' => array(
283 '[list:$uid]' => $this->bbcode_tpl('ulist_open_default', $bbcode_id),
284 '[/list:u:$uid]' => $this->bbcode_tpl('ulist_close', $bbcode_id),
285 '[/list:o:$uid]' => $this->bbcode_tpl('olist_close', $bbcode_id),
286 '[*:$uid]' => $this->bbcode_tpl('listitem', $bbcode_id),
287 '[/*:$uid]' => $this->bbcode_tpl('listitem_close', $bbcode_id),
288 '[/*:m:$uid]' => $this->bbcode_tpl('listitem_close', $bbcode_id)
291 break;
293 case 10:
294 $this->bbcode_cache[$bbcode_id] = array(
295 'preg' => array(
296 '#\[email:$uid\]((.*?))\[/email:$uid\]#is' => $this->bbcode_tpl('email', $bbcode_id),
297 '#\[email=([^\[]+):$uid\](.*?)\[/email:$uid\]#is' => $this->bbcode_tpl('email', $bbcode_id)
300 break;
302 case 11:
303 if (phpbb::$user->optionget('viewflash'))
305 $this->bbcode_cache[$bbcode_id] = array(
306 'preg' => array(
307 '#\[flash=([0-9]+),([0-9]+):$uid\](.*?)\[/flash:$uid\]#' => $this->bbcode_tpl('flash', $bbcode_id),
311 else
313 $this->bbcode_cache[$bbcode_id] = array(
314 'preg' => array(
315 '#\[flash=([0-9]+),([0-9]+):$uid\](.*?)\[/flash:$uid\]#' => str_replace('$1', '$3', str_replace('$2', '[ flash ]', $this->bbcode_tpl('url', $bbcode_id, true)))
319 break;
321 case 12:
322 $this->bbcode_cache[$bbcode_id] = array(
323 'str' => array(
324 '[/attachment:$uid]' => $this->bbcode_tpl('inline_attachment_close', $bbcode_id)
326 'preg' => array(
327 '#\[attachment=([0-9]+):$uid\]#' => $this->bbcode_tpl('inline_attachment_open', $bbcode_id)
330 break;
332 default:
333 if (isset($rowset[$bbcode_id]))
335 if ($this->template_bitfield->get($bbcode_id))
337 // The bbcode requires a custom template to be loaded
338 if (!$bbcode_tpl = $this->bbcode_tpl($rowset[$bbcode_id]['bbcode_tag'], $bbcode_id))
340 // For some reason, the required template seems not to be available, use the default template
341 $bbcode_tpl = (!empty($rowset[$bbcode_id]['second_pass_replace'])) ? $rowset[$bbcode_id]['second_pass_replace'] : $rowset[$bbcode_id]['bbcode_tpl'];
343 else
345 // In order to use templates with custom bbcodes we need
346 // to replace all {VARS} to corresponding backreferences
347 // Note that backreferences are numbered from bbcode_match
348 if (preg_match_all('/\{(URL|LOCAL_URL|EMAIL|TEXT|SIMPLETEXT|IDENTIFIER|COLOR|NUMBER)[0-9]*\}/', $rowset[$bbcode_id]['bbcode_match'], $m))
350 foreach ($m[0] as $i => $tok)
352 $bbcode_tpl = str_replace($tok, '$' . ($i + 1), $bbcode_tpl);
357 else
359 // Default template
360 $bbcode_tpl = (!empty($rowset[$bbcode_id]['second_pass_replace'])) ? $rowset[$bbcode_id]['second_pass_replace'] : $rowset[$bbcode_id]['bbcode_tpl'];
363 // Replace {L_*} lang strings
364 $bbcode_tpl = preg_replace('/{L_([A-Z_]+)}/e', "phpbb::\$user->lang('\$1')", $bbcode_tpl);
366 if (!empty($rowset[$bbcode_id]['second_pass_replace']))
368 // The custom BBCode requires second-pass pattern replacements
369 $this->bbcode_cache[$bbcode_id] = array(
370 'preg' => array($rowset[$bbcode_id]['second_pass_match'] => $bbcode_tpl)
373 else
375 $this->bbcode_cache[$bbcode_id] = array(
376 'str' => array($rowset[$bbcode_id]['second_pass_match'] => $bbcode_tpl)
380 else
382 $this->bbcode_cache[$bbcode_id] = false;
384 break;
390 * Return bbcode template
392 function bbcode_tpl($tpl_name, $bbcode_id = -1, $skip_bitfield_check = false)
394 static $bbcode_hardtpl = array();
395 if (empty($bbcode_hardtpl))
397 $bbcode_hardtpl = array(
398 'b_open' => '<span style="font-weight: bold">',
399 'b_close' => '</span>',
400 'i_open' => '<span style="font-style: italic">',
401 'i_close' => '</span>',
402 'u_open' => '<span style="text-decoration: underline">',
403 'u_close' => '</span>',
404 'img' => '<img src="$1" alt="' . phpbb::$user->lang['IMAGE'] . '" />',
405 'size' => '<span style="font-size: $1%; line-height: normal">$2</span>',
406 'color' => '<span style="color: $1">$2</span>',
407 'email' => '<a href="mailto:$1">$2</a>'
411 if ($bbcode_id != -1 && !$skip_bitfield_check && !$this->template_bitfield->get($bbcode_id))
413 return (isset($bbcode_hardtpl[$tpl_name])) ? $bbcode_hardtpl[$tpl_name] : false;
416 if (empty($this->bbcode_template))
418 if (($tpl = file_get_contents($this->template_filename)) === false)
420 trigger_error('Could not load bbcode template', E_USER_ERROR);
423 // replace \ with \\ and then ' with \'.
424 $tpl = str_replace('\\', '\\\\', $tpl);
425 $tpl = str_replace("'", "\'", $tpl);
427 // strip newlines and indent
428 $tpl = preg_replace("/\n[\n\r\s\t]*/", '', $tpl);
430 // Turn template blocks into PHP assignment statements for the values of $bbcode_tpl..
431 $this->bbcode_template = array();
433 $matches = preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END (?:.*?) -->#', $tpl, $match);
435 for ($i = 0; $i < $matches; $i++)
437 if (empty($match[1][$i]))
439 continue;
442 $this->bbcode_template[$match[1][$i]] = $this->bbcode_tpl_replace($match[1][$i], $match[2][$i]);
446 return (isset($this->bbcode_template[$tpl_name])) ? $this->bbcode_template[$tpl_name] : ((isset($bbcode_hardtpl[$tpl_name])) ? $bbcode_hardtpl[$tpl_name] : false);
450 * Return bbcode template replacement
452 function bbcode_tpl_replace($tpl_name, $tpl)
454 static $replacements = array(
455 'quote_username_open' => array('{USERNAME}' => '$1'),
456 'color' => array('{COLOR}' => '$1', '{TEXT}' => '$2'),
457 'size' => array('{SIZE}' => '$1', '{TEXT}' => '$2'),
458 'img' => array('{URL}' => '$1'),
459 'flash' => array('{WIDTH}' => '$1', '{HEIGHT}' => '$2', '{URL}' => '$3'),
460 'url' => array('{URL}' => '$1', '{DESCRIPTION}' => '$2'),
461 'email' => array('{EMAIL}' => '$1', '{DESCRIPTION}' => '$2')
464 $tpl = preg_replace('/{L_([A-Z_]+)}/e', "phpbb::\$user->lang('\$1')", $tpl);
466 if (!empty($replacements[$tpl_name]))
468 $tpl = strtr($tpl, $replacements[$tpl_name]);
471 return trim($tpl);
475 * Second parse list bbcode
477 function bbcode_list($type)
479 if ($type == '')
481 $tpl = 'ulist_open_default';
482 $type = 'default';
484 else if ($type == 'i')
486 $tpl = 'olist_open';
487 $type = 'lower-roman';
489 else if ($type == 'I')
491 $tpl = 'olist_open';
492 $type = 'upper-roman';
494 else if (preg_match('#^(disc|circle|square)$#i', $type))
496 $tpl = 'ulist_open';
497 $type = strtolower($type);
499 else if (preg_match('#^[a-z]$#', $type))
501 $tpl = 'olist_open';
502 $type = 'lower-alpha';
504 else if (preg_match('#[A-Z]#', $type))
506 $tpl = 'olist_open';
507 $type = 'upper-alpha';
509 else if (is_numeric($type))
511 $tpl = 'olist_open';
512 $type = 'decimal';
514 else
516 $tpl = 'olist_open';
517 $type = 'decimal';
520 return str_replace('{LIST_TYPE}', $type, $this->bbcode_tpl($tpl));
524 * Second parse quote tag
526 function bbcode_second_pass_quote($username, $quote)
528 // when using the /e modifier, preg_replace slashes double-quotes but does not
529 // seem to slash anything else
530 $quote = str_replace('\"', '"', $quote);
531 $username = str_replace('\"', '"', $username);
533 // remove newline at the beginning
534 if ($quote == "\n")
536 $quote = '';
539 $quote = (($username) ? str_replace('$1', $username, $this->bbcode_tpl('quote_username_open')) : $this->bbcode_tpl('quote_open')) . $quote;
541 return $quote;
545 * Second parse code tag
547 function bbcode_second_pass_code($type, $code)
549 // when using the /e modifier, preg_replace slashes double-quotes but does not
550 // seem to slash anything else
551 $code = str_replace('\"', '"', $code);
553 switch ($type)
555 case 'php':
556 // Not the english way, but valid because of hardcoded syntax highlighting
557 if (strpos($code, '<span class="syntaxdefault"><br /></span>') === 0)
559 $code = substr($code, 41);
562 // no break;
564 default:
565 $code = str_replace("\t", '&nbsp; &nbsp;', $code);
566 $code = str_replace(' ', '&nbsp; ', $code);
567 $code = str_replace(' ', ' &nbsp;', $code);
569 // remove newline at the beginning
570 if (!empty($code) && $code[0] == "\n")
572 $code = substr($code, 1);
574 break;
577 $code = $this->bbcode_tpl('code_open') . $code . $this->bbcode_tpl('code_close');
579 return $code;