Made some more changes to the way namespaces are handled, for optimization purposes. This is a bit of a structural reorganization: $paths->pages is obsoleted in its entirety; calculating page existence and metadata is now the job of the Namespace_* backend class. There are many things in PageProcessor that should be reorganized, and page actions in general should really be rethought. This is probably the beginning of a long process that will be taking place over the course of the betas.
<?php/* * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between * Version 1.1.6 (Caoineag beta 1) * Copyright (C) 2006-2008 Dan Fuhry * render.php - handles fetching pages and parsing them into HTML * * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. */class RenderMan { public static function strToPageID($string) { global $db, $session, $paths, $template, $plugins; // Common objects $k = array_keys($paths->nslist); $proj_alt = 'Project:'; if ( substr($string, 0, (strlen($proj_alt))) == $proj_alt ) { $ns = 'Project'; $pg = substr($string, strlen($proj_alt), strlen($string)); return Array($pg, $ns); } for($i=0;$i<sizeof($paths->nslist);$i++) { $ln = strlen($paths->nslist[$k[$i]]); if(substr($string, 0, $ln) == $paths->nslist[$k[$i]]) { $ns = $k[$i]; $pg = substr($string, strlen($paths->nslist[$ns]), strlen($string)); } } return Array($pg, $ns); } public static function getPage($page_id, $namespace, $wiki = 1, $smilies = true, $filter_links = true, $redir = true, $render = true) { global $db, $session, $paths, $template, $plugins; // Common objects $page = new PageProcessor($page_id, $namespace); $text = $page->fetch_text(); if ( !$render ) return $text; $text = self::render($text, $wiki, $smilies, $filter_links); return $text; } public static function getTemplate($id, $parms) { global $db, $session, $paths, $template, $plugins; // Common objects if ( !isPage($paths->get_pathskey($id, 'Template')) ) { return '[['.$paths->nslist['Template'].$id.']]'; } if(isset($paths->template_cache[$id])) { $text = $paths->template_cache[$id]; } else { $page = new PageProcessor($id, 'Template'); $text = $page->fetch_text(); $paths->template_cache[$id] = $text; } $text = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '', $text); $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '\\1', $text); preg_match_all('#\(_([0-9]+)_\)#', $text, $matchlist); foreach($matchlist[1] as $m) { if(isset($parms[((int)$m)+1])) { $p = $parms[((int)$m)+1]; } else { $p = '<b>Notice:</b> RenderMan::getTemplate(): Parameter '.$m.' is not set'; } $text = str_replace('(_'.$m.'_)', $p, $text); $text = str_replace('{{' . ( $m + 1 ) . '}}', $p, $text); } $text = RenderMan::include_templates($text); return $text; } public static function fetch_template_text($id) { global $db, $session, $paths, $template, $plugins; // Common objects $fetch_ns = 'Template'; if ( !isPage($paths->get_pathskey($id, 'Template')) ) { // Transclusion of another page // 1.1.5: Now You, Too, Can Be A Template, Even If You're Just A Plain Old Article! (TM) $nssep = substr($paths->nslist['Special'], -1); $nslist = $paths->nslist; foreach ( $nslist as &$ns ) { if ( $ns == '' ) $ns = $nssep; } $prefixlist = array_flip($nslist); foreach ( $nslist as &$ns ) { $ns = preg_quote($ns); } $nslist = implode('|', $nslist); if ( preg_match("/^($nslist)(.*?)$/", $id, $match) ) { // in practice this should always be true but just to be safe... if ( isset($prefixlist[$match[1]]) ) { $new_id = $paths->nslist[ $prefixlist[$match[1]] ] . sanitize_page_id($match[2]); if ( !isPage($new_id) ) { return "[[$new_id]]"; } $fetch_ns = $prefixlist[$match[1]]; $id = sanitize_page_id($match[2]); } } else { return '[['.$paths->nslist['Template'].$id.']]'; } } if(isset($paths->template_cache[$id])) { $text = $paths->template_cache[$id]; } else { $text = RenderMan::getPage($id, $fetch_ns, 0, false, false, false, false); $paths->template_cache[$id] = $text; } if ( is_string($text) ) { $text = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '', $text); $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '\\1', $text); } return $text; } /** * Renders a glob of text. Note that this is PHP-safe, so if returned text (or rather, "?>" . $returned) has PHP it can be eval'ed. * @param string Text to render * @param int Render parameters - see constants.php * @return string Rendered text */ public static function render($text, $flags = RENDER_WIKI_DEFAULT, $smilies = true) { global $db, $session, $paths, $template, $plugins; // Common objects if ( !$smilies ) $flags |= RENDER_NOSMILIES; if ( $flags & ~RENDER_NOSMILIES ) { $text = RenderMan::smilieyize($text); } if ( $flags & RENDER_WIKI_DEFAULT ) { $text = RenderMan::next_gen_wiki_format($text, $flags); } else if ( $flags & RENDER_WIKI_TEMPLATE ) { $text = $template->tplWikiFormat($text); } return $text; } private static function next_gen_wiki_format($text, $flags = 0) { global $db, $session, $paths, $template, $plugins; // Common objects global $lang; require_once(ENANO_ROOT.'/includes/wikiformat.php'); require_once(ENANO_ROOT.'/includes/wikiengine/Tables.php'); profiler_log("RenderMan: starting wikitext render"); $random_id = md5( time() . mt_rand() ); // Strip out <nowiki> sections and PHP code $nw = preg_match_all('#<nowiki>(.*?)<\/nowiki>#is', $text, $nowiki); for($i=0;$i<sizeof($nowiki[1]);$i++) { $text = str_replace('<nowiki>'.$nowiki[1][$i].'</nowiki>', '{NOWIKI:'.$random_id.':'.$i.'}', $text); } $code = $plugins->setHook('render_wikiformat_veryearly'); foreach ( $code as $cmd ) { eval($cmd); } $php = preg_match_all('#<\?php(.*?)\?>#is', $text, $phpsec); for($i=0;$i<sizeof($phpsec[1]);$i++) { $text = str_replace('<?php'.$phpsec[1][$i].'?>', '{PHP:'.$random_id.':'.$i.'}', $text); } $text = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '\\1', $text); if ( $paths->namespace == 'Template' ) { $text = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '', $text); } if ( !($flags & RENDER_BLOCKONLY) ) { preg_match_all('/<lang (?:code|id)="([a-z0-9_-]+)">([\w\W]+?)<\/lang>/', $text, $langmatch); foreach ( $langmatch[0] as $i => $match ) { if ( $langmatch[1][$i] == $lang->lang_code ) { $text = str_replace_once($match, $langmatch[2][$i], $text); } else { $text = str_replace_once($match, '', $text); } } $code = $plugins->setHook('render_wikiformat_pre'); foreach ( $code as $cmd ) { eval($cmd); } //$template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is"; $template_regex = "/\{\{(.+)((\n|\|[ ]*([A-z0-9]+)[ ]*=[ ]*(.+))*)\}\}/isU"; $i = 0; while ( preg_match($template_regex, $text) ) { $i++; if ( $i == 5 ) break; $text = RenderMan::include_templates($text); } $code = $plugins->setHook('render_wikiformat_posttemplates'); foreach ( $code as $cmd ) { eval($cmd); } // Process images $text = RenderMan::process_image_tags($text, $taglist); $text = RenderMan::process_imgtags_stage2($text, $taglist); } // Before shipping it out to the renderer, replace spaces in between headings and paragraphs: $text = preg_replace('/<\/(h[0-9]|div|p)>([\s]+)<(h[0-9]|div|p)( .+?)?>/i', '</\\1><\\3\\4>', $text); $text = process_tables($text); if ( !($flags & RENDER_BLOCKONLY) ) $text = RenderMan::parse_internal_links($text); $wiki = Text_Wiki::singleton('Mediawiki'); $wiki->setRenderConf('Xhtml', 'wikilink', 'view_url', contentPath); $wiki->setRenderConf('Xhtml', 'Url', 'css_descr', 'external'); if ( $flags & RENDER_BLOCKONLY ) { $wiki->disableRule('Freelink'); $wiki->disableRule('Url'); $wiki->disableRule('Toc'); $wiki->disableRule('Image'); } else if ( $flags & RENDER_INLINEONLY ) { foreach ( array('code', 'html', 'raw', 'include', 'embed', 'horiz', 'break', 'blockquote', 'list', 'newline', 'paragraph', 'revise', 'tighten') as $rule ) { $wiki->disableRule($rule); } } $result = $wiki->transform($text, 'Xhtml'); // HTML fixes $result = preg_replace('#<tr>([\s]*?)<\/tr>#is', '', $result); $result = preg_replace('#<p>([\s]*?)<\/p>#is', '', $result); $result = preg_replace('#<br />([\s]*?)<table#is', '<table', $result); $result = str_replace("<pre><code>\n", "<pre><code>", $result); $result = preg_replace("/<p><table([^>]*?)><\/p>/", "<table\\1>", $result); $result = str_replace("<br />\n</td>", "\n</td>", $result); $result = str_replace("<p><tr>", "<tr>", $result); $result = str_replace("<tr><br />", "<tr>", $result); $result = str_replace("</tr><br />", "</tr>", $result); $result = str_replace("</table><br />", "</table>", $result); $result = preg_replace('/<\/table>$/', "</table><br /><br />", $result); $result = str_replace("<p></div></p>", "</div>", $result); $result = str_replace("<p></table></p>", "</table>", $result); if ( !($flags & RENDER_BLOCKONLY) ) { $code = $plugins->setHook('render_wikiformat_post'); foreach ( $code as $cmd ) { eval($cmd); } } // Reinsert <nowiki> sections for($i=0;$i<$nw;$i++) { $result = str_replace('{NOWIKI:'.$random_id.':'.$i.'}', $nowiki[1][$i], $result); } // Reinsert PHP for($i=0;$i<$php;$i++) { $result = str_replace('{PHP:'.$random_id.':'.$i.'}', '<?php'.$phpsec[1][$i].'?>', $result); } profiler_log("RenderMan: finished wikitext render"); return $result; } public static function wikiFormat($message, $filter_links = true, $do_params = false, $plaintext = false) { global $db, $session, $paths, $template, $plugins; // Common objects return RenderMan::next_gen_wiki_format($message, $plaintext, $filter_links, $do_params); } public static function destroy_javascript($message, $_php = false) { $message = preg_replace('#<(script|object|applet|embed|iframe|frame|form|input|select)(.*?)>#is', '<\\1\\2>', $message); $message = preg_replace('#</(script|object|applet|embed|iframe|frame|form|input|select)(.*?)>#is', '</\\1\\2>', $message); $message = preg_replace('#(javascript|script|activex|chrome|about|applet):#is', '\\1:', $message); if ( $_php ) { // Left in only for compatibility $message = preg_replace('#<(.*?)>#is', '<\\1>', $message); $message = preg_replace('#<(.*?)>#is', '<\\1>', $message); $message = preg_replace('#<(\?|\?php|%)(.*?)(\?|%)>#is', '<\\1\\2\\3>', $message); // strip <a href="foo" onclick="bar();">-type attacks $message = preg_replace('#<([a-zA-Z:\-]+) (.*?)on([A-Za-z]*)=(.*?)>#is', '<\\1\\2on\\3=\\4>', $message); } return $message; } public static function strip_php($message) { return RenderMan::destroy_javascript($message, true); } public static function sanitize_html($text) { $text = htmlspecialchars($text); $allowed_tags = Array('b', 'i', 'u', 'pre', 'code', 'tt', 'br', 'p', 'nowiki', '!--([\w\W]+)--'); foreach($allowed_tags as $t) { $text = preg_replace('#<'.$t.'>(.*?)</'.$t.'>#is', '<'.$t.'>\\1</'.$t.'>', $text); $text = preg_replace('#<'.$t.' />#is', '<'.$t.' />', $text); $text = preg_replace('#<'.$t.'>#is', '<'.$t.'>', $text); } return $text; } /** * Reverse-renders a blob of text (converts it from XHTML back to wikitext) by using parser hints and educated guesses. * @param string XHTML * @return string Wikitext */ public static function reverse_render($text) { // convert \r\n to \n $text = str_replace("\r\n", "\n", $text); // Separate certain block level elements onto their own lines. This tidies up the tag // soup that TinyMCE sometimes produces. $block_elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'table', 'ul', 'pre'); $block_elements = implode('|', $block_elements); $regex = "#(</(?:$block_elements)>)\n?<($block_elements)(>| .+?>)#i"; $text = preg_replace($regex, "$1\n\n<$2$3", $text); $text = self::reverse_process_parser_hints($text); $text = self::reverse_process_headings($text); $text = self::reverse_process_lists($text); $text = self::reverse_process_tables($text); // Lastly, strip out paragraph tags. $text = preg_replace('|^ *<p>(.+?)</p> *$|m', "\\1", $text); return $text; } public static function reverse_process_parser_hints($text) { global $db, $session, $paths, $template, $plugins; // Common objects if ( !preg_match_all('|<!--#([a-z0-9_]+)(?: (.+?))?-->([\w\W]*?)<!--#/\\1-->|s', $text, $matches) ) return $text; foreach ( $matches[0] as $i => $match ) { $tag =& $matches[1][$i]; $attribs =& $matches[2][$i]; $inner =& $matches[3][$i]; $attribs = self::reverse_process_hint_attribs($attribs); switch($tag) { case 'smiley': case 'internallink': case 'imagelink': if ( isset($attribs['code']) ) { $text = str_replace($match, $attribs['code'], $text); } else if ( isset($attribs['src']) ) { $text = str_replace($match, $attribs['src'], $text); } break; } } return $text; } public static function reverse_process_hint_attribs($attribs) { $return = array(); if ( !preg_match_all('/([a-z0-9_-]+)="([^"]+?)"/', $attribs, $matches) ) return array(); foreach ( $matches[0] as $i => $match ) { $name =& $matches[1][$i]; $value =& $matches[2][$i]; $value = base64_decode($value); $return[$name] = $value; } return $return; } /** * Escapes a string so that it's safe to use as an attribute in a parser hint. * @param string * @return string */ public static function escape_parser_hint_attrib($text) { return base64_encode($text); } public static function reverse_process_headings($text) { if ( !preg_match_all('|^<h([1-6])(?: id="toc[0-9]+")?>(.*?)</h\\1>$|m', $text, $matches) ) return $text; foreach ( $matches[0] as $i => $match ) { // generate heading tag $heading_size = intval($matches[1][$i]); $eq = ''; for ( $j = 0; $j < $heading_size; $j++ ) $eq .= '='; $heading =& $matches[2][$i]; $tag = "$eq $heading $eq"; $text = str_replace($match, $tag, $text); } return $text; } public static function reverse_process_lists($text) { if ( !preg_match('!(</?(?:ul|ol|li)>)!', $text) ) return $text; $split = preg_split('!(</?(?:ul|ol|li)>)!', $text, -1, PREG_SPLIT_DELIM_CAPTURE); $stack_height = 0; $current_list = ''; $old_current_list = ''; $spaces = ''; $marker = '*'; $list_id = 0; $just_terminated = false; foreach ( $split as $tag ) { switch($tag) { case '<ul>': case '<ol>': $stack_height++; $just_terminated = false; if ( $stack_height > 1 ) $spaces .= $marker; $marker = ( $tag == 'ol' ) ? '#' : '*'; if ( $stack_height > 1 ) $current_list .= "\n"; break; case '</ul>': case '</ol>': $stack_height--; $spaces = substr($spaces, 1); if ( $stack_height == 0 ) { // rotate $text = str_replace_once("{$old_current_list}{$tag}", trim($current_list), $text); $current_list = ''; $old_current_list = ''; } $just_terminated = true; break; case '<li>': if ( $stack_height < 1 ) break; $current_list .= "{$spaces}{$marker} "; break; case '</li>': if ( $stack_height < 1 ) break; if ( !$just_terminated ) $current_list .= "\n"; $just_terminated = false; break; default: if ( $stack_height > 0 ) { $current_list .= trim($tag); } break; } if ( $stack_height > 0 ) { $old_current_list .= $tag; } } return $text; } public static function reverse_process_tables($text) { return $text; } /** * Parses internal links (wikilinks) in a block of text. * @param string Text to process * @param string Optional. If included will be used as a template instead of using the default syntax. * @return string */ public static function parse_internal_links($text, $tplcode = false) { global $db, $session, $paths, $template, $plugins; // Common objects if ( is_string($tplcode) ) { $parser = $template->makeParserText($tplcode); } // stage 1 - links with alternate text preg_match_all('/\[\[([^\[\]<>\{\}\|]+)\|(.+?)\]\]/', $text, $matches); foreach ( $matches[0] as $i => $match ) { list($page_id, $namespace) = RenderMan::strToPageID($matches[1][$i]); if ( ($pos = strrpos($page_id, '#')) !== false ) { $hash = substr($page_id, $pos); $page_id = substr($page_id, 0, $pos); } else { $hash = ''; } $pid_clean = $paths->nslist[$namespace] . sanitize_page_id($page_id); $url = makeUrl($pid_clean, false, true) . $hash; $inner_text = $matches[2][$i]; $quot = '"'; $exists = ( isPage($pid_clean) ) ? '' : ' class="wikilink-nonexistent"'; if ( $tplcode ) { $parser->assign_vars(array( 'HREF' => $url, 'FLAGS' => $exists, 'TEXT' => $inner_text )); $link = $parser->run(); } else { $omatch = self::escape_parser_hint_attrib($match); $link = "<!--#internallink src=\"$omatch\" --><a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a><!--#/internallink-->"; } $text = str_replace($match, $link, $text); } // stage 2 - links with no alternate text preg_match_all('/\[\[([^\[\]<>\{\}\|]+)\]\]/', $text, $matches); foreach ( $matches[0] as $i => $match ) { list($page_id, $namespace) = RenderMan::strToPageID($matches[1][$i]); $pid_clean = $paths->nslist[$namespace] . sanitize_page_id($page_id); $url = makeUrl($pid_clean, false, true); $inner_text = ( isPage($pid_clean) ) ? htmlspecialchars(get_page_title($pid_clean)) : htmlspecialchars($matches[1][$i]); $quot = '"'; $exists = ( isPage($pid_clean) ) ? '' : ' class="wikilink-nonexistent"'; if ( $tplcode ) { $parser->assign_vars(array( 'HREF' => $url, 'FLAGS' => $exists, 'TEXT' => $inner_text )); $link = $parser->run(); } else { $omatch = self::escape_parser_hint_attrib($match); $link = "<!--#internallink src=\"$omatch\" --><a href={$quot}{$url}{$quot}{$exists}>{$inner_text}</a><!--#/internallink-->"; } $text = str_replace($match, $link, $text); } return $text; } /** * Parses a partial template tag in wikitext, and return an array with the parameters. * @param string The portion of the template tag that contains the parameters. * @example * <code> foo = lorem ipsum bar = dolor sit amet * </code> * @return array Example: * [foo] => lorem ipsum * [bar] => dolor sit amet */ public static function parse_template_vars($input, $newlinemode = true) { $parms = array(); $input = trim($input); if ( $newlinemode ) { $result = preg_match_all('/ (?:^|[\s]*)\|? # start of parameter - string start or series of spaces [ ]* (?: ([A-z0-9_]+) # variable name [ ]* = [ ]* # assignment )? # this is optional - if the parameter name is not given, a numerical index is assigned (.+) # value /x', trim($input), $matches); } else { $result = preg_match_all('/ (?:^|[ ]*)\| # start of parameter - string start or series of spaces [ ]* (?: ([A-z0-9_]+) # variable name [ ]* = [ ]* # assignment )? # name section is optional - if the parameter name is not given, a numerical index is assigned ([^\|]+|.+?\n[ ]*\|) # value /x', trim($input), $matches); } if ( $result ) { $pi = 0; for ( $i = 0; $i < count($matches[0]); $i++ ) { $matches[1][$i] = trim($matches[1][$i]); $parmname = !empty($matches[1][$i]) ? $matches[1][$i] : strval(++$pi); $parms[ $parmname ] = $matches[2][$i]; } } return $parms; } /** * Processes all template tags within a block of wikitext. * Updated in 1.0.2 to also parse template tags in the format of {{Foo |a = b |b = c |c = therefore, a}} * @param string The text to process * @return string Formatted text * @example * <code> $text = '{{Template | parm1 = Foo | parm2 = Bar }}'; $text = RenderMan::include_templates($text); * </code> */ public static function include_templates($text) { global $db, $session, $paths, $template, $plugins; // Common objects // $template_regex = "/\{\{([^\]]+?)((\n([ ]*?)[A-z0-9]+([ ]*?)=([ ]*?)(.+?))*)\}\}/is"; // matches: // 1 - template name // 2 - parameter section $template_regex = "/ \{\{ # opening ([^\n\t\a\r]+) # template name ((?:(?:[\s]+\|?)[ ]*(?:[A-z0-9_]+)[ ]*=[ ]*?(?:.+))*) # parameters \}\} # closing /isxU"; if ( $count = preg_match_all($template_regex, $text, $matches) ) { //die('<pre>' . print_r($matches, true) . '</pre>'); for ( $i = 0; $i < $count; $i++ ) { $matches[1][$i] = sanitize_page_id($matches[1][$i]); $newlinemode = ( substr($matches[2][$i], 0, 1) == "\n" ); $parmsection = trim($matches[2][$i]); if ( !empty($parmsection) ) { $parms = RenderMan::parse_template_vars($parmsection, $newlinemode); if ( !is_array($parms) ) // Syntax error $parms = array(); } else { $parms = Array(); } if ( $tpl_code = RenderMan::fetch_template_text($matches[1][$i]) ) { $parser = $template->makeParserText($tpl_code); $parser->assign_vars($parms); $text = str_replace($matches[0][$i], $parser->run(), $text); } } } return $text; } /** * Preprocesses an HTML text string prior to being sent to MySQL. * @param string $text * @param bool $strip_all_php - if true, strips all PHP regardless of user permissions. Else, strips PHP only if user level < USER_LEVEL_ADMIN. */ public static function preprocess_text($text, $strip_all_php = true, $sqlescape = true) { global $db, $session, $paths, $template, $plugins; // Common objects $random_id = md5( time() . mt_rand() ); $code = $plugins->setHook('render_sanitize_pre'); foreach ( $code as $cmd ) { eval($cmd); } $can_do_php = ( $session->get_permissions('php_in_pages') && !$strip_all_php ); $can_do_html = $session->get_permissions('html_in_pages'); if ( $can_do_html && !$can_do_php ) { $text = preg_replace('#<(\?|\?php|%)(.*?)(\?|%)>#is', '<\\1\\2\\3>', $text); } else if ( !$can_do_html && !$can_do_php ) { $text = sanitize_html($text, true); // If we can't do PHP, we can't do Javascript either. $text = RenderMan::destroy_javascript($text); } // Strip out <nowiki> sections and PHP code $php = preg_match_all('#(<|<)\?php(.*?)\?(>|>)#is', $text, $phpsec); //die('<pre>'.htmlspecialchars(print_r($phpsec, true))."\n".htmlspecialchars(print_r($text, true)).'</pre>'); for($i=0;$i<sizeof($phpsec[1]);$i++) { $text = str_replace($phpsec[0][$i], '{PHP:'.$random_id.':'.$i.'}', $text); } $nw = preg_match_all('#<nowiki>(.*?)<\/nowiki>#is', $text, $nowiki); for($i=0;$i<sizeof($nowiki[1]);$i++) { $text = str_replace('<nowiki>'.$nowiki[1][$i].'</nowiki>', '{NOWIKI:'.$random_id.':'.$i.'}', $text); } $text = str_replace('~~~~~', enano_date('G:i, j F Y (T)'), $text); $text = str_replace('~~~~', "[[User:$session->username|$session->username]] ".enano_date('G:i, j F Y (T)'), $text); $text = str_replace('~~~', "[[User:$session->username|$session->username]] ", $text); $code = $plugins->setHook('render_sanitize_post'); foreach ( $code as $cmd ) { eval($cmd); } // gently apply some reverse-processing to allow Text_Wiki to do magic with TOCs and stuff $text = self::reverse_process_headings($text); // Reinsert <nowiki> sections for($i=0;$i<$nw;$i++) { $text = str_replace('{NOWIKI:'.$random_id.':'.$i.'}', '<nowiki>'.$nowiki[1][$i].'</nowiki>', $text); } // Reinsert PHP for($i=0;$i<$php;$i++) { $phsec = ''.$phpsec[1][$i].'?php'.$phpsec[2][$i].'?'.$phpsec[3][$i].''; if ( $strip_all_php ) $phsec = htmlspecialchars($phsec); $text = str_replace('{PHP:'.$random_id.':'.$i.'}', $phsec, $text); } $text = ( $sqlescape ) ? $db->escape($text) : $text; return $text; } public static function smilieyize($text, $complete_urls = false) { $random_id = md5( time() . mt_rand() ); // Smileys array - eventually this will be fetched from the database by // RenderMan::initSmileys during initialization, but it will all be hardcoded for beta 2 $smileys = Array( 'O:-)' => 'face-angel.png', 'O:)' => 'face-angel.png', 'O=)' => 'face-angel.png', ':-)' => 'face-smile.png', ':)' => 'face-smile.png', '=)' => 'face-smile-big.png', ':-(' => 'face-sad.png', ':(' => 'face-sad.png', ';(' => 'face-sad.png', ':-O' => 'face-surprise.png', ';-)' => 'face-wink.png', ';)' => 'face-wink.png', '8-)' => 'face-glasses.png', '8)' => 'face-glasses.png', ':-D' => 'face-grin.png', ':D' => 'face-grin.png', '=D' => 'face-grin.png', ':-*' => 'face-kiss.png', ':*' => 'face-kiss.png', '=*' => 'face-kiss.png', ':\'(' => 'face-crying.png', ':-|' => 'face-plain.png', ':-\\' => 'face-plain.png', ':-/' => 'face-plain.png', ':joke:' => 'face-plain.png', ']:->' => 'face-devil-grin.png', ']:->' => 'face-devil-grin.png', ':kiss:' => 'face-kiss.png', ':-P' => 'face-tongue-out.png', ':P' => 'face-tongue-out.png', ':-p' => 'face-tongue-out.png', ':p' => 'face-tongue-out.png', ':-X' => 'face-sick.png', ':X' => 'face-sick.png', ':sick:' => 'face-sick.png', ':-]' => 'face-oops.png', ':]' => 'face-oops.png', ':oops:' => 'face-oops.png', ':-[' => 'face-embarassed.png', ':[' => 'face-embarassed.png' ); // Strip out <nowiki> sections //return '<pre>'.htmlspecialchars($text).'</pre>'; $nw = preg_match_all('#<nowiki>(.*?)<\/nowiki>#is', $text, $nowiki); for ( $i = 0; $i < count($nowiki[1]); $i++ ) { $text = str_replace('<nowiki>' . $nowiki[1][$i] . '</nowiki>', '{NOWIKI:'.$random_id.':'.$i.'}', $text); } foreach ( $smileys as $smiley => $smiley_path ) { $hex_smiley = hexencode($smiley, '&#x', ';'); $pfx = ( $complete_urls ) ? get_server_url() : ''; $text = str_replace(' ' . $smiley, ' <!--#smiley code="' . self::escape_parser_hint_attrib($smiley) . '"--><nowiki> <!-- The above is a reverse-parser hint --> <img title="' . $hex_smiley . '" alt="' . $hex_smiley . '" src="' . $pfx . scriptPath . '/images/smilies/' . $smiley_path . '" style="border: 0;" /> </nowiki><!--#/smiley-->', $text); } //*/ // Reinsert <nowiki> sections for ( $i = 0; $i < $nw; $i++ ) { $text = str_replace('{NOWIKI:'.$random_id.':'.$i.'}', '<nowiki>'.$nowiki[1][$i].'</nowiki>', $text); } return $text; } /** * Generates a summary of the differences between two texts, and formats it as XHTML. * @param $str1 string the first block of text * @param $str2 string the second block of text * @return string */ public static function diff($str1, $str2) { global $db, $session, $paths, $template, $plugins; // Common objects require_once(ENANO_ROOT.'/includes/diff.php'); $str1 = explode("\n", $str1); $str2 = explode("\n", $str2); $diff = new Diff($str1, $str2); $renderer = new TableDiffFormatter(); return '<table class="diff">'.$renderer->format($diff).'</table>'; } /** * Changes wikitext image tags to HTML. * @param string The wikitext to process * @param array Will be overwritten with the list of HTML tags (the system uses tokens for TextWiki compatibility) * @return string */ public static function process_image_tags($text, &$taglist) { global $db, $session, $paths, $template, $plugins; // Common objects $s_delim = "\xFF"; $f_delim = "\xFF"; $taglist = array(); // Wicked huh? $ns_file = str_replace('/', '\\/', preg_quote($paths->nslist['File'])); $regex = '/ \[\[ # starting delimiter :' . $ns_file . '([\w\s0-9_\(\)!@%\^\+\|\.-]+?\.(?:png|gif|jpg|jpeg)) # image filename (?:(?:\|(?:.+?))*) # parameters \]\] # ending delimiter /ix'; preg_match_all($regex, $text, $matches); foreach ( $matches[0] as $i => $match ) { $full_tag =& $matches[0][$i]; $filename =& $matches[1][$i]; // apply recursion (hack? @todo could this be done with (?R) in PCRE?) $tag_pos = strpos($text, $full_tag); $tag_end_pos = $tag_pos + strlen($full_tag); while ( get_char_count($full_tag, ']') < get_char_count($full_tag, '[') && $tag_end_pos < strlen($text) ) { $full_tag .= substr($text, $tag_end_pos, 1); $tag_end_pos++; } if ( $tag_end_pos > strlen($text) ) { // discard tag, not closed fully continue; } // init the various image parameters $width = null; $height = null; $scale_type = null; $raw_display = false; $clear = null; $caption = null; // trim tag and parse particles $tag_trim = rtrim(ltrim($full_tag, '['), ']'); // trim off the filename from the start of the tag $filepart_len = 1 + strlen($paths->nslist['File']) + strlen($filename) + 1; $tag_trim = substr($tag_trim, $filepart_len); // explode and we should have parameters $tag_parts = explode('|', $tag_trim); // for each of the parameters, see if it matches a known option. If so, apply it; // otherwise, see if a plugin reserved that parameter and if not treat it as the caption foreach ( $tag_parts as $param ) { switch($param) { case 'left': case 'right': $clear = $param; break; case 'thumb': $scale_type = 'thumb'; break; case 'raw': $raw_display = true; break; default: // height specification if ( preg_match('/^([0-9]+)x([0-9]+)$/', $param, $dims) ) { $width = intval($dims[1]); $height = intval($dims[2]); break; } // not the height, so see if a plugin took this over // this hook requires plugins to return true if they modified anything $code = $plugins->setHook('img_tag_parse_params'); foreach ( $code as $cmd ) { if ( eval($cmd) ) break 2; } // we would have broken out by now if a plugin properly handled this, // so just set the caption now. $caption = $param; break; } } if ( !isPage( $paths->nslist['File'] . $filename ) ) { $text = str_replace($full_tag, '[[' . $paths->nslist['File'] . $filename . ']]', $text); continue; } if ( $scale_type == 'thumb' ) { $r_width = 225; $r_height = 225; $url = makeUrlNS('Special', 'DownloadFile/' . $filename, 'preview&width=' . $r_width . '&height=' . $r_height, true); } else if ( !empty($width) && !empty($height) ) { $r_width = $width; $r_height = $height; $url = makeUrlNS('Special', 'DownloadFile/' . $filename, 'preview&width=' . $r_width . '&height=' . $r_height, true); } else { $url = makeUrlNS('Special', 'DownloadFile/' . $filename); } $img_tag = '<img src="' . $url . '" '; // if ( isset($r_width) && isset($r_height) && $scale_type != '|thumb' ) // { // $img_tag .= 'width="' . $r_width . '" height="' . $r_height . '" '; // } $img_tag .= 'style="border-width: 0px; /* background-color: white; */" '; $code = $plugins->setHook('img_tag_parse_img'); foreach ( $code as $cmd ) { eval($cmd); } $img_tag .= '/>'; $s_full_tag = self::escape_parser_hint_attrib($full_tag); $complete_tag = '<!--#imagelink src="' . $s_full_tag . '" -->'; if ( !empty($scale_type) && !$raw_display ) { $complete_tag .= '<div class="thumbnail" '; $clear_text = ''; if ( !empty($clear) ) { $side = ( $clear == 'left' ) ? 'left' : 'right'; $opposite = ( $clear == 'left' ) ? 'right' : 'left'; $clear_text .= "float: $side; margin-$opposite: 20px; width: {$r_width}px;"; $complete_tag .= 'style="' . $clear_text . '" '; } $complete_tag .= '>'; $complete_tag .= '<a href="' . makeUrlNS('File', $filename) . '" style="display: block;">'; $complete_tag .= $img_tag; $complete_tag .= '</a>'; $mag_button = '<a href="' . makeUrlNS('File', $filename) . '" style="display: block; float: right; clear: right; margin: 0 0 10px 10px;"><img alt="[ + ]" src="' . scriptPath . '/images/thumbnail.png" style="border-width: 0px;" /></a>'; if ( !empty($caption) ) { $complete_tag .= $mag_button . $caption; } $complete_tag .= '</div>'; } else if ( $raw_display ) { $complete_tag .= "$img_tag"; $taglist[$i] = $complete_tag; $repl = "{$s_delim}e_img_{$i}{$f_delim}"; $text = str_replace($full_tag, $repl, $text); continue; } else { $complete_tag .= '<a href="' . makeUrlNS('File', $filename) . '" style="display: block;"'; $code = $plugins->setHook('img_tag_parse_link'); foreach ( $code as $cmd ) { eval($cmd); } $complete_tag .= '>'; $complete_tag .= $img_tag; $complete_tag .= '</a>'; } $complete_tag .= "<!--#/imagelink-->"; $taglist[$i] = $complete_tag; /* $pos = strpos($text, $full_tag); while(true) { $check1 = substr($text, $pos, 3); $check2 = substr($text, $pos, 1); if ( $check1 == '<p>' || $pos == 0 || $check2 == "\n" ) { // die('found at pos '.$pos); break; } $pos--; } */ /* $repl = "{$s_delim}e_img_{$i}{$f_delim}"; $text = substr($text, 0, $pos) . $repl . substr($text, $pos); $text = str_replace($full_tag, '', $text); */ $text = str_replace_once($full_tag, $complete_tag, $text); unset($full_tag, $filename, $scale_type, $width, $height, $clear, $caption, $r_width, $r_height); } // if ( count($matches[0]) > 0 ) // die('<pre>' . htmlspecialchars($text) . '</pre>'); return $text; } /** * Finalizes processing of image tags. * @param string The preprocessed text * @param array The list of image tags created by RenderMan::process_image_tags() */ public static function process_imgtags_stage2($text, $taglist) { $s_delim = "\xFF"; $f_delim = "\xFF"; foreach ( $taglist as $i => $tag ) { $repl = "{$s_delim}e_img_{$i}{$f_delim}"; $text = str_replace($repl, $tag, $text); } return $text; }}?>