Source for file format.tikiwiki.php
Documentation is available at format.tikiwiki.php
* @subpackage plugins_format
define( 'PLUGIN_GUID_TIKIWIKI', 'tikiwiki' );
'store_function' => 'tikiwiki_save_data',
'load_function' => 'tikiwiki_parse_data',
'verify_function' => 'tikiwiki_verify_data',
'description' => 'TikiWiki Syntax Format Parser',
'edit_label' => 'Tiki Wiki Syntax',
'help_page' => 'TikiWikiSyntax',
$pParamHash['content_store']['data'] = $pParamHash['edit'];
$ret = $parser->parseData( $pParseHash, $pCommonObject );
// This function handles wiki codes for those special HTML characters
// that textarea won't leave alone.
// cleaning some user input
$pData = preg_replace( "/&(?!([a-z]{1,7};))/", "&", $pData );
// oft-used characters (case insensitive)
include_once( UTIL_PKG_PATH. "PHP_Compat/Compat/Function/str_ireplace.php" );
foreach( $patterns as $pattern => $replace ) {
// add an easy method to clear floats
$pData = preg_replace( "/(\r|\n)?~clear~/i", '<br style="clear:both;" />', $pData );
// HTML numeric character entities
// Match things like [...], but ignore things like [[foo].
if( preg_match_all( "/(?<!\[)\[([^\[\|\]]+)(\||\])/", $pData, $r1 )) {
while (($i < strlen($str)) && (isset ($str{$i})) && ($str{$i}== $car)) {
// Find all matches to {|...|} with no {| inside.
while( preg_match( '/\n?\{\|(.*?)\n\|\}/sm', $pData, $matches )) {
// get all instances where put in info like: background=blue and convert it to background="blue"
$xhtmlfix['pattern'] = "!=([^'\"][^\s]*)!";
$xhtmlfix['replace'] = '="$1"';
while( preg_match('/^![^!]+!!/m', $table_data )) {
/* Replace !! with \n! but ONLY in !-defined header rows. */
$table_data = preg_replace( '/^!([^!]+)!!/m', "!$1\n!", $table_data );
if( substr( $table_data, 0, 1 ) != "\n" ) {
// We have table parameters.
list ( $table_params, $table_data ) = explode( "\n", $table_data, 2 );
$table_params = preg_replace( $xhtmlfix['pattern'], $xhtmlfix['replace'], trim( $table_params ));
/* TODO: This attempt to support foo:bar table params needs help!
if (strlen($table_params)) {
$table_params = preg_replace("/\b(\w+):/", '$1=', $table_params);
// apply default class if no other class has been set
if( !empty( $table_params ) && strpos( 'class=', $table_params ) !== FALSE ) {
$table_params .= ' class="table"';
$content = "<table $table_params>";
foreach( $lines as $line ) {
if(( substr( $line, 0, 1 ) == '|' ) || ( substr( $line, 0, 1 ) == '!' )) {
if( preg_match( '/^\|\+\s*(.+)$/', $line, $row_matches )) {
$content .= "<caption>$row_matches[1]</caption>";
} elseif( preg_match( '/^\|-\s*(.+)?$/', $line, $row_matches )) {
if( !empty( $row_matches[1] )) {
$row_matches[1] = preg_replace( $xhtmlfix['pattern'], $xhtmlfix['replace'], trim( $row_matches[1] ));
$content .= "<tr {$row_matches[1]}>";
} elseif( preg_match( '/^([\|!])\s*([^\|]+\s*\|)?\s*(.*)$/', $line, $row_matches )) {
if( !empty( $row_matches[2] )) {
$row_matches[2] = preg_replace( $xhtmlfix['pattern'], $xhtmlfix['replace'], trim( $row_matches[2] ));
$td = 't'. (( $row_matches[1] == '!' ) ? 'h' : 'd' );
$content .= "<$td". (( !empty( $row_matches[2] )) ? ' '. trim( substr( $row_matches[2], 0, - 1 )) : '' ). '>'. $row_matches[3]. "</$td>";
$content .= "<!-- ERROR: Ignoring invalid line \"$line\" -->";
$content .= "<!-- ERROR: Ignoring invalid line \"$line\" -->";
$content .= '</tr></table>';
function parseData( $pParseHash, &$pCommonObject ) {
global $gBitSystem, $gLibertySystem, $gBitUser, $page;
$data = $pParseHash['data'];
$contentId = $pParseHash['content_id'];
// this is used for setting the links when section editing is enabled
if( $gBitSystem->isPackageActive( 'wiki' ) ) {
require_once( WIKI_PKG_PATH. 'BitPage.php' );
// if the object isn't loaded, we'll try and get the content prefs manually
if( !empty( $pCommonObject->mPrefs ) ) {
$contentPrefs = $pCommonObject->mPrefs;
} elseif( empty( $pCommonObject->mContentId ) && !empty( $contentId ) ) {
$contentPrefs = $pCommonObject->loadPreferences( $contentId );
// only strip out html if needed
if( $gBitSystem->isFeatureActive( 'content_allow_html' ) || $gBitSystem->isFeatureActive( 'content_force_allow_html' )) {
// we allow html unconditionally with this parser
} elseif( !empty( $contentPrefs['content_enter_html'] )) {
// we allow html on a per page basis
// we are parsing this page and we either have no way of checking permissions or we have no need for html
// Extract [link] sections (to be re-inserted later)
$noparsedlinks = array();
// This section matches [...].
// Added handling for [[foo] sections. -rlpowell
// Replace special characters
//done after url catching because otherwise urls of dyn. sites will be modified
//$data = strip_tags($data);
$data = preg_replace("/\{l2r\}/", "<div dir='ltr'>", $data);
$data = preg_replace("/\{r2l\}/", "<div dir='rtl'>", $data);
// Parse MediaWiki-style pipe syntax tables.
if(( strpos( $data, "{|" ) === 0 || strpos( $data, "\n{|" ) !== FALSE ) && strpos( $data, "\n|}" ) !== FALSE ) {
// ============================================= this should go - xing
// Replace dynamic variables
// Dynamic variables are similar to dynamic content but they are editable
// from the page directly, intended for short data, not long text but text
// Now won't match HTML-style '%nn' letter codes.
if (preg_match_all("/%([^% 0-9][^% 0-9][^% ]*)%/",$data,$dvars)) {
// remove repeated elements
$dvars = array_unique($dvars[1]);
// Now replace each dynamic variable by a pair composed of the
// variable value and a text field to edit the variable. Each
foreach($dvars as $dvar) {
$query = "select `data` from `".BIT_DB_PREFIX."liberty_dynamic_variables` where `name`=?";
$result = $this->mDb->query($query,Array($dvar));
$value = $result->fetchRow();
if( $gBitUser->hasPermission( 'p_wiki_edit_dynvar' ) ) {
$span1 = "<span style='display:inline;' id='dyn_".$dvar."_display'><a class='dynavar' onclick='javascript:toggle_dynamic_var(\"$dvar\");' title='".tra('Click to edit dynamic variable').": $dvar'>$value</a></span>";
$span2 = "<span style='display:none;' id='dyn_".$dvar."_edit'><input type='text' name='dyn_".$dvar."' value='".$value."' /></span>";
$span1 = "<span class='dynavar' style='display:inline;' id='dyn_".$dvar."_display'>$value</span>";
//It's important to replace only once
$dvar_preg = preg_quote( $dvar );
$data = preg_replace("+%$dvar_preg%+",$html,$data,1);
//Further replacements only with the value
$data = str_replace("%$dvar%",$value,$data);
//At the end put an update button
//<br /><div style="text-align:center"><input type="submit" name="dyn_update" value="'.tra('Update variables').'"/></div>
$data='<form method="post" name="dyn_vars">'.$data.'<div style="display:none;"><input type="submit" name="_dyn_update" value="'.tra('Update variables').'"/></div></form>';
// Replace boxes - add a new line that we can have something like: ^!heading^ without the need for a \n after the initial ^ - \n will be removed below
$data = preg_replace("/\^([^\^]+)\^/", "<div class=\"alert alert-info bitbox\"><!-- bitremovebr -->\n$1</div>", $data);
// Replace colors ~~color:text~~
$data = preg_replace("/\~\~([^\:]+):([^\~]+)\~\~/", "<span style=\"color:$1;\">$2</span>", $data);
// Replace background colors ++color:text++
$data = preg_replace("/\+\+([^\s][^\: ]+):([^\+]+)\+\+/", "<span style=\"background:$1;\">$2</span>", $data);
$data = preg_replace("/===([^\=]+)===/", "<span style=\"text-decoration:underline;\">$1</span>", $data);
$data = preg_replace("/::(.+?)::/", "<div style=\"text-align:center;\">$1</div>", $data);
// reinsert hash-replaced links into page
foreach ($noparsedlinks as $np) {
// Note that there're links that are replaced
foreach( $links as $link ) {
if(( strstr( $link, $_SERVER["SERVER_NAME"] )) || ( !strstr( $link, '//' ))) {
$attributes = 'class="external"';
// comments and anonymously created pages get nofollow
if( $pCommonObject && ( get_class( $pCommonObject ) == 'comments' || ( isset ( $pCommonObject->mInfo['user_id'] ) && $pCommonObject->mInfo['user_id'] == ANONYMOUS_USER_ID ))) {
$attributes .= ' rel="nofollow" ';
// The (?<!\[) stuff below is to give users an easy way to
// enter square brackets in their output; things like [[foo]
// get rendered as [foo]. -rlpowell
// prepare link for pattern usage
$pattern = "/(?<!\[)\[$link2\|([^\]\|]+)([^\]])*\]/";
$data = preg_replace( $pattern, "<a $attributes href='$link'>$1</a>", $data );
$pattern = "/(?<!\[)\[$link2\]/";
$data = preg_replace( $pattern, "<a $attributes href='$link'>$link</a>", $data );
// Handle double square brackets. -rlpowell
// now that all links have been taken care of, we can replace all email addresses with the encoded form
// this will also encode email addressed that have not been linked using []
if ($gBitSystem->getConfig('wiki_tables') != 'new') {
for ($i = 0; $i < count($tables[0]); $i++ ) {
$rows = explode('||', $tables[0][$i]);
for ($j = 0; $j < count($rows); $j++ ) {
$cols[$i][$j] = explode('|', $rows[$j]);
if (count($cols[$i][$j]) > $maxcols)
$maxcols = count($cols[$i][$j]);
for ($i = 0; $i < count($tables[0]); $i++ ) {
$repl = '<table class="table">';
for ($j = 0; $j < count($cols[$i]); $j++ ) {
$ncols = count($cols[$i][$j]);
if ($ncols == 1 && !$cols[$i][$j][0])
$repl .= '<tr class="'. ( ( $j % 2 ) ? 'even' : 'odd' ). '">';
for ($k = 0; $k < $ncols; $k++ ) {
if ($k == $ncols - 1 && $ncols < $maxcols)
$repl .= ' colspan="' . ($maxcols - $k). '"';
$repl .= '>' . $cols[$i][$j][$k] . '</td>';
for( $i = 0; $i < count( $tables[0] ); $i++ ) {
$rows = preg_split( "/(\n|\<br\/\>)/", $tables[0][$i] );
for( $j = 0; $j < count( $rows ); $j++ ) {
$cols[$i][$j] = explode( '|', $rows[$j] );
if( count( $cols[$i][$j] ) > $maxcols ) {
$maxcols = count( $cols[$i][$j] );
for( $i = 0; $i < count( $tables[0] ); $i++ ) {
$repl = '<table class="table table-striped">';
if( preg_match( "#^~#", $cols[$i][0][0] ) && $cols[$i][0][0] = preg_replace( "#^~#", "", $cols[$i][0][0] ) ) {
for( $j = 0; $j < count( $cols[$i] ); $j++ ) {
$ncols = count( $cols[$i][$j] );
if( $ncols == 1 && !$cols[$i][$j][0] ) {
$repl .= '<tr class="'. ( ( $j % 2 ) ? 'odd' : 'even' ). '">';
for( $k = 0; $k < $ncols; $k++ ) {
$thd = ( ( $j == 0 && $th ) ? 'th' : 'td' );
if( $k == $ncols - 1 && $ncols < $maxcols ) {
$repl .= ' colspan="'. ( $maxcols - $k ). '"';
$repl .= ">". ( str_replace( "\\n", "<br />", $cols[$i][$j][$k] ) ). "</$thd>";
// Now tokenize the expression and process the tokens
// Use tab and newline as tokenizing characters as well ////
// loop: process all lines
foreach ($lines as $line) {
// bitweaver now ignores leading space because it is *VERY* disturbing to unaware users - spiderr
// unless 'feature_wiki_preserve_leading_blanks is set'. This is used for sites that have
// migrated from TikiWiki and have lots of pages whose formatting depends on the presevation of leading spaces
if (!$gBitSystem->isFeatureActive('wiki_preserve_leading_blanks')) {
// check if we are inside a table, if so, ignore monospaced and do
// If the first character is ' ' and we are not in pre then we are in pre
// bitweaver now ignores leading space because it is *VERY* disturbing to unaware users - spiderr
if (substr($line, 0, 1) == ' ' && $gBitSystem->isFeatureActive('wiki_monosp') && $inTable == 0) {
// This is not list item -- must close lists currently opened
// If the first character is space then
// change spaces for
$line = '<span style="font-family:monospace;">' . str_replace(' ', ' ', substr($line, 1)). '</span>';
$line = preg_replace( "/\-\=([^=]+)\=\-/", "<div class='bitbar'>$1</div><!-- bitremovebr -->", $line );
$line = preg_replace( "/-\+(.*?)\+-/", "<code>$1</code>", $line );
$line = preg_replace( "/__(.*?)__/", "<strong>$1</strong>", $line );
$line = preg_replace( "/''(.*?)''/", "<em>$1</em>", $line );
$line = preg_replace( "/^;([^:]+):(.+)/", "<dl><dt>$1</dt><dd>$2</dd></dl><!-- bitremovebr -->", $line );
// This line is parseable then we have to see what we have
if (substr($line, 0, 3) == '---') {
// This is not list item -- must close lists currently opened
$litype = substr($line, 0, 1);
if ($litype == '*' || $litype == '#') {
if ($listlevel < count($listbeg)) {
while ($listlevel != count($listbeg))
} elseif ($listlevel > count($listbeg)) {
while ($listlevel != count($listbeg)) {
if ($listlevel == count($listbeg)) {
$listate = substr($line, $listlevel, 1);
if (($listate == '+' || $listate == '-') && !($litype == '*' && !strstr(current($listbeg), '</ul>') || $litype == '#' && !strstr(current($listbeg), '</ol>'))) {
$data .= '<br /><a id="flipper' . $thisid . '" href="javascript:flipWithSign(\'' . $thisid . '\',1)">[' . ($listate == '-' ? '+' : '-') . ']</a>';
$listyle = ' id="' . $thisid . '" style="display:' . ($listate == '+' ? 'block' : 'none') . ';"';
$data .= ($litype == '*' ? "<ul$listyle>" : "<ol$listyle>");
$listate = substr($line, $listlevel, 1);
if (($listate == '+' || $listate == '-')) {
$data .= '<br /><a id="flipper' . $thisid . '" href="javascript:flipWithSign(\'' . $thisid . '\',1)">[' . ($listate == '-' ? '+' : '-') . ']</a>';
$listyle = ' id="' . $thisid . '" style="display:' . ($listate == '+' ? 'block' : 'none') . ';"';
$data .= ($litype == '*' ? "<ul$listyle>" : "<ol$listyle>");
array_unshift($listbeg, ($litype == '*' ? '</li></ul>' : '</li></ol>'));
$line = $liclose . '<li>' . substr($line, $listlevel + $addremove);
} elseif ($litype == '+') {
// Must append paragraph for list item of given depth...
// Close lists down to requested level
while ($listlevel < count($listbeg))
// This is not list item -- must close lists currently opened
// Get count of (possible) header signs at start
// If 1st char on line is '!' and its count less than 6 (max in HTML)
if ($litype == '!' && $hdrlevel > 0 && $hdrlevel <= 6) {
// OK. Parse headers here...
// Close lower level divs if opened
// May be spesial signs present after '!'s?
$divstate = substr($line, $hdrlevel, 1);
if ($divstate == '+' || $divstate == '-') {
// OK. Must insert flipper after HEADER, and then open new div...
$aclose = '<a id="flipper' . $thisid . '" href="javascript:flipWithSign(\'' . $thisid . '\',1)">[' . ($divstate == '-' ? '+' : '-') . ']</a>';
$aclose .= '<div id="' . $thisid . '" style="display:' . ($divstate == '+' ? 'block' : 'none') . ';">';
if( $gBitSystem->isFeatureActive( 'wiki_section_edit' ) && $gBitUser->hasPermission( 'p_wiki_update_page' ) ) {
if( $hdrlevel == $gBitSystem->getConfig( 'wiki_section_edit' ) ) {
$edit_url = WIKI_PKG_URL. "edit.php?content_id=". $contentId. "&section=". $section_count++ ;
$edit_link = '<span class="editsection" style="float:right;margin-left:5px;">[<a href="'. $edit_url. '">'. tra( "edit" ). '</a>]</span>';
$hTagLevel = $hdrlevel + 1; // there should only be 1 <h1> per html document
. substr($line, $hdrlevel + $addremove)
} elseif (!strcmp($line, "...page...")) {
// Close lists and divs currently opened
while (count($listbeg)) {
while (count($divdepth)) {
// Leave line unchanged... index.php will split wiki here
// Close lists may remains opened
while (count($listbeg)) {
// Close header divs may remains opened
for ($i = 1; $i <= count($divdepth); $i++ ) {
// Close BiDi DIVs if any
for ($i = 0; $i < $bidiCount; $i++ ) {
$data = str_replace( "<!-- bitremovebr --><br />", "", $data );
|