PageRenderTime 97ms CodeModel.GetById 35ms app.highlight 43ms RepoModel.GetById 4ms app.codeStats 1ms

/shared/codes.php

https://github.com/altatof/yacs
PHP | 4235 lines | 2154 code | 753 blank | 1328 comment | 384 complexity | 16fac73c75f329bb4e89037c5b60a86a MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Transform some text containing UBB-like code sequences.
   4 *
   5 * @todo CDATA for proxy http://javascript.about.com/library/blxhtml.htm
   6 * @todo &#91;files] - most recent files, in a compact list
   7 * @todo &#91;files=section:&lt;id>] - files attached in the given section
   8 * @todo &#91;links] - most recent links, in a compact list
   9 * @todo &#91;links=section:&lt;id>] - links attached in the given section
  10 * @todo for [read, add hits aside
  11 * @todo add a code to link images with clickable maps
  12 * @todo replace marquee with our own customizable scroller
  13 * @todo WiKi rendering for lists
  14 *
  15 * This module uses the Skin class for the actual rendering.
  16 *
  17 * Basic codes, demonstrated into [link]codes/basic.php[/link]:
  18 * - **...** - wiki bold text
  19 * - &#91;b]...[/b] - bold text
  20 * - //...// - italics
  21 * - &#91;i]...[/i] - italics
  22 * - __...__ - underlined
  23 * - &#91;u]...[/u] - underlined
  24 * - ##...## - monospace
  25 * - &#91;code]...[/code] - a short sample of fixed-size text (e.g. a file name)
  26 * - &#91;color]...[/color] - change font color
  27 * - &#91;tiny]...[/tiny] - tiny size
  28 * - &#91;small]...[/small] - small size
  29 * - &#91;big]...[/big] - big size
  30 * - &#91;huge]...[/huge] - huge size
  31 * - &#91;subscript]...[/subscript] - subscript
  32 * - &#91;superscript]...[/superscript] - superscript
  33 * - ++...++ - inserted
  34 * - &#91;inserted]...[/inserted] - inserted
  35 * - --...-- - deleted
  36 * - &#91;deleted]...[/deleted] - deleted
  37 * - &#91;flag]...[/flag] - draw attention
  38 * - &#91;lang=xy]...[/lang] - show some text only on matching language
  39 * - &#91;style=sans-serif]...[/style] - use a sans-serif font
  40 * - &#91;style=serif]...[/style] - use a serif font
  41 * - &#91;style=cursive]...[/style] - mimic hand writing
  42 * - &#91;style=comic]...[/style] - make it funny
  43 * - &#91;style=fantasy]...[/style] - guess what will appear
  44 * - &#91;style=my_style]...[/style] - translated to &lt;span class="my_style"&gt;...&lt;/span&gt;
  45 *
  46 * @see codes/basic.php
  47 *
  48 * Block codes, demonstrated in [link]codes/blocks.php[/link]:
  49 * - &#91;indent]...[/indent] - shift text to the right
  50 * - &#91;center]...[/center] - some centered text
  51 * - &#91;right]...[/right] - some right-aligned text
  52 * - &#91;decorated]...[/decorated] - some pretty paragraphs
  53 * - &#91;caution]...[/caution] - a warning paragraph
  54 * - &#91;note]...[/note] - a noticeable paragraph
  55 * - &#91;php]...[/php] - a snippet of php
  56 * - &#91;snippet]...[/snippet] - a snippet of fixed font data
  57 * - &#91;quote]...[/quote] - a block of quoted text
  58 * - &#91;folded]...[/folded] - click to view its content, or to fold it away
  59 * - &#91;folded=foo bar]...[/folded] - with title 'foo bar'
  60 * - &#91;unfolded]...[/unfolded] - click to fold
  61 * - &#91;unfolded=foo bar]...[/unfolded] - with title 'foo bar'
  62 * - &#91;sidebar]...[/sidebar] - a nice box aside
  63 * - &#91;sidebar=foo bar]...[/sidebar] - with title 'foo bar'
  64 * - &#91;scroller]...[/scroller] - some scrolling text
  65 *
  66 * @see codes/blocks.php
  67 *
  68 * List codes, demonstrated in [link]codes/lists.php[/link]:
  69 * - &#91;*] - for simple lists
  70 * - &#91;list]...[/list] - bulleted list
  71 * - &#91;list=1]...[/list] - numbered list, use numbers
  72 * - &#91;list=a]...[/list] - numbered list, use letters
  73 * - &#91;list=A]...[/list] - numbered list, use capital letters
  74 * - &#91;list=i]...[/list] - numbered list, use roman numbers
  75 * - &#91;list=I]...[/list] - numbered list, use upper case roman numbers
  76 *
  77 * @see codes/lists.php
  78 *
  79 * Codes for links, demonstrated in [link]codes/links.php[/link]:
  80 * - &lt;url&gt; - &lt;a href="url">url&lt;/a> or &lt;a href="url" class="external">url&lt;/a>
  81 * - &#91;link]&lt;url&gt;[/link] - &lt;a href="url">url&lt;/a> or &lt;a href="url" class="external">url&lt;/a>
  82 * - &#91;&lt;label&gt;|&lt;url&gt;] - &lt;a href="url">label&lt;/a> or &lt;a href="url" class="external">label&lt;/a>
  83 * - &#91;link=&lt;label&gt;]&lt;url&gt;[/link] - &lt;a href="url">label&lt;/a> or &lt;a href="url" class="external">label&lt;/a>
  84 * - &#91;url]&lt;url&gt;[/url] - deprecated by &#91;link]
  85 * - &#91;url=&lt;url&gt;]&lt;label&gt;[/url] - deprecated by &#91;link]
  86 * - &#91;button=&lt;label&gt;|&lt;url&gt;] - build simple buttons with css
  87 * - &#91;click=&lt;label&gt;|&lt;url&gt;] - a button that counts clicks
  88 * - &#91;clicks=&lt;url&gt;] - lists people who have clicked
  89 * - &lt;address&gt; - &lt;a href="mailto:address" class="email">address&lt;/a>
  90 * - &#91;email]&lt;address&gt;[/email] - &lt;a href="mailto:address" class="email">address&lt;/a>
  91 * - &#91;email=&lt;name&gt;]&lt;address&gt;[/email] - &lt;a href="mailto:address" class="email">name&lt;/a>
  92 * - &#91;go=&lt;name&gt;, &lt;label&gt;] - trigger the selector on 'name'
  93 * - &#91;&#91;&lt;name&gt;, &lt;label&gt;]] - Wiki selector
  94 * - &#91;article=&lt;id>] - use article title as link label
  95 * - &#91;article=&lt;id>, foo bar] - with label 'foo bar'
  96 * - &#91;article.description=&lt;id>] - insert article description
  97 * - &#91;next=&lt;id>] - shortcut to next article
  98 * - &#91;next=&lt;id>, foo bar] - with label 'foo bar'
  99 * - &#91;previous=&lt;id>] - shortcut to previous article
 100 * - &#91;previous=&lt;id>, foo bar] - with label 'foo bar'
 101 * - &#91;random] - pick up one page randomly
 102 * - &#91;random=&lt;section:id>] - one page in this section
 103 * - &#91;section=&lt;id>] - use section title as link label
 104 * - &#91;section=&lt;id>, foo bar] - with label 'foo bar'
 105 * - &#91;category=&lt;id>] - use category title as link label
 106 * - &#91;category=&lt;id>, foo bar] - with label 'foo bar'
 107 * - &#91;category.description=&lt;id>] - insert category description
 108 * - &#91;user=&lt;id>] - use nick name as link label
 109 * - &#91;user=&lt;id>, foo bar] - with label 'foo bar'
 110 * - &#91;server=&lt;id>] - use server title as link label
 111 * - &#91;server=&lt;id>, foo bar] - with label 'foo bar'
 112 * - &#91;file=&lt;id>] - use file title as link label
 113 * - &#91;file=&lt;id>, foo bar] - with label 'foo bar'
 114 * - &#91;download=&lt;id>] - a link to download a file
 115 * - &#91;download=&lt;id>, foo bar] - with label 'foo bar'
 116 * - &#91;comment=&lt;id>] - use comment id in link label
 117 * - &#91;comment=&lt;id>, foo bar] - with label 'foo bar'
 118 * - &#91;script]&lt;path/script.php&gt;[/script] - to the phpDoc page for script 'path/script.php'
 119 * - &#91;search] - a search form
 120 * - &#91;search=&lt;word&gt;] - hit Enter to search for 'word'
 121 * - &#91;wikipedia=&lt;keyword] - search Wikipedia
 122 * - &#91;wikipedia=&lt;keyword, foo bar] - search Wikipedia, with label 'foo bar'
 123 * - &#91;proxy]&lt;url&gt;[/proxy] - proxy a remote address
 124 *
 125 * @see codes/links.php
 126 *
 127 * Titles and questions, demonstrated in [link]codes/titles.php[/link]:
 128 * - &#91;toc] - table of contents
 129 * - ==...== - a level 1 headline
 130 * - &#91;title]...[/title] - a level 1 headline, put in the table of contents
 131 * - ===...=== - a level 2 headline
 132 * - &#91;subtitle]...[/subtitle] - a level 2 headline
 133 * - &#91;header1]...[/header1] - a level 1 headline
 134 * - &#91;header2]...[/header2] - a level 2 headline
 135 * - &#91;header3]...[/header3] - a level 3 headline
 136 * - &#91;header4]...[/header4] - a level 4 headline
 137 * - &#91;header5]...[/header5] - a level 5 headline
 138 * - &#91;toq] - the table of questions for this page
 139 * - &#91;question]...[/question] - a question-title
 140 * - &#91;question] - a simple question
 141 * - &#91;answer] - some answer in a FAQ
 142 *
 143 * @see codes/titles.php
 144 *
 145 * Tables, demonstrated in [link]codes/tables.php[/link]:
 146 * - &#91;table]...[/table] - one simple table
 147 * - &#91;table=grid]...[/table] - add a grid
 148 * - &#91;table].[body].[/table] - a table with headers
 149 * - &#91;csv]...[/csv] - import some data from a spreadsheet
 150 * - &#91;csv=;]...[/csv] - import some data from a spreadsheet
 151 * - &#91;table.json] - format a table as json
 152 *
 153 * @see codes/tables.php
 154 *
 155 * Live codes, demonstrated in [link]codes/live.php[/link]:
 156 * - &#91;sections] - site map
 157 * - &#91;sections=section:&lt;id>] - sub-sections
 158 * - &#91;sections=self] - sections assigned to current surfer
 159 * - &#91;sections=user:&lt;id>] - sections assigned to given user
 160 * - &#91;categories] - category tree
 161 * - &#91;categories=category:&lt;id>] - sub-categories
 162 * - &#91;categories=self] - categories assigned to current surfer
 163 * - &#91;categories=user:&lt;id>] - categories assigned to given user
 164 * - &#91;published] - most recent published pages, in a compact list
 165 * - &#91;published=section:&lt;id>] - articles published most recently in the given section
 166 * - &#91;published=category:&lt;id>] - articles published most recently in the given category
 167 * - &#91;published=user:&lt;id>] - articles published most recently created by given user
 168 * - &#91;published.decorated=self, 20] - 20 most recent pages from current surfer, as a decorated list
 169 * - &#91;updated] - most recent updated pages, in a compact list
 170 * - &#91;updated=section:&lt;id>] - articles updated most recently in the given section
 171 * - &#91;updated=category:&lt;id>] - articles updated most recently in the given category
 172 * - &#91;updated=user:&lt;id>] - articles updated most recently created by given user
 173 * - &#91;updated.simple=self, 12] - articles updated most recently created by current surfer, as a simple list
 174 * - &#91;read] - most read articles, in a compact list
 175 * - &#91;read=section:&lt;id>] - articles of fame in the given section
 176 * - &#91;read=self] - personal hits
 177 * - &#91;read=user:&lt;id>] - personal hits
 178 * - &#91;voted] - most voted articles, in a compact list
 179 * - &#91;voted=section:&lt;id>] - articles of fame in the given section
 180 * - &#91;voted=self] - personal hits
 181 * - &#91;voted=user:&lt;id>] - personal hits
 182 * - &#91;users=present] - list of users present on site
 183 *
 184 * @see codes/live.php
 185 *
 186 * Widgets, demonstrated in [link]codes/widgets.php[/link]:
 187 * - &#91;newsfeed=url] - integrate a newsfeed dynamically
 188 * - &#91;newsfeed.embed=url] - integrate a newsfeed dynamically
 189 * - &#91;twitter=id] - twitter updates of one person
 190 * - &#91;tsearch=token] - twitter search on a given topic
 191 * - &#91;iframe=&lt;width&gt;, &lt;height&gt;]&lt;url&gt;[/iframe] - include some external page
 192 * - &#91;cloud] - the tags used at this site
 193 * - &#91;cloud=12] - maximum count of tags used at this site
 194 * - &#91;calendar] - events for this month
 195 * - &#91;calendar=section:&lt;id>] - dates in one section
 196 * - &#91;locations=all] - newest locations
 197 * - &#91;locations=users] - map user locations on Google maps
 198 * - &#91;location=latitude, longitude, label] - to build a dynamic map
 199 *
 200 * @see codes/widgets.php
 201 *
 202 * Miscellaneous codes, demonstrated in [link]codes/misc.php[/link]:
 203 * - &#91;hint=&lt;help popup]...[/hint] - &lt;acronym tite="help popup">...&lt;/acronym>
 204 * - &#91;nl] - new line
 205 * - ----... - line break
 206 * - &#91;---] or &#91;___] - horizontal rule
 207 * - &#91;new] - something new
 208 * - &#91;popular] - people love it
 209 * - &#91;be] - country flag
 210 * - &#91;ca] - country flag
 211 * - &#91;ch] - country flag
 212 * - &#91;de] - country flag
 213 * - &#91;en] - country flag
 214 * - &#91;es] - country flag
 215 * - &#91;fr] - country flag
 216 * - &#91;gb] - country flag
 217 * - &#91;gr] - country flag
 218 * - &#91;it] - country flag
 219 * - &#91;pt] - country flag
 220 * - &#91;us] - country flag
 221 * - &#91;chart]...[/chart] - draw a dynamic chart
 222 * - &#91;execute=script] - include another local script
 223 * - &#91;redirect=link] - jump to another local page
 224 * - &#91;parameter=name] - value of one attribute of the global context
 225 * - &#91;escape]...[/escape]
 226 * - &#91;anonymous]...[/anonymous] - for non-logged people only
 227 * - &#91;authenticated]...[/authenticated] - for logged members only
 228 * - &#91;associate]...[/associate] - for associates only
 229 *
 230 * @see codes/misc.php
 231 *
 232 * In-line elements:
 233 * - &#91;embed=&lt;id>, &lt;width>, &lt;height>, &lt;flashparams>] - embed a multimedia file
 234 * - &#91;embed=&lt;id>, window] - render a multimedia file in a separate window
 235 * - &#91;sound=&lt;id>] - play a sound
 236 * - &#91;image=&lt;id>] - an inline image
 237 * - &#91;image=&lt;id>,left] - a left-aligned image
 238 * - &#91;image=&lt;id>,center] - a centered image
 239 * - &#91;image=&lt;id>,right] - a right-aligned image
 240 * - &#91;image]src[/image]
 241 * - &#91;image=&lt;alt>]src[/image]
 242 * - &#91;images=&lt;id1>, &lt;id2>, ...] - a stack of images
 243 * - &#91;img]src[/img] (deprecated)
 244 * - &#91;img=&lt;alt>]src[/img] (deprecated)
 245 * - &#91;table=&lt;id>] - an inline table
 246 * - &#91;location=&lt;id>] - embed a map
 247 * - &#91;location=&lt;id>, foo bar] - with label 'foo bar'
 248 * - &#91;clear] - to introduce breaks after floating elements
 249 *
 250 * @link http://www.estvideo.com/dew/index/2005/02/16/370-player-flash-mp3-leger-comme-une-plume the dewplayer page
 251 *
 252 * Other codes:
 253 * - &#91;menu=label]url[/menu] - one of the main menu command
 254 * - &#91;submenu=label]url[/submenu] - one of the second-level menu commands
 255 *
 256 * This script attempts to fight bbCode code injections by filtering strings to be used
 257 * as [code]src[/code] or as [code]href[/code] attributes (Thank you Mordread).
 258 *
 259 * @author Bernard Paques
 260 * @author Mordread Wallas
 261 * @author GnapZ
 262 * @author Alain Lesage (Lasares)
 263 * @tester Viviane Zaniroli
 264 * @tester Agnes
 265 * @tester Pat
 266 * @tester Guillaume Perez
 267 * @tester Fw_crocodile
 268 * @tester Christian Piercot
 269 * @tester Christian Loubechine
 270 * @tester Daniel Dupuis
 271 * @reference
 272 * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
 273 */
 274Class Codes {
 275
 276	/**
 277	 * beautify some text for final rendering
 278	 *
 279	 * This function is used to transform some text before sending it back to the browser.
 280	 * It actually performs following analysis:
 281	 * - implicit formatting
 282	 * - formatting codes
 283	 * - smileys
 284	 *
 285	 * If the keyword [escape][formatted][/escape] appears at the first line of text,
 286	 * or if options have the keyword ##formatted##, no implicit formatting is performed.
 287	 *
 288	 * If the keyword [escape][hardcoded][/escape] appears at the first line of text,
 289	 * or if options have the keyword ##hardcoded##, the only transformation is new lines to breaks.
 290	 *
 291	 * If options feature the keyword ##compact##, then YACS codes that may
 292	 * generate big objects are removed, such as [escape][table]...[/table][/escape]
 293	 * and [escape][location][/escape].
 294	 *
 295	 * @param string the text to beautify
 296	 * @param string the set of options that apply to this text
 297	 * @return the beautified text
 298	 *
 299	 * @see articles/view.php
 300	 */
 301	public static function &beautify($text, $options='') {
 302		global $context;
 303
 304		// save CPU cycles
 305		$text = trim($text);
 306		if(!$text)
 307			return $text;
 308
 309		//
 310		// looking for compact content
 311		//
 312		if(preg_match('/\bcompact\b/i', $options))
 313			$text = preg_replace(array('/\[table.+?\/table\]/', '/\[location.+?\]/'), '', $text);
 314
 315		//
 316		// implicit formatting
 317		//
 318
 319		// new lines will have to be checked
 320		$new_lines = 'proceed';
 321
 322		// text is already formatted
 323		if(!strncmp($text, '[formatted]', 11)) {
 324			$new_lines = 'none';
 325			$text = substr($text, 11);
 326
 327		// text is already formatted (through options)
 328		} elseif(preg_match('/\bformatted\b/i', $options))
 329			$new_lines = 'none';
 330
 331		// newlines are hard coded
 332		elseif(!strncmp($text, '[hardcoded]', 11)) {
 333			$new_lines = 'hardcoded';
 334			$text = substr($text, 11);
 335
 336		// newlines are hard coded (through options)
 337		} elseif(preg_match('/\bhardcoded\b/i', $options))
 338			$new_lines = 'hardcoded';
 339
 340		// implicit formatting
 341		else
 342			$text =& Codes::beautify_implied($text, 'text');
 343
 344		//
 345		// translate codes
 346		//
 347
 348		// render codes
 349		$text =& Codes::render($text);
 350
 351		// render smileys after codes, else it will break escaped strings
 352		if(is_callable(array('Smileys', 'render_smileys')))
 353			$text = Smileys::render_smileys($text);
 354
 355		// relocate images
 356		$text = str_replace('"skins/', '"'.$context['path_to_root'].'skins/', $text);
 357
 358		//
 359		// adjust end of lines
 360		//
 361
 362		// newlines are hard coded
 363		if($new_lines == 'hardcoded')
 364			$text = nl2br($text);
 365
 366		// implicit formatting
 367		elseif($new_lines == 'proceed')
 368			$text =& Codes::beautify_implied($text, 'newlines');
 369
 370		return $text;
 371	}
 372
 373	/**
 374	 * beautify some text in the extra panel
 375	 *
 376	 * @param string the text to beautify
 377	 * @return the beautified text
 378	 *
 379	 * @see articles/view.php
 380	 */
 381	public static function &beautify_extra($text) {
 382		global $context;
 383
 384		$search = array();
 385		$replace = array();
 386
 387		// [box.extra=title]...[/box]
 388		$search[] = '/\[box\.(extra)=([^\]]+?)\](.*?)\[\/box\]/ise';
 389		$replace[] = "Skin::build_box(stripslashes('$2'), stripslashes('$3'), '$1')";
 390
 391		// [box.navigation=title]...[/box]
 392		$search[] = '/\[box\.(navigation)=([^\]]+?)\](.*?)\[\/box\]/ise';
 393		$replace[] = "Skin::build_box(stripslashes('$2'), stripslashes('$3'), '$1')";
 394
 395		// process all codes
 396		$text = preg_replace($search, $replace, $text);
 397
 398		// regular rendering
 399		$text =& Codes::beautify($text);
 400
 401		return $text;
 402
 403	}
 404
 405	/**
 406	 * render some basic formatting
 407	 *
 408	 * - suppress multiple newlines
 409	 * - render empty lines
 410	 * - render simple bulleted lines
 411	 * - make URL clickable (http://..., www.foo.bar, foo.bar@foo.com)
 412	 *
 413	 * Now this function looks for the keyword &#91;escape] in order
 414	 * to avoid for formatting pre-formatted areas.
 415	 *
 416	 * For example, if you type:
 417	 * [snippet]
 418	 * hello
 419	 * world
 420	 *
 421	 * how are
 422	 * you doing?
 423	 *
 424	 * - my first item
 425	 * - my second item
 426	 *
 427	 * > quoted from
 428	 * > a previous message
 429	 * [/snippet]
 430	 *
 431	 * This will be rendered visually in the browser as:
 432	 * [snippet]
 433	 * hello world
 434	 *
 435	 * how are you doing?
 436	 *
 437	 * - my first item
 438	 * - my second item
 439	 *
 440	 * > quoted from
 441	 * > a previous message
 442	 * [/snippet]
 443	 *
 444	 * @param string the text to transform
 445	 * @param sring either 'text' or 'newlines'
 446	 * @return the modified string
 447	 */
 448	public static function &beautify_implied($text, $variant='text') {
 449
 450		// streamline newlines, even if this has been done elsewhere
 451		$text = str_replace(array("\r\n", "\r"), "\n", $text);
 452
 453		// only change end of lines
 454		if($variant == 'newlines') {
 455
 456			// formatting patterns
 457			$search = array(
 458				"|<br\s*/>\n+|i",		/* don't insert additional \n after <br /> */
 459				"|\n\n+|i"				/* force an html space between paragraphs */
 460				);
 461
 462			$replace = array(
 463				BR,
 464				BR.BR
 465				);
 466
 467		// change everything, except new lines
 468		} else {
 469
 470			// formatting patterns
 471			$search = array(
 472				"|</h1>\n+|i",			/* strip \n after title */
 473				"|</h2>\n+|i",
 474				"|</h3>\n+|i",
 475				"|</h4>\n+|i",
 476				'/http:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_\-]+)[a-zA-Z0-9_\-&=]*/i', // YouTube link
 477				'/http:\/\/youtu\.be\/([a-zA-Z0-9_\-]+)/i', // YouTube link too
 478				"#([\n\t \(])([a-z]+?)://([a-z0-9_\-\.\~\/@&;:=%$\?]+)#ie", /* make URL clickable */
 479				"#([\n\t \(])www\.([a-z0-9\-]+)\.([a-z0-9_\-\.\~]+)((?:/[^,< \r\n\)]*)?)#ie",	/* web server */
 480				"/\n[ \t]*(From|To|cc|bcc|Subject|Date):(\s*)/i",	/* common message headers */
 481				"|\n[ \t]*-(\s+)|i",		/* - list item > */
 482				"|\n[ \t]*>(\s*)|i",		/* quoted by > */
 483				"|\n[ \t]*\|(\s*)|i",		/* quoted by | */
 484				"#([\n\t ])(mailto:|)([a-z0-9_\-\.\~]+?)@([a-z0-9_\-\.\~]+\.[a-z0-9_\-\.\~]+)([\n\t ]*)#ie" /* mail address*/
 485				);
 486
 487			$replace = array(
 488				"</h1>",
 489				"</h2>",
 490				"</h3>",
 491				"</h4>",
 492				'<iframe class="youtube-player" type="text/html" width="445" height="364" src="http://www.youtube.com/embed/$1" frameborder="0"></iframe>', // YouTube link
 493				'<iframe class="youtube-player" type="text/html" width="445" height="364" src="http://www.youtube.com/embed/$1" frameborder="0"></iframe>', // YouTube link too
 494				"'$1'.Skin::build_link('$2://$3', '$2://$3')",
 495				"'$1'.Skin::build_link('http://www.$2.$3$4', 'www.$2.$3$4')",
 496				BR."$1:$2",
 497				BR."-$1",
 498				BR.">$1",
 499				BR."|$1",
 500				"'$1'.Skin::build_link('mailto:$3@$4', '$3@$4', 'email').'$5'"
 501				);
 502		}
 503
 504		// preserve escaped areas
 505		$text = str_replace(array('[escape]', '[/escape]', '[list]', '[/list]', '[php]', '[/php]', '[snippet]', '[/snippet]'),
 506			array('<escape>', '</escape>', '<list>', '</list>', '<php>', '</php>', '<snippet>', '</snippet>'), $text);
 507
 508		// locate pre-formatted areas
 509		$areas = preg_split('#<(code|escape|list|php|snippet|pre)>(.*?)</\1>#is', trim($text), -1, PREG_SPLIT_DELIM_CAPTURE);
 510
 511		// format only adequate areas
 512		$index = 0;
 513		$formatted = '';
 514		$inside = FALSE;
 515		$target = '';
 516		foreach($areas as $area) {
 517
 518			switch($index%3) {
 519			case 0: // area to be formatted
 520
 521				// do not rewrite tags
 522				$items = preg_split('/<(.+?)>/is', $area, -1, PREG_SPLIT_DELIM_CAPTURE);
 523				$where = 0;
 524				foreach($items as $item) {
 525
 526					switch($where%2) {
 527
 528					case 0: // outside a tag
 529						if($inside)
 530							$target .= $item;
 531						else
 532							$formatted .= preg_replace($search, $replace, $item);
 533						break;
 534
 535					case 1: // inside a tag
 536
 537						// inside or outside a link
 538						if($inside && !strncmp($item, '/a', 2)) {
 539							$formatted .= preg_replace($search, $replace, $target).'<'.$item.'>';
 540							$target = '';
 541							$inside = FALSE;
 542						} elseif($inside)
 543							$target .= '<'.$item.'>';
 544						elseif(!strncmp($item, 'a ', 2)) {
 545							$formatted .= '<'.$item.'>';
 546							$inside = TRUE;
 547						} else
 548							$formatted .= '<'.$item.'>';
 549						break;
 550
 551					}
 552					$where++;
 553				}
 554				break;
 555
 556			case 1: // area boundary
 557				$tag = $area;
 558				break;
 559
 560			case 2: // pre-formatted area - left unmodified
 561
 562				// inside a link, or regular text
 563				if($inside)
 564					$target .= '<'.$tag.'>'.$area.'</'.$tag.'>';
 565				else
 566					$formatted .= '<'.$tag.'>'.$area.'</'.$tag.'>';
 567				break;
 568
 569			}
 570			$index++;
 571		}
 572
 573		// post-optimization
 574		if($variant == 'text')
 575			$formatted = preg_replace('#</ul>\n{0,1}<ul>#', '', $formatted);
 576		$formatted = preg_replace('#\n\n+<ul#', "\n<ul", $formatted);
 577
 578		// restore escaped areas
 579		$formatted = str_replace(array('<escape>', '</escape>', '<list>', '</list>', '<php>', '</php>', '<snippet>', '</snippet>'),
 580			array('[escape]', '[/escape]', '[list]', '[/list]', '[php]', '[/php]', '[snippet]', '[/snippet]'), $formatted);
 581
 582		return $formatted;
 583	}
 584
 585	/**
 586	 * format an introduction
 587	 *
 588	 * @param string raw introduction
 589	 * @return string finalized title
 590	 */
 591	public static function &beautify_introduction($text) {
 592
 593		// render codes
 594		$output =& Codes::render($text);
 595
 596		// render smileys after codes, else it will break escaped strings
 597		if(is_callable(array('Smileys', 'render_smileys')))
 598			$output = Smileys::render_smileys($output);
 599
 600		// return by reference
 601		return $output;
 602	}
 603
 604	/**
 605	 * format a title
 606	 *
 607	 * New lines and images are the only things accepted in titles.
 608	 * The goal is to provide a faster service than beautify()
 609	 *
 610	 * @param string raw title
 611	 * @return string finalized title
 612	 */
 613	public static function &beautify_title($text) {
 614
 615		// suppress pairing codes
 616		$output =& Codes::strip($text, FALSE);
 617
 618		// the only code transformed in titles
 619		$output = str_replace(array('[nl]', '[NL]'), '<br />', $output);
 620
 621		// remove everything, except links, breaks and images, and selected tags
 622		$output = strip_tags($output, '<a><abbr><acronym><b><big><br><code><del><div><dfn><em><i><img><ins><p><q><small><span><strong><sub><sup><tt><u>');
 623
 624		// return by reference
 625		return $output;
 626	}
 627
 628	/**
 629	 * determine if a code is already in some text
 630	 *
 631	 * @param string the text to check
 632	 * @param string code to check (e.g., 'embed')
 633	 * @param int the id of the object
 634	 * @return boolean TRUE if the code is present, false otherwise
 635	 */
 636	public static function check_embedded($text, $code, $id) {
 637
 638		// we check the string of digits
 639		$id = strval($id);
 640
 641		// parse the full string
 642		$count = strlen($text);
 643		$position = 0;
 644
 645		// look for '[embed' or similar
 646		while(($position = strpos($text, '['.$code, $position)) !== FALSE) {
 647			$position += 1+strlen($code);
 648
 649			// parse remaining chars
 650			while($position < $count) {
 651
 652				// digits just follow the '=' sign
 653				if($text[$position] == '=') {
 654					$position++;
 655
 656					// exact match
 657					if(($position + 2 + strlen($id) < $count) && !strcmp(substr($text, $position, strlen($id)), $id))
 658						return TRUE;
 659
 660					// not in this code, look at next one
 661					break;
 662
 663				// malformed code
 664				} elseif($text[$position] == ']') {
 665					$position++;
 666					break;
 667				}
 668
 669				// next char
 670				$position++;
 671			}
 672		}
 673
 674		// not found
 675		return FALSE;
 676	}
 677
 678	/**
 679	 * delete a code if it is present in some text
 680	 *
 681	 * @param string the text to check
 682	 * @param string code to check (e.g., 'embed')
 683	 * @param int the id of the object
 684	 * @return string the resulting string
 685	 */
 686	public static function delete_embedded($text, $code, $id) {
 687
 688		// we check the string of digits
 689		$id = strval($id);
 690
 691		// parse the full string
 692		$count = strlen($text);
 693		$position = 0;
 694
 695		// look for '[embed' or similar
 696		while(($position = strpos($text, '['.$code, $position)) !== FALSE) {
 697
 698			// we have to take everything before that point
 699			$prefix = $position;
 700
 701			// next char
 702			$position += 1+strlen($code);
 703
 704			// parse remaining chars
 705			while($position < $count) {
 706
 707				// digits just follow the '=' sign
 708				if($text[$position] == '=') {
 709					$position++;
 710
 711					// exact match
 712					if(($position + strlen($id) <= $count) && !strcmp(substr($text, $position, strlen($id)), $id)) {
 713						$position += strlen($id);
 714
 715						// look for ']'
 716						while($position < $count) {
 717							if($text[$position] == ']') {
 718								$position++;
 719								break;
 720							}
 721							$position++;
 722						}
 723
 724						// do the deletion
 725						$modified = '';
 726						if($prefix > 0)
 727							$modified .= substr($text, 0, $prefix);
 728						if($position < $count)
 729							$modified .= substr($text, $position, $count-$position);
 730						return $modified;
 731					}
 732
 733					// not in this code, look at next one
 734					break;
 735
 736				// malformed code
 737				} elseif($text[$position] == ']') {
 738					$position++;
 739					break;
 740				}
 741
 742				// next char
 743				$position++;
 744			}
 745		}
 746
 747		// not found
 748		return $text;
 749	}
 750
 751	/**
 752	 * fix line breaks
 753	 *
 754	 * This function moves unclosed tags to the beginning of content.
 755	 *
 756	 * @param string input
 757	 * @return string original or modified content
 758	 */
 759	public static function &fix_tags($text) {
 760
 761		// look for opening tag at content end
 762		$last_open = strrpos($text, '<p>');
 763		$last_close = strrpos($text, '</p');
 764		if($last_open && (($last_close === FALSE) || ($last_open > $last_close))) {
 765
 766			// trail
 767			$trail = '';
 768			if(strlen($text) > $last_open + 3)
 769				$trail = substr($text, $last_open + 3);
 770
 771			// move it to content start to restore pairing tags
 772			$text = '<p>'.substr($text, 0, $last_open).$trail;
 773
 774		}
 775
 776		// also fix broken img tags, if any
 777		$text = preg_replace('#<(img[^</]+)>#i', '<$1 />', $text);
 778
 779		// remove slashes added by preg_replace -- only for double quotes
 780		$text = str_replace('\"', '"', $text);
 781
 782		// done
 783		return $text;
 784	}
 785
 786	/**
 787	 * reset global variables used for rendering
 788	 *
 789	 * This function should be called between the processing of different articles in a loop
 790	 *
 791	 * @param string the target URL for this rendering (e.g., 'articles/view.php/123')
 792	 */
 793	public static function initialize($main_target=NULL) {
 794		global $context;
 795
 796		if($main_target)
 797			$context['self_url'] = $context['url_to_root'].$main_target;
 798
 799	}
 800
 801	/**
 802	 * list all ids matching some code
 803	 *
 804	 * @param string the text to check
 805	 * @param string code to check (e.g., 'embed')
 806	 * @return array the list of matching ids
 807	 */
 808	public static function list_embedded($text, $code='embed') {
 809
 810		// all ids we have found
 811		$ids = array();
 812
 813		// parse the full string
 814		$count = strlen($text);
 815		$position = 0;
 816
 817		// look for '[embed' or similar
 818		while(($position = strpos($text, '['.$code, $position)) !== FALSE) {
 819			$position += 1+strlen($code);
 820
 821			// parse remaining chars
 822			while($position < $count) {
 823
 824				// digits just follow the '=' sign
 825				if($text[$position] == '=') {
 826					$position++;
 827
 828					// capture all digits
 829					$id = '';
 830					while($position < $count) {
 831						if(($text[$position] >= '0') && ($text[$position] <= '9')) {
 832							$id .= $text[$position];
 833							$position++;
 834						} else
 835							break;
 836					}
 837
 838					// save this id
 839					if(strlen($id))
 840						$ids[] = $id;
 841
 842					// look at next code
 843					break;
 844
 845				// malformed code
 846				} elseif($text[$position] == ']') {
 847					$position++;
 848					break;
 849				}
 850
 851				// next char
 852				$position++;
 853			}
 854		}
 855
 856		// job done
 857		return $ids;
 858	}
 859
 860	/**
 861	 * transform codes to html
 862	 *
 863	 * [php]
 864	 * // build the page
 865	 * $context['text'] .= ...
 866	 *
 867	 * // transform codes
 868	 * $context['text'] = Codes::render($context['text']);
 869	 *
 870	 * // final rendering
 871	 * render_skin();
 872	 * [/php]
 873	 *
 874	 * @link http://pureform.wordpress.com/2008/01/04/matching-a-word-characters-outside-of-html-tags/
 875	 *
 876	 * @param string the input string
 877	 * @return string the transformed string
 878	 */
 879	public static function &render($text) {
 880		global $context;
 881
 882		// streamline newlines, even if this has been done elsewhere
 883		$text = str_replace(array("\r\n", "\r"), "\n", $text);
 884
 885		// prevent wysiwyg editors to bracket our own tags
 886		$text = preg_replace('#^<p>(\[.+\])</p>$#m', '$1', $text);
 887
 888 		// initialize only once
 889		static $pattern;
 890		if(!isset($pattern)) {
 891
 892//			$pattern[] = ;
 893//			$replace[] = ;
 894//
 895//			$pattern[] = ;
 896//			$replace[] = ;
 897//
 898//			$pattern[] = ;
 899//			$replace[] = ;
 900//
 901//			$pattern[] = ;
 902//			$replace[] = ;
 903//
 904//			$pattern[] = ;
 905//			$replace[] = ;
 906
 907			$pattern = array(
 908				"|<!-- .* -->|i",								// remove HTML comments
 909				'/\[escape\](.*?)\[\/escape\]/ise', 	// [escape]...[/escape] (before everything)
 910				'/\[php\](.*?)\[\/php\]/ise',			// [php]...[/php]
 911				'/\[snippet\](.*?)\[\/snippet\]/ise',	// [snippet]...[/snippet]
 912				'/(\[page\].*)$/is',					// [page] (provide only the first one)
 913				'/\[associate\](.*?)\[\/associate\]/ise', 	// [associate]...[/associate] (save some cycles if at the beginning)
 914				'/\[hidden\](.*?)\[\/hidden\]/ise', 	// [hidden]...[/hidden] obsolete, replaced by [associate]...[/associate]
 915				'/\[authenticated\](.*?)\[\/authenticated\]/ise', // [authenticated]...[/authenticated] (save some cycles if at the beginning)
 916				'/\[restricted\](.*?)\[\/restricted\]/ise', 	// [restricted]...[/restricted] obsolete, replaced by [authenticated]...[/authenticated]
 917				'/\[anonymous\](.*?)\[\/anonymous\]/ise', // [anonymous]...[/anonymous] (save some cycles if at the beginning)
 918				'/\[redirect=([^\]]+?)\]/ise', 			// [redirect=<link>]
 919				'/\[execute=([^\]]+?)\]/ise', 			// [execute=<name>]
 920				'/\[parameter=([^\]]+?)\]/ise', 		// [parameter=<name>]
 921				'/\[lang=([^\]]+?)\](.*?)\[\/lang\]/ise',		// [lang=xy]...[/lang]
 922				'/\[csv=(.)\](.*?)\[\/csv\]/ise',		// [csv=;]...[/csv] (before [table])
 923				'/\[csv\](.*?)\[\/csv\]/ise',			// [csv]...[/csv] (before [table])
 924				'/\[table=([^\]]+?)\](.*?)\[\/table\]/ise', // [table=variant]...[/table]
 925				'/\[table\](.*?)\[\/table\]/ise',		// [table]...[/table]
 926				'/\[images=([^\]]+?)\]/ie', 				// [images=<ids>] (before other links)
 927				'/\[image\](.*?)\[\/image\]/ise',		// [image]src[/image]
 928				'/\[image=([^\]]+?)\](.*?)\[\/image\]/ise', // [image=alt]src[/image]
 929				'/\[img\](.*?)\[\/img\]/ise',			// [img]src[/img]
 930				'/\[img=([^\]]+?)\](.*?)\[\/img\]/ise', 	// [img=alt]src[/img]
 931				'/\[image=([^\]]+?)\]/ie',					// [image=<id>]
 932				'/##(\S.*?\S)##/is',					// ##...##
 933				'/\[code\](.*?)\[\/code\]/is',			// [code]...[/code]
 934				'/\[indent\](.*?)\[\/indent\]/ise', 	// [indent]...[/indent]
 935				'/\[quote\](.*?)\[\/quote\]/ise',		// [quote]...[/quote]
 936				'/\[folded=([^\]]+?)\](.*?)\[\/folded\]\s*/ise',	// [folded=...]...[/folded]
 937				'/\[folded\](.*?)\[\/folded\]\s*/ise',	// [folded]...[/folded]
 938				'/\[folder=([^\]]+?)\](.*?)\[\/folder\]\s*/ise',	// [folder=...]...[/folder]
 939				'/\[folder\](.*?)\[\/folder\]\s*/ise',	// [folder]...[/folder]
 940				'/\[unfolded=([^\]]+?)\](.*?)\[\/unfolded\]\s*/ise',	// [unfolded=...]...[/unfolded]
 941				'/\[unfolded\](.*?)\[\/unfolded\]\s*/ise',	// [unfolded]...[/unfolded]
 942				'/\[sidebar=([^\]]+?)\](.*?)\[\/sidebar\]\s*/ise',	// [sidebar=...]...[/sidebar]
 943				'/\[sidebar\](.*?)\[\/sidebar\]\s*/ise',	// [sidebar]...[/sidebar]
 944				'/\[note\](.*?)\[\/note\]\s*/ise',		// [note]...[/note]
 945				'/\[caution\](.*?)\[\/caution\]\s*/ise', // [caution]...[/caution]
 946				'/\[search=([^\]]+?)\]/ise',				// [search=words]
 947				'/\[search\]/ise',						// [search]
 948				'/\[cloud=(\d+?)\]/ise',				// [cloud=12]
 949				'/\[cloud\]/ise',						// [cloud]
 950				'/\[login=([^\]]+?)\]/is',				// [login=words] --obsoleted
 951				'/\[login\]/is',						// [login] --obsoleted
 952				'/\[center\](.*?)\[\/center\]/ise', 	// [center]...[/center]
 953				'/\[right\](.*?)\[\/right\]/ise',		// [right]...[/right]
 954				'/\[decorated\](.*?)\[\/decorated\]/ise',// [decorated]...[/decorated]
 955				'/\[style=([^\]]+?)\](.*?)\[\/style\]/ise', // [style=variant]...[/style]
 956				'/\[hint=([^\]]+?)\](.*?)\[\/hint\]/is',	// [hint=help]...[/hint]
 957				'/\[tiny\](.*?)\[\/tiny\]/ise', 		// [tiny]...[/tiny]
 958				'/\[small\](.*?)\[\/small\]/ise',		// [small]...[/small]
 959				'/\[big\](.*?)\[\/big\]/ise',			// [big]...[/big]
 960				'/\[huge\](.*?)\[\/huge\]/ise', 		// [huge]...[/huge]
 961				'/\[subscript\](.*?)\[\/subscript\]/is',// [subscript]...[/subscript]
 962				'/\[superscript\](.*?)\[\/superscript\]/is',// [superscript]...[/superscript]
 963				'/\+\+(\S.*?\S)\+\+(?!([^<]+)?>)/is',	// ++...++
 964				'/\[(---+|___+)\]\s*/ise',				// [---], [___] --- before inserted
 965				'/^-----*/me',							// ----
 966				'/\[inserted\](.*?)\[\/inserted\]/is',	// [inserted]...[/inserted]
 967				'/ --(\S.*?\S)--(?!([^<]+)?>)/is',		// --...--
 968				'/\[deleted\](.*?)\[\/deleted\]/is',	// [deleted]...[/deleted]
 969				'/\*\*(\S.*?\S)\*\*/is',				// **...**
 970				'/\[b\](.*?)\[\/b\]/is',				// [b]...[/b]
 971				'/ \/\/(\S.*?\w)\/\/(?!([^<]+)?>)/is',				// //...//
 972				'/\[i\](.*?)\[\/i\]/is',				// [i]...[/i]
 973				'/__(\S.*?\S)__(?!([^<]+)?>)/is',		// __...__
 974				'/\[u\](.*?)\[\/u\]/is',				// [u]...[/u]
 975				'/\[color=([^\]]+?)\](.*?)\[\/color\]/is',	// [color=<color>]...[/color]
 976				'/\[new\]/ie',							// [new]
 977				'/\[popular\]/ie',						// [popular]
 978				'/\[flag=([^\]]+?)\]/ie',				// [flag=<flag>]
 979				'/\[flag\](.*?)\[\/flag\]/ise', 		// [flag]...[/flag]
 980				'/\[list\](.*?)\[\/list\]/ise', 		// [list]...[/list]
 981				'/\[list=([^\]]+?)\](.*?)\[\/list\]/ise',	// [list=1]...[/list]
 982				'/\n\n+[ \t]*\[\*\][ \t]*/ie',			// [*] (outside [list]...[/list])
 983				'/\n?[ \t]*\[\*\][ \t]*/ie',
 984				'/\[li\](.*?)\[\/li\]/is',				// [li]...[/li] (outside [list]...[/list])
 985				'/\[chart=([^\]]+?)\](.*?)\[\/chart\]/ise',	// [chart=<width>, <height>, <params>]...[/chart]
 986				'/\[embed=([^\]]+?)\]/ie',					// [embed=<id>, <width>, <height>, <params>] or [embed=<id>, window]
 987				'/\[flash=([^\]]+?)\]/ie',					// [flash=<id>, <width>, <height>, <params>] or [flash=<id>, window]
 988				'/\[sound=([^\]]+?)\]/ie',					// [sound=<id>]
 989				'/\[go=([^\]]+?)\]/ie', 					// [go=<name>]
 990				'/\[\[([^\]]+?)\]\]/ie', 					// [[<name>]]
 991				'/\[article\.description=([^\]]+?)\]/ie',	// [article.description=<id>]
 992				'/\[article=([^\]]+?)\]/ie',				// [article=<id>] or [article=<id>, title]
 993				'/\[next=([^\]]+?)\]/ie',					// [next=<id>]
 994				'/\[previous=([^\]]+?)\]/ie',				// [previous=<id>]
 995				'/\[random\]/ie',							// [random]
 996				'/\[random\.description=([^\]]+?)\]/ie',	// [random.description=section:<id>]
 997				'/\[random=([^\]]+?)\]/ie',					// [random=section:<id>] or [random=category:<id>]
 998				'/\[form=([^\]]+?)\]/ie',					// [form=<id>] or [form=<id>, title]
 999				'/\[section=([^\]]+?)\]/ie',				// [section=<id>] or [section=<id>, title]
1000				'/\[category\.description=([^\]]+?)\]\n*/ise',	// [category.description=<id>]
1001				'/\[category=([^\]]+?)\]/ie',				// [category=<id>] or [category=<id>, title]
1002				'/\[user=([^\]]+?)\]/ie',					// [user=<id>] or [user=<id>, title]
1003				'/\[server=([^\]]+?)\]/ie', 				// [server=<id>]
1004				'/\[file=([^\]]+?)\]/ie',					// [file=<id>] or [file=<id>, title]
1005				'/\[download=([^\]]+?)\]/ie',				// [download=<id>] or [download=<id>, title]
1006				'/\[comment=([^\]]+?)\]/ie',				// [comment=<id>] or [comment=<id>, title]
1007				'/\[url=([^\]]+?)\](.*?)\[\/url\]/ise', 	// [url=url]label[/url] (deprecated by [link])
1008				'/\[url\](.*?)\[\/url\]/ise',				// [url]url[/url] (deprecated by [link])
1009				'/\[link=([^\]]+?)\](.*?)\[\/link\]/ise',	// [link=label]url[/link]
1010				'/\[link\](.*?)\[\/link\]/ise', 			// [link]url[/link]
1011				'/\[proxy\](.*?)\[\/proxy\]/ise', 			// [proxy]url[/proxy]
1012				'/\[button=([^\]]+?)\](.*?)\[\/button\]/ise',	// [button=label]url[/button]
1013				'/\[button=([^\|]+?)\|([^\]]+?)]/ise',		// [button=label|url]
1014				'/\[click=([^\|]+?)\|([^\]]+?)]/ise',		// [click=label|url]
1015				'/\[clicks=([^\]]+?)]/ise',					// [clicks=url]
1016				'/\[script\](.*?)\[\/script\]/ise', 		// [script]url[/script]
1017				'/\[menu\](.*?)\[\/menu\]\n*/ise',			// [menu]url[/menu]
1018				'/\[menu=([^\]]+?)\](.*?)\[\/menu\]\n{0,1}/ise',	// [menu=label]url[/menu]
1019				'/\[submenu\](.*?)\[\/submenu\]\n{0,1}/ise',	// [submenu]url[/submenu]
1020				'/\[submenu=([^\]]+?)\](.*?)\[\/submenu\]\n*/ise', // [submenu=label]url[/submenu]
1021				'/\[email=([^\]]+?)\](.*?)\[\/email\]/ise', // [email=label]url[/email]
1022				'/\[email\](.*?)\[\/email\]/ise',			// [email]url[/email]
1023				'/\[([^ ][^\]\|]+?[^ ])\|([^ ][^\]]+?[^ ])\]/ise',			// [label|url]
1024				'/\[question\](.*?)\[\/question\]\n*/ise',	// [question]...[/question]
1025				'/\[question\]/ise',						// [question]
1026				'/\[answer\]/ise',							// [answer]
1027				'/\[newsfeed=([^\]]+?)\]/ise',				// [newsfeed=url]
1028				'/\[newsfeed\.([^=\]]+?)=([^\]]+?)\]/ise',	// [newsfeed.variant=url]
1029				'/\[twitter=([^\]]+?)\]/ise',				// [twitter=id]
1030				'/\[tsearch=([^\]]+?)\]/ise',				// [tsearch=id]
1031				'/\[retweet\]/ise',							// [retweet]
1032				'/\[iframe\](.*?)\[\/iframe\]/ise',			// [iframe]<url>[/iframe]
1033				'/\[iframe=([^\]]+?)\](.*?)\[\/iframe\]/ise',	// [iframe=<width>, <height>]<url>[/iframe]
1034				'/\[scroller\](.*?)\[\/scroller\]/ise', 	// [scroller]...[/scroller]
1035				'/\[toq\]\n*/ise',							// [toq] (table of questions)
1036				'/\[title\](.*?)\[\/title\]\n*/is', 		// [title]...[/title]
1037				'/\[subtitle\](.*?)\[\/subtitle\]\n*/is',	// [subtitle]...[/subtitle]
1038				'#\[(header[1-5])\](.*?)\[/\1\]\n*#ise',	// [header1]...[/header1] ... [header5]...[/header5]
1039				'/^======(\S.*?\S)======/me',				// ======...====== level 5 headline
1040				'/<(br \/|p)>======(\S.*?\S)======<(br \/|\/p)>/me',		// ======...====== level 5 headline
1041				'/^=====(\S.*?\S)=====/me',					// =====...===== level 4 headline
1042				'/<(br \/|p)>=====(\S.*?\S)=====<(br \/|\/p)>/me',			// =====...===== level 4 headline
1043				'/^====(\S.*?\S)====/me',					// ====...==== level 3 headline
1044				'/<(br \/|p)>====(\S.*?\S)====<(br \/|\/p)>/me',			// ====...==== level 3 headline
1045				'/^===(\S.*?\S)===/me',						// ===...=== level 2 headline
1046				'/<(br \/|p)>===(\S.*?\S)===<(br \/|\/p)>/me',				// ===...=== level 2 headline
1047				'/^==(\S.*?\S)==/me',						// ==...== level 1 headline
1048				'/<(br \/|p)>==(\S.*?\S)==<(br \/|\/p)>/me',				// ==...== level 1 headline
1049				'/\[toc\]\n*/ise',							// [toc] (table of content)
1050				'/\[published\.([^\]=]+?)=([^\]]+?)\]\n*/ise',	// [published.decorated=section:4029]
1051				'/\[published\.([^\]]+?)\]\n*/ise',			// [published.decorated]
1052				'/\[published=([^\]]+?)\]\n*/ise',			// [published=section:4029]
1053				'/\[published\]\n*/ise',					// [published]
1054				'/\[read\.([^\]=]+?)=([^\]]+?)\]\n*/ise',	// [read.decorated=section:4029]
1055				'/\[read\.([^\]]+?)\]\n*/ise',				// [read.decorated]
1056				'/\[read=([^\]]+?)\]\n*/ise',				// [read=section:4029]
1057				'/\[read\]\n*/ise', 						// [read]
1058				'/\[updated\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [updated.simple=section:4029] (a list of recent updates)
1059				'/\[updated\.([^\]]+?)\]\n*/ise', 			// [updated.simple] (a list of recent updates)
1060				'/\[updated=([^\]]+?)\]\n*/ise', 			// [updated=section:4029] (a compact list of recent updates)
1061				'/\[updated\]\n*/ise',						// [updated] (a compact list of recent updates)
1062				'/\[voted\.([^\]=]+?)=([^\]]+?)\]\n*/ise',	// [voted.decorated=section:4029]
1063				'/\[voted\.([^\]]+?)\]\n*/ise',				// [voted.decorated]
1064				'/\[voted=([^\]]+?)\]\n*/ise',				// [voted=section:4029]
1065				'/\[voted\]\n*/ise', 						// [voted]
1066				'/\[sections\]\n*/ise',						// [sections] (site map)
1067				'/\[sections\.([^\]=]+?)\]\n*/ise',			// [sections.folded] (site map)
1068				'/\[sections=([^\]]+?)\]\n*/ise',			// [sections=section:4029] (sub-sections)
1069				'/\[sections\.([^\]=]+?)=([^\]]+?)\]\n*/ise',	// [sections.simple=self] (assigned)
1070				'/\[categories\]\n*/ise',					// [categories] (category tree)
1071				'/\[categories\.([^\]=]+?)\]\n*/ise',		// [categories.folded] (category tree)
1072				'/\[categories=([^\]]+?)\]\n*/ise',			// [categories=section:4029] (sub-categories)
1073				'/\[categories\.([^\]=]+?)=([^\]]+?)\]\n*/ise',	// [categories.simple=self] (assigned)
1074				'/\[calendar\]\n*/ise',					// [calendar]
1075				'/\[calendar=([^\]]+?)\]\n*/ise',		// [calendar=section:4029]
1076				'/\[users=([^\]]+?)\]/ie',					// [users=present]
1077				'/\[news=([^\]]+?)\]/ise',				// [news=flash]
1078				'/\[table=([^\]]+?)\]/ise', 			// [table=<id>]
1079				'/\[table\.([^=\]]+?)=([^\]]+?)\]/ise', // [table.json=<id>]  [table.timeplot=<id>]
1080				'/\[locations=([^\]]+?)\]/ise', 		// [locations=<id>]
1081				'/\[location=([^\]]+?)\]/ise',			// [location=<id>]
1082				'/\[wikipedia=([^\]]+?)\]/ise', 		// [wikipedia=keyword] or [wikipedia=keyword, title]
1083				'/\[digraph\](.*?)\[\/digraph\]/ise',	// [digraph]url[/digraph]
1084				'/\[be\]/i',							// [be] belgian flag
1085				'/\[ca\]/i',							// [ca] canadian flag
1086				'/\[ch\]/i',							// [ch] swiss flag
1087				'/\[de\]/i',							// [de] german flag
1088				'/\[en\]/i',							// [en] english flag
1089				'/\[es\]/i',							// [es] spanish flag
1090				'/\[fr\]/i',							// [fr] french flag
1091				'/\[gb\]/i',							// [gb] gb flag
1092				'/\[gr\]/i',							// [gr] greek flag
1093				'/\[it\]/i',							// [it] italian flag
1094				'/\[pt\]/i',							// [pt] portuguese flag
1095				'/\[us\]/i',							// [pt] us flag
1096				'/\[clear\]\n*/i',						// [clear]
1097				'/\[nl\]\n*/si',						// [nl] (after tables)
1098				'/\[br\]/i' 							// [br] (deprecated by [nl])
1099			);
1100		}
1101
1102		// initialize only once
1103		static $replace;
1104		if(!isset($replace)) {
1105			$replace = array(
1106				'',																	// delete HTML comments
1107				"Codes::render_escaped(Codes::fix_tags('$1'))",						// [escape]...[/escape]
1108				"Codes::render_pre(Codes::fix_tags('$1'), 'php')",					// [php]...[/php]
1109				"Codes::render_pre(Codes::fix_tags('$1'), 'snippet')",				// [snippet]...[/snippet]
1110				'', 																// [page]
1111				"Codes::render_hidden(Codes::fix_tags('$1'), 'associate')",			// [associate]...[/associate]
1112				"Codes::render_hidden(Codes::fix_tags('$1'), 'associate')",			// [hidden]...[/hidden]
1113				"Codes::render_hidden(Codes::fix_tags('$1'), 'authenticated')",		// [authenticated]...[/authenticated]
1114				"Codes::render_hidden(Codes::fix_tags('$1'), 'authenticated')",		// [restricted]...[/restricted]
1115				"Codes::render_hidden(Codes::fix_tags('$1'), 'anonymous')",			// [anonymous]...[/anonymous]
1116				"Codes::render_redirect('$1')",									// [redirect=<link>]
1117				"Codes::render_execute('$1')",										// [execute=<name>]
1118				"Codes::render_parameter('$1')",									// [parameter=<name>]
1119				"i18n::filter(Codes::fix_tags('$2'), '$1')", 						// [lang=xy]...[/lang]
1120				"utf8::encode(str_replace('$1', '|', utf8::from_unicode(Codes::fix_tags('$2'))))",	// [csv=;]...[/csv]
1121				"str_replace(',', '|', Codes::fix_tags('$1'))",						// [csv]...[/csv]
1122				"Codes::render_static_table(Codes::fix_tags('$2'), '$1')",			// [table=variant]...[/table]
1123				"Codes::render_static_table(Codes::fix_tags('$1'), '')",			// [table]...[/table]
1124				"Codes::render_object('images', '$1')",								// [images=<ids>]
1125				"'<div class=\"external_image\"><img src=\"'.encode_link('$1').'\" alt=\"\" /></div>'",	// [image]src[/image]
1126				"'<div class=\"external_image\"><img src=\"'.encode_link('$2').'\" alt=\"'.encode_link('$1').'\" /></div>'", // [image=alt]src[/image]
1127				"'<div class=\"external_image\"><img src=\"'.encode_link('$1').'\" alt=\"\" /></div>'",	// [img]src[/img]
1128				"'<div class=\"external_image\"><img src=\"'.encode_link('$2').'\" alt=\"'.encode_link('$1').'\" /></div>'", // [img=alt]src[/img]
1129				"Codes::render_object('image', Codes::fix_tags('$1'))",				// [image=<id>]
1130				'<code>$1</code>', 												// ##...##
1131				'<code>$1</code>', 												// [code]...[/code]
1132				"Skin::build_block(Codes::fix_tags('$1'), 'indent')", 				// [indent]...[indent]
1133				"Skin::build_block(Codes::fix_tags('$1'), 'quote')",				// [quote]...[/quote]
1134				"Skin::build_box('$1', Codes::fix_tags('$2'), 'folded')",			// [folded=title]...[/folded]
1135				"Skin::build_box(NULL, Codes::fix_tags('$1'), 'folded')", 			// [folded]...[/folded]
1136				"Skin::build_box('$1', Codes::fix_tags('$2'), 'folded')",			// [folder=title]...[/folder]
1137				"Skin::build_box(NULL, Codes::fix_tags('$1'), 'folded')", 			// [folder]...[/folder]
1138				"Skin::build_box('$1', Codes::fix_tags('$2'), 'unfolded')",			// [unfolded=title]...[/unfolded]
1139				"Skin::build_box(NULL, Codes::fix_tags('$1'), 'unfolded')", 		// [unfolded]...[/unfolded]
1140				"Skin::build_box('$1', Codes::fix_tags('$2'), 'sidebar')",			// [sidebar=title]...[/sidebar]
1141				"Skin::build_box(NULL, Codes::fix_tags('$1'), 'sidebar')",			// [sidebar]...[/sidebar]
1142				"Skin::build_block(Codes::fix_tags('$1'), 'note')",					// [note]...[/note]
1143				"Skin::build_block(Codes::fix_tags('$1'), 'caution')",				// [caution]...[/caution]
1144				"Skin::build_block('$1', 'search')",								// [search=<words>]
1145				"Skin::build_block(NULL, 'search')",								// [search]
1146				"Codes::render_cloud('$1')",										// [cloud=12]
1147				"Codes::render_cloud(20)",											// [cloud]
1148				'', 																// [login=<words>] --obsoleted
1149				'', 																// [login] --obsoleted
1150				"Skin::build_block(Codes::fix_tags('$1'), 'center')", 				// [center]...[/center]
1151				"Skin::build_block(Codes::fix_tags('$1'), 'right')",				// [right]...[/right]
1152				"Skin::build_block(Codes::fix_tags('$1'), 'decorated')",			// [decorated]...[/decorated]
1153				"Skin::build_block(Codes::fix_tags('$2'), '$1')", 					// [style=variant]...[/style]
1154				'<acronym title="$1">$2</acronym>',									// [hint=help]...[/hint]
1155				"Skin::build_block(Codes::fix_tags('$1'), 'tiny')",					// [tiny]...[/tiny]
1156				"Skin::build_block(Codes::fix_tags('$1'), 'small')",				// [small]...[/small]
1157				"Skin::build_block(Codes::fix_tags('$1'), 'big')",					// [big]...[/big]
1158				"Skin::build_block(Codes::fix_tags('$1'), 'huge')",					// [huge]...[/huge]
1159				'<sub>$1</sub>',													// [subscript]...[/subscript]
1160				'<sup>$1</sup>',													// [superscript]...[/superscript]
1161				'<ins>$1</ins>',													// ++...++
1162				"HORIZONTAL_RULER", 												// [---], [___]
1163				"HORIZONTAL_RULER", 												// ----
1164				'<ins>$1</ins>',													// [inserted]...[/inserted]
1165				' <del>$1</del>',													// --...--
1166				'<del>$1</del>',													// [deleted]...[/deleted]
1167				'<b>$1</b>',														// **...**
1168				'<b>$1</b>',														// [b]...[/b]
1169				' <i>$1</i>',														// //...//
1170				'<i>$1</i>',														// [i]...[/i]
1171				'<span style="text-decoration: underline">$1</span>',				// __...__
1172				'<span style="text-decoration: underline">$1</span>',				// [u]...[/u]
1173				'<span style="color: $1">$2</span>',								// [color]...[/color]
1174				"NEW_FLAG", 														// [new]
1175				"POPULAR_FLAG", 													// [popular]
1176				"Skin::build_flag('$1')",											// [flag=....]
1177				"Skin::build_flag('$1')",											// [flag]...[/flag]
1178				"Codes::render_list(Codes::fix_tags('$1'), NULL)",					// [list]...[/list]
1179				"Codes::render_list(Codes::fix_tags('$2'), '$1')",					// [list=?]...[/list]
1180				"BR.BR.BULLET_IMG.'&nbsp;'",										// standalone [*]
1181				"BR.BULLET_IMG.'&nbsp;'",
1182				'<li>$1</li>', 														// [li]...[/li]
1183				"Codes::render_chart(Codes::fix_tags('$2'), '$1')",					// [chart=<width>, <height>, <params>]...[/chart]
1184				"Codes::render_embed(Codes::fix_tags('$1'))",						// [embed=<id>, <width>, <height>, <params>]
1185				"Codes::render_embed(Codes::fix_tags('$1'))",						// [flash=<id>, <width>, <height>, <params>] -- obsoleted by 'embed'
1186				"Codes::render_object('sound', Codes::fix_tags('$1'))",				// [sound=<id>]
1187				"Codes::render_object('go', Codes::fix_tags('$1'))",				// [go=<name>]
1188				"Codes::render_object('go', Codes::fix_tags('$1'))",				// [[<name>]]
1189				"Codes::render_object('article.description', Codes::fix_tags('$1'))",// [article.description=<id>]
1190				"Codes::render_object('article', Codes::fix_tags('$1'))",			// [article=<id>]
1191				"Codes::render_object('next', Codes::fix_tags('$1'))",				// [next=<id>]
1192				"Codes::render_object('previous', Codes::fix_tags('$1'))",			// [previous=<id>]
1193				"Codes::render_random()",											// [random]
1194				"Codes::render_random('$1', 'description')",						// [random.description=section:<id>]
1195				"Codes::render_random('$1')",										// [random=section:<id>]
1196				"Codes::render_object('form', Codes::fix_tags('$1'))",				// [form=<id>]
1197				"Codes::render_object('section', Codes::fix_tags('$1'))",			// [section=<id>]
1198				"Codes::render_object('category.description', '$1')", 				// [category.description=<id>]
1199				"Codes::render_object('category', Codes::fix_tags('$1'))",	 		// [category=<id>]
1200				"Codes::render_object('user', Codes::fix_tags('$1'))",				// [user=<id>]
1201				"Codes::render_object('server', Codes::fix_tags('$1'))",			// [server=<id>]
1202				"Codes::render_object('file', Codes::fix_tags('$1'))",				// [file=<id>] or [file=<id>, title]
1203				"Codes::render_object('download', Codes::fix_tags('$1'))",			// [download=<id>] or [download=<id>, title]
1204				"Codes::render_object('comment', Codes::fix_tags('$1'))",			// [comment=<id>] or [comment=<id>, title]
1205				"Skin::build_link(encode_link('$1'), Codes::fix_tags('$2'))",		// [url=url]label[/link] (deprecated by [link])
1206				"Skin::build_link(encode_link('$1'), NULL)",						// [url]url[/url] (deprecated by [link])
1207				"Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'))",		// [link=label]url[/link]
1208				"Skin::build_link(enco…

Large files files are truncated, but you can click here to view the full file