Difference between revisions of "Xmlwiki.php"

From Organic Design wiki
m (Caretaker: {{#security:edit|dev}})
m
Line 1: Line 1:
{{#security:edit|dev}}
 
 
# xmlWiki - MediaWiki XML Hack
 
# xmlWiki - MediaWiki XML Hack
 
# Nad - Started: 2005-05-18
 
# Nad - Started: 2005-05-18
 +
# {{#security:edit|dev}}
  
 
# Exit if not included from index.php
 
# Exit if not included from index.php

Revision as of 04:46, 16 April 2007

  1. xmlWiki - MediaWiki XML Hack
  2. Nad - Started: 2005-05-18
  3. {{#security:edit|dev}}
  1. Exit if not included from index.php

defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!'); if (php(3)) die('xmlWiki cannot run on less than PHP4!');

  1. Add the rest of the hooks

$wgHooks['PreParser'][] = 'xwPreParserHook'; $wgHooks['ParserBeforeStrip'][] = 'xwPreParserHook'; $wgHooks['ArticleSaveComplete'][] = 'xwPostParserHook'; $wgHooks['ParserAfterTidy'][] = 'xwPostParserHook'; $wgHooks['Input'][] = 'xwInputHook'; $wgHooks['Output'][] = 'xwOutputHook'; $wgHooks['ParserFunctions'][] = 'xwTransclusionSecurity'; function xwTransclusionSecurity(&$title,&$text,$args,$argc) { $title = ereg_replace('^:',,$title); if ($title && !xwArticleAccess($title)) { $text = 'Sorry, article not readable!'; $title = false; } return true; }

  1. System globals

$xwDebug = false; $xwMessages = array(); $xwArticleCache = array(); $xwStyleSheets = array(); $xwTemplate = null; $xwParserHookCalled = false; $xwSkinHookCalled = false; $xwMsgToken = ; $xwCssToken = ; $xwScript = $wgScript; $xwTransformID = 1;

  1. User globals

$xwUserName = ucwords( $wgUser->mName ); $xwAnonymous = ereg( "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", $xwUserName ); $xwUserSYS = null; $xwUserGroups = array($xwUserName, 'anyone'); $xwEdit = isset($_REQUEST['action']) && ($_REQUEST['action'] == 'edit'); $xwView = (!isset($_REQUEST['action']) || (isset($_REQUEST['action']) && ($_REQUEST['action'] == 'view'))); $xwRaw = isset($_REQUEST['action']) && ($_REQUEST['action'] == 'raw'); $xwPreview = isset($_REQUEST['wpPreview']); $xwSave = isset($_REQUEST['wpSave']);

  1. Article globals

$xwArticleTitle = $wgTitle->getPrefixedText(); $xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false); $xwArticleProperties = null; $xwIsProperties = preg_match('/^xml:.+$/i', $xwArticleTitle); $xwIsSystem = preg_match('/^sys:.+$/i', $xwArticleTitle); $xwIsUser = preg_match('/^user:.+$/i', $xwArticleTitle); $xwIsSpecial = preg_match('/^special:.+$/i', $xwArticleTitle); $xwIsAdmin = false; $xwArticleReadableBy = null; $xwArticleWritableBy = null;

  1. Get default properties article

$xwDefaultProperties = $xwDefaultPropertiesXML = xwArticleContent( 'default-properties.xml', false ); xwDomificateArticle( $xwDefaultProperties, 'defaults' );

  1. Get user-system-article and extract users' groups from it if any

$xwUserSYS = xwArticleContent( "sys:$xwUserName" ); xwDomificateArticle( $xwUserSYS, "sys:$xwUserName" ); $xwUserGroups = array_merge( $xwUserGroups, xwGetListByTagname( $xwUserSYS, 'groups' ) );

  1. Read user-prefs

xwGetProperty( $xwUserSYS, 'xpath:/user/debug', $xwDebug );

  1. Get article-meta-file and extract article-perms

if ( $xwIsSystem ) $xwArticleReadableBy = $xwArticleWritableBy = array('admin'); else {

# Get xml:article (self if already xml:*) if ( $p = $xwIsProperties ? xwDomificateArticle( $xwArticle ) : xwArticleProperties( $xwArticleTitle ) )

# Start with default-properties and merge article props (so new transforms on top) $xwArticleProperties = '<?xml version="1.0" standalone="yes"?> <!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd"> <properties/>'; xwDomificateArticle( $xwArticleProperties, 'CreateProperties' ); xwMergeDOM( $xwArticleProperties, $xwDefaultProperties ); xwMergeDOM( $xwArticleProperties, $p );

$xwArticleReadableBy = xwGetListByTagname( $xwArticleProperties, 'read' ); $xwArticleWritableBy = xwGetListByTagname( $xwArticleProperties, 'write' ); if ( $xwIsProperties ) $xwArticleProperties = false; } if ( !is_object( $xwArticleProperties ) ) $xwArticleProperties = $xwDefaultProperties; if ( $xwIsSpecial || !count( $xwArticleReadableBy ) ) $xwArticleReadableBy = array('anyone'); if ( !count($xwArticleWritableBy) ) $xwArticleWritableBy = array('anyone');

  1. If no language specified in properties, guess from name and content

if ( !xwGetProperty( $xwArticleProperties, 'language', $xwLanguage ) ) xwSetProperty( $xwArticleProperties, 'language', xwArticleType( $xwArticleTitle, $xwArticle ) );

  1. Set perms for this request

if ($xwDebug) xwMessage('PERMISSIONS:','green'); if (in_array('admin', $xwUserGroups)) $xwIsAdmin = $xwReadable = $xwWritable = true; else { $xwReadable = 0 < count(array_intersect($xwArticleReadableBy, $xwUserGroups)); $xwWritable = 0 < count(array_intersect($xwArticleWritableBy, $xwUserGroups)); } if ($xwDebug) { xwMessage('Groups: '.join(', ', $xwUserGroups)); xwMessage('Readable ('.($xwReadable?'yes':'no').'): '.join(', ', $xwArticleReadableBy)); xwMessage('Writable ('.($xwWritable?'yes':'no').'): '.join(', ', $xwArticleWritableBy)); }

  1. Divert to access-denied article if not readable

if (!$xwReadable) { $action = 'view'; $xwSave = $xwEdit = false; }

  1. Handle security for Move

if (!$xwIsAdmin and ($target = $_REQUEST['target']) ) { if ($xwArticleProperties = new Article(Title::newFromText("xml:$target"))) $xwArticleProperties = $xwArticleProperties->getContentWithoutUsingSoManyDamnGlobals(); $writableBy = xwGetListByTagname(xwDomificateArticle($xwArticleProperties), 'write'); if (!count($writableBy)) $writableBy = array('anyone'); if (!count(array_intersect($writableBy, $xwUserGroups))) { $xwArticleTitle = $target; xwMessage("Sorry article \"$xwArticleTitle\" not movable!",'red'); $wgArticle = new Article($wgTitle = Title::newFromText($target)); $xwArticle = $wgArticle->getContent(false); } }

  1. Apply init transforms

xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'init', 'INIT-HOOK' );


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. INPUT HOOK
  1. Parse and process input from forms

function xwInputHook() {

global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST; global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle; global $xwUserGroups, $xwIsProperties, $xwIsSystem, $xwIsAdmin; if ($xwDebug) xwMessage('INPUT-HOOK:','green');

# If not writable, change action to view if ( !$xwWritable && !in_array($action, array('view','history','raw')) ) { xwMessage('Sorry, article not writable, action changed to "view".', 'red'); $action = 'view'; $xwSave = $xwEdit = false; }

# Scan POST and apply any XPath inputs foreach ($_REQUEST as $query => $value) { if (ereg('^xpath.3A.+.3A', $query)) $query = urldecode( $query ); if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, str_replace('&', '%26', $value)); }

# if saving a pseudo-namespace... if ($xwSave) { if ( $xwIsProperties || $xwIsSystem ) { # wpTextbox1 should be valid XML to be saved $tb = $_REQUEST['wpTextbox1']; xwDomificateArticle( $tb, 'POST-DATA' ); if ( !is_object($tb) ) { xwMessage('A meta-article must be valid XML! article not saved.', 'red'); $action = 'view'; $xwSave = $xwEdit = false; } elseif ( $xwIsProperties && !$xwIsAdmin ) { foreach ( xwGetListByTagname($tb, 'write') as $perm ) { if ( !in_array( $perm, $xwUserGroups ) ) { xwMessage("Only members of \"$perm\" can set permissions for \"$perm\"! article not saved.", 'red'); $action = 'view'; $xwSave = $xwEdit = false; } } } } }

}


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. OUTPUT HOOK
  1. Post-process and output article

function xwOutputHook() {

global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem, $xwIsAdmin; global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit, $action; if ($action == 'raw') return; if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');

# Activate the skin-hook and generate wiki's output $wgUser->setOption('skin', 'xwskin'); $wgOut->output();

# Editing article if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) { # If editing an empty sys:article or xml:article, add default content if ($xwIsSystem) { $xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $xwArticle .= "<!DOCTYPE xmlwiki:user SYSTEM \"xmlwiki-user.dtd\">\n"; $xwArticle .= "<user>\n\t<groups></groups>\n</user>\n".$m[2]; xwMessage('Note: There is currently no content for this system article, default content has been generated','red'); } elseif ($xwIsProperties) { $a = xwArticleContent( str_replace( 'Xml:', , $xwArticleTitle ) ); if ( preg_match("/\\[\\[Category:(.+?)]]/", $a, $n) && $xwArticle = xwArticleProperties('Category:'.$n[1]) ) { xwUndomificateArticle( $xwArticle ); xwMessage('Note: This article has no properties, default content has been inherited from Xml:'.$n[1], 'red'); } else { xwMessage('Note: This article has no properties, a general default has been generated', 'red'); $xwArticle = "<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $xwArticle .= "<!DOCTYPE xmlwiki:properties SYSTEM \"xmlwiki-properties.dtd\">\n"; $xwArticle .= "<properties>\n\t<read>anyone</read>\n\t<write>$xwUserName</write>\n"; $xwArticle .= "\t<language></language>\n\t<category></category>\n\t\n\t<view></view>\n\t<edit></edit>\n\t<save></save>\n</properties>\n"; } $xwArticle = $m[1].$xwArticle.$m[2]; } } elseif ( $xwEdit ) { # Editing normally, apply transforms in edit-list xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'edit', 'OUTPUT-HOOK' ); }

if ( $xwDebug ) { global $xwParserHookCalled, $xwSkinHookCalled; if ( !$xwParserHookCalled ) xwMessage( 'PARSER-HOOK was not called', 'green' ); if ( !$xwSkinHookCalled ) xwMessage( 'SKIN-HOOK was not called', 'green' ); }

# Insert stylesheets and messages into output xwReplaceTokens( $xwArticle );

# Permhack: remove private info if search result if ( isset($_REQUEST['search']) && !$xwIsAdmin ) $xwArticle = preg_replace_callback(

'/

  • <a href.+?>(.+?)<\\/a>.+?<\\/li>/is', create_function( '$m', 'return xwArticleAccess($m[1],"read")?$m[0]:"";' ), $xwArticle ); # Output final result print $xwArticle; }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. PRE-PARSER HOOK
    function xwPreParserHook( &$text ) { global $xwArticleTitle, $xwArticle, $xwArticleProperties, $xwSave; global $xwParserHookCalled, $xwDebug, $xwReadable, $xwScript; if ( $xwDebug ) xwMessage( 'PREPARSER-HOOK:', 'green' ); $xwParserHookCalled = true; # Save clears all output, but we need to parse it for save-transforms if ( $xwSave ) $text = $_REQUEST['wpTextbox1']; # If this text-fragment is our article, apply transforms if ( $xwSave or is_object($xwArticle) or strncmp( $text, $xwArticle, 100 ) == 0 or strncmp( $text, xwArticleContent( $xwArticleTitle, false ), 100 ) == 0 ) { if ( $xwDebug ) xwMessage( "Matching text-fragment intercepted." ); if ( !$xwReadable ) { xwMessage( 'Sorry, article not readable!', 'red' ); $text = $xwArticle = "<a href=\"$xwScript?title=Special:Userlogin\">Please Login</a>"; return false; } # Apply data-transforms # - domificate first if xml # - undomificate after if still an object if ( !is_object($xwArticle) and preg_match("/^<\\?xml/i", $text) ) xwDomificateArticle( $text, 'PreParserHook/text' ); xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK' ); xwUndomificateArticle( $text, $xwArticleTitle ); } elseif ( $xwDebug ) xwMessage( 'Parsing other content' ); # Get language and return true back to parser if lang wasn't ours xwGetProperty( $xwArticleProperties, 'language', $language ); if ($xwDebug) xwMessage( 'Content language determined as '.($language ? strtoupper($language) : 'WIKI') ); return $language == ; }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. POST-PARSER HOOK
    3. - this hook was not implemented or had been removed,
    4. I've put in on the official ArticleSaveComplete hook for now
    5. - was function xwPostParserHook( &$text )
    function xwPostParserHook( &$article, &$user, &$text, &$summary, &$minoredit, &$watchthis, &$sectionanchor ) { global $xwArticleTitle, $xwArticleProperties, $xwSave, $xwDebug; #if ( !$xwSave ) return; if ( $xwDebug ) xwMessage( 'POSTPARSER-HOOK:','green' ); # Apply view transforms first #xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'view', 'POSTPARSER-HOOK' ); # Now apply the save-stack $dummy = ""; xwReduceTransformStack( $dummy, $xwArticleProperties, $xwArticleTitle, 'save', 'POSTPARSER-HOOK' ); # Output should be empty for save #$text = ; }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. SKIN HOOK
    1. If attempted xml encountered, try to domificate and transform
    2. - Raw requests don't get here
    function xwSkinHook( &$tmpl ) { global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw; global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw; # Extract article from existing template structure if ( $xwDebug ) xwMessage( 'SKIN-HOOK:', 'green' ); $xwSkinHookCalled = true; $xwTemplate = $tmpl; if ( $xwReadable ) $xwArticle = $xwTemplate->data['bodytext']; # Apply view-transforms # - "default-skin.php" builds XML output structure # - "default-skin.xslt" transforms XML to HTML with table-layout # - "default-skin.css" transforms HTML with design xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK' ); }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. ARTICLE FUNCTIONS
    3. - these all use super-globals since they can be called from within evals
    1. Return true if passed title readable/writable by current user
    function xwArticleAccess( $title, $perm = 'read' ) { global $xwIsAdmin, $xwUserGroups, $xwDefaultProperties; if ( !$access = $xwIsAdmin ) { $properties = xwArticleProperties( $title ); xwMergeDOM( $properties, $xwDefaultProperties ); $access = xwGetListByTagname( $properties, $perm ) + array( 'anyone' ); $access = count( array_intersect( $access, $xwUserGroups ) ); } return $access > 0; }
    1. Retreive wiki-article as raw text
    function xwArticleContent( $articleTitle, $secure = true ) { global $xwArticleCache, $xwDebug, $xwIsAdmin, $xwUserGroups; if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue'); # Temp: quick fix for slack security model :-( # - should combine article and properties into getContent() if ($secure and !$xwIsAdmin) { if ($properties = new Article(Title::newFromText('xml:'.ucwords($articleTitle)))) $properties = $properties->getContentWithoutUsingSoManyDamnGlobals(); $readableBy = xwGetListByTagname(xwDomificateArticle($properties), 'read') + array('anyone'); if (!count(array_intersect($readableBy, $xwUserGroups))) { xwMessage("Sorry article \"$articleTitle\" not readable!",'red'); return "Please Login"; } } # Return with results if already cached if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle]; # Get wiki article content (or use global if no article passed) # - using getContentWithoutUsingSoManyDamnGlobals() because getContent() fucks up nested-article-reads if ($article = new Article(Title::newFromText($articleTitle))) $article = $article->getContentWithoutUsingSoManyDamnGlobals(); if ($article == ) { if ($xwDebug) xwMessage("Article \"$articleTitle\" not found."); return false; } elseif ($article == '(There is currently no text in this page)') { if ($xwDebug) xwMessage("Article \"$articleTitle\" not found."); return false; } if ($articleTitle) $xwArticleCache[$articleTitle] = $article; return $article; }
    1. Return properties object from title
    2. - returns self if already a properties article
    function xwArticleProperties( $articleTitle, $xmlself = true ) { global $xwDebug; if ($xwDebug) xwMessage("xwArticleProperties(\"$articleTitle\")",'blue'); $id = ( $xmlself && eregi( '^xml:', $articleTitle ) ) ? $articleTitle : 'xml:'.ucfirst($articleTitle); if ( !$properties = xwArticleContent( $id, false ) ) $properties = '<?xml version="1.0" standalone="yes"?> <!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd"> <properties/>'; return xwDomificateArticle( $properties, $id ); }
    1. Decide kind of article from content and title
    function xwArticleType( $title, $article ) { if ( is_object($article) ) return 'xml'; if ( eregi('\\.php$', $title) ) return 'php'; if ( eregi('\\.css$', $title) ) return 'css'; if ( eregi('^talk:', $title) ) return ; if ( preg_match("/^<\\?xml.+?\\?>.*<xsl:stylesheet/is", $article) ) return 'xslt'; if ( eregi('^(sys|xml):.+$', $title) || ereg('^<\\?xml', $article) ) return 'xml'; if ( eregi('^<\\?html', $article ) || eregi('\\.html$', $title)) return 'html4strict'; if ( eregi('\\.p[lm]$', $title) ) return 'perl'; if ( ereg('^#![/a-zA-Z0-9]+\\/perl', $article) ) return 'perl'; if ( eregi('\\.as$', $title) ) return 'actionscript'; if ( eregi('\\.dtd$', $title) ) return 'xml'; if ( eregi('\\.tex$', $title) ) return 'tex'; if ( eregi('\\.nt$', $title) ) return 'dna'; if ( eregi('\\.r$', $title) ) return 'r'; if ( eregi('\\.py$', $title) ) return 'python'; if ( eregi('\\.java$', $title) ) return 'java'; if ( eregi('\\.(c\\+\\+|cpp)$', $title)) return 'cpp'; if ( eregi('\\.[ch]$', $title) ) return 'c'; if ( eregi('\\.js$', $title) ) return 'javascript'; if ( ereg('^#![/a-zA-Z0-9]+sh', $article) ) return 'bash'; return ; }
    1. Convert passed article to a DOM object
    2. - Article is unchanged if not valid XML
    function xwDomificateArticle(&$article, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue'); ob_start(); if ($article) { if (php(4)) $dom = domxml_open_mem($article); else $dom = DOMDocument::loadXML($article); } if (isset($dom) && is_object($dom)) $article = $dom; else { # Could not convert, extract error messages from output if ($xwDebug) xwMessage("Failed :-(", 'red'); xwExtractMessages(); } ob_end_clean(); return $article; }
    1. Reduce article to a string if it's a DOM object
    2. - ie if all xslt's had xml-output-method
    function xwUndomificateArticle(&$article, $id = ) { if (!is_object($article)) return false; global $xwDebug; if ($xwDebug) xwMessage("xwUndomificate(\"$id\")",'blue'); # TODO: # Validate, report errors # xwApplyXSLT($article, $xslt) if xml referrs one # Convert DOM back to XML if still an object if (php(4)) { # Attempt to fix PHP4's crap dump $xml = "<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $children = $article->child_nodes(); $xml .= trim($article->dump_node($children[0]))."\n"; for ($i = 1; $i < count($children); $i++) { $top = $children[$i]; $name = $top->node_name(); $xml .= "<$name>\n"; foreach ($top->child_nodes() as $child) { $line = trim($article->dump_node($child)); if ($line) $xml .= "\t$line\n"; } $xml .= "</$name>\n"; } $article = $xml; } else $article = $article->saveXML(); return true; }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. TRANSFORM FUNCTIONS
    1. Apply all transforms in passed stack
    function xwReduceTransformStack( &$article, &$properties, $title, $event, $id = ) { global $xwDebug; $GLOBALS['xwTransformID']++; if ( $xwDebug ) xwMessage( "Applying \"$event\" transforms to \"$title\" from \"$id\"."); if ( is_object( $properties ) ) { if (php(4)) { $docType = $properties->doctype(); $root = $properties->document_element(); } else { $docType = $properties->doctype; $root = $properties->documentElement; } if ( ereg( '^xmlwiki:properties', $docType->name ) ) { if (php(4)) { while (count($tNodes = $root->get_elements_by_tagname($event))) { $tName = $tNodes[0]->get_content(); xwApplyTransform($tName, $article, $properties, $title, $event); $tNodes[0]->unlink_node(); } } else { while (($tNodes = $root->getElementsByTagname($event)) && $tNodes->length) { $tNode = $tNodes->item(0); xwApplyTransform($tNode->nodeValue, $article, $properties, $title, $event); $tNode->parentNode->removeChild($tNode); } } } else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red'); } }
    1. Apply a transform
    function xwApplyTransform($transform, &$article, &$properties, &$title, $event) { global $xwDebug; if ( $transform ) { if ( $tText = xwArticleContent($transform) ) { # Get transform language (take a guess if none specified) $tProperties = xwArticleProperties($transform); xwGetProperty( $tProperties, 'language', $tLang ); if ( !$tLang ) $tLang = xwArticleType( $transform, $tText ); if ( $tLang == 'xslt' ) xwApplyXSLT( $article, $title, $properties, $tText, $transform ); elseif ( $tLang == 'php' ) xwApplyPHP( $article, $tText, $transform, $title, $properties, $tProperties, $event ); elseif ( $tLang == 'css' ) xwApplyCSS( $title, $transform ); elseif ( $tLang == 'xml' ) xwMergeDOM( $properties, xwDomificateArticle( $tText, $transform ), $title, $transform ); } else xwMessage( "Unknown transform \"$transform\" attached to \"$event\" event!", 'red' ); } }
    1. Apply PHP-transform if article perms are only writable by admin or dev
    function xwApplyPHP( &$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event ) { global $xwDebug; if ( $xwDebug ) xwMessage( "xwApplyPHP( $title , $tTitle )", 'blue' ); # Get transform perms if ( !count( preg_grep( '/^(admin)|(dev)$/i', xwGetListByTagname( $tProperties, 'write' ) ) ) ) { xwMessage( "Failed to execute \"$tTitle\", must be writable only by dev or admin!", 'red' ); return; } # Permissions ok, execute the transform code and trap output $path = '/properties/'.preg_replace( "/\\.\\w+$/", , $tTitle ); $script = $GLOBALS['xwScript']; ob_start(); eval( "?>$tCode<?" ); xwExtractMessages( $tTitle ); ob_end_clean(); }
    1. Append CSS stylesheet link to output
    function xwApplyCSS($title, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue'); $xwStyleSheets[] = $tName; }
    1. Apply XSLT to article-dom
    2. - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
    3. - if output-method is html, article will get stringified and language updated
    function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue'); if (is_object($article)) { ob_start(); if ($tObject = domxml_xslt_stylesheet($xslt)) { $tResult = $tObject->process($article); # If output-method is html, use XSLT's dump_mem to create an html-string if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt)) { $tResult = $tObject->result_dump_mem($tResult); xwSetProperty($properties, 'language', ); } } xwExtractMessages(); ob_end_clean(); if (isset($tResult)) $article = $tResult; } else $xwStyleSheets[] = $tName; }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. DOM FUNCTIONS
    1. Return result set of an XPath query on passed DOM-object
    function xwXPathQuery(&$dom, $query) { $result = array(); if (is_object($dom)) { global $xwDebug; if ($xwDebug) xwMessage("xwXPathQuery( $query )",'blue'); ob_start(); if (php(4)) { $context = $dom->xpath_new_context(); if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset; elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red'); } else if ($xpath = new DOMXPath($dom)) $result = $xpath->query($query); xwExtractMessages(); ob_end_clean(); } if (php(4)) return is_array($result) ? $result : array(); else { $tmp = array(); foreach ($result as $node) $tmp[] = $node; return $tmp; } } function xwGetProperty(&$properties, $query, &$value) { global $xwDebug; if (!preg_match("/^xpath:(.+)$/", $query, $match)) $match = array(, "/properties/$query"); if (count($results = xwXPathQuery($properties, $match[1])) == 0) return false; if (php(4)) $value = $results[count($results)-1]->get_content(); else $value = $results[count($results)-1]->nodeValue; if ($xwDebug) xwMessage("xwGetProperty( $query ) returned \"$value\"", 'purple'); return true; }
    1. - Syntax: xpath:XPathQuery:[[@]node][+] = value
    2. - if "node" exists, then a new element (or att if "@node") is created for the value
    3. - if "+" exists, content is appended, else replaced
    function xwSetProperty(&$properties, $query, $value) { global $xwDebug; if ($xwDebug) xwMessage("xwSetProperty( $query , $value )", 'purple'); if (!is_object($properties)) return false; if (!preg_match("/^xpath:(.+):(@?)(\\w*?)(\\+?)$/", $query, $match)) { # If not xpath, remove property before creating $tNodes = xwXPathQuery($properties, "/properties/$query"); foreach ($tNodes as $tNode) if (php(4)) $tNode->unlink_node(); else $tNode->parentNode->removeChild($tNode); $match = array(, '/properties', , $query, ); } list(, $xpath, $att, $node, $append) = $match; foreach (xwXPathQuery($properties, $xpath) as $result) { if ($node) { # create new element/attribute in result-node if ($att) { if (php(4)) $result->append_child($properties->create_attribute($node, $value)); else $result->setAttribute($node, $value); } else { if (php(4)) { $node = $properties->create_element($node); $node->set_content($value); $result->append_child($node); } else $result->appendChild($properties->createElement($node, $value)); } } else { # node not set, replace or append current-result-value if ($append) { if (php(4)) $result->set_content( ($result->get_content()).$value ); else $result->nodeValue .= $value; } else { if (php(4)) { $newnode = $properties->create_element($result->tagname); $newnode->set_content($value); $result->replace_node($newnode); } else $result->parentNode->replaceChild($properties->createElement($result->nodeName, $value), $result); } } } return true; }
    1. Return array of comma-separated-items in element content
    2. - if more than one element all are split and appended into the list
    function xwGetListByTagname(&$xml, $tag) { $list = array(); if (is_object($xml)) { if (php(4)) { $root = $xml->document_element(); foreach ($root->get_elements_by_tagname($tag) as $element) if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv)); } else { foreach ($xml->documentElement->getElementsByTagname($tag) as $element) if ($csv = $element->nodeValue) $list = array_merge($list, split(',', $csv)); } } return $list; }
    1. Merge the two passed DOM objects
    2. - appends, does not overwrite
    function xwMergeDOM(&$dom1, &$dom2, $id1 = 'dom1', $id2 = 'dom2') { global $xwDebug; # Return if first DOM not a valid properties article if (!is_object($dom1)) return false; if (php(4)) $docType = $dom1->doctype(); else $docType = $dom1->doctype; if (!ereg('^xmlwiki:properties', $docType->name)) { if ($xwDebug) xwMessage('xwMergeDOM: First parameter is not an xmlwiki:properties object!','red'); return false; } # Return DOM1 if DOM2 not valid properties if (!is_object($dom2)) return $dom1; if (php(4)) $docType = $dom2->doctype(); else $docType = $dom2->doctype; if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1; # Both are ok, perform the merge if ($xwDebug) xwMessage("xwMergeDOM( &$id1 , $id2 )",'blue'); if (php(4)) { $root1 = $dom1->document_element(); $root2 = $dom2->document_element(); foreach ($root2->child_nodes() as $node) $root1->append_child($node->clone_node(true)); } else $dom1->documentElement->appendChild($dom1->importNode($dom2->documentElement, true)); return true; }
    1. Remove all occourences of a certain element
    function xwRemoveElement( &$properties, $element ) { $root = $properties->document_element(); if (php(4)) { while ( count($tNodes = $root->get_elements_by_tagname($element)) ) $tNodes[0]->unlink_node(); } else { while ( ($tNodes = $root->getElementsByTagname($element)) && $tNodes->length ) { $tNode = $tNodes->item(0); $tNode->parentNode->removeChild( $tNode ); } } }
    1. ---------------------------------------------------------------------------------------------------------------------- #
    2. UTILITY FUNCTIONS
    1. Extract error-messages from captured output and put in proper message-queue
    function xwExtractMessages($where = false) { if ($where) $where = " of \"$where\""; $err = preg_replace("/<.+?>/", "", ob_get_contents()); foreach (split("\n", $err) as $msg) if (trim($msg)) { if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1]$where)"; $msg = preg_replace('/ in .+? on line.+?([0-9]+)/', " (Line $1$where)", $msg); if (isset($line)) $msg .= $line; xwMessage($msg, 'red'); } }
    1. Add message to queue
    function xwMessage($msg, $col = '#000080') { global $xwMessages; $msg = htmlentities($msg); return $xwMessages[] = "$msg"; } function xwReplaceTokens(&$article, $embed = false) { global $xwStyleSheets, $xwMessages, $xwCssToken, $xwMsgToken, $xwScript; # Stylesheets $stylesheets = ; foreach ($xwStyleSheets as $title) if ($embed) $stylesheets .= '<style type="text/css" media="screen">'.xwArticleContent($title).'</style>'; else $stylesheets = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$xwScript?title=$title&action=raw&ctype=text/css\" />\n$stylesheets"; $article = str_replace($xwCssToken, $stylesheets, $article); # Messages if (count($xwMessages)) { $msg = '
    There are Messages
    '; $msg .= '
    • '.join("
    • ", $xwMessages).'
    ';

    $article = str_replace($xwMsgToken, $msg, $article); } }

    1. Print list of passed object's methods and properties

    function xwCheckoutObject($obj) { if (is_object($obj)) { print strtoupper("
    $obj properties:
    "); foreach (get_object_vars($obj) as $k=>$v) print htmlentities("$k => $v")."
    "; print strtoupper("
    $obj methods:
    "); foreach (get_class_methods($obj) as $k=>$v) print "$v
    "; } if (is_array($obj)) { print strtoupper("
    Array content:
    "); foreach ($obj as $k=>$v) print "$k => $v
    "; } die; }

    1. Return true if PHP major version equals passed int
    2. - needed due to different DOM implementation on 4 and 5 (and none on 3)

    function php($ver) { return substr(phpversion(),0,1) == "$ver"; }

    1. Add a log entry to a log article ([[XmlWiki Log]] is default)

    function xwLog($comment, $title = 'XmlWiki Log') { global $wgLang; $ts = $wgLang->timeanddate(wfTimestampNow(),true); $log = xwArticleContent($title)."\n*$ts : $comment"; $a = new Article(Title::newFromText($title)); $a->quickEdit($log); }

    1. Parse wikitext string and return HTML string

    function xwWikiParse(&$text) { global $wgHooks,$wgTitle,$wgUser; $tmp = $wgHooks['PreParser']; $wgHooks['PreParser'] = array(); $parser = new Parser; $output = $parser->parse($text,$wgTitle,ParserOptions::newFromUser($wgUser),true,false); $wgHooks['PreParser'] = $tmp; return $output->getText(); }

    1. Parse an XmlWiki article and return resulting HTML

    function xwXmlWikiParse($title) { global $wgTitle,$wgHooks,$wgUser,$xwUserName,$xwAnonymous; $bookmarks = $xwAnonymous ? "Bookmarks:Default" : "Bookmarks:$xwUserName"; $text = str_replace('Template:BOOKMARKS',$bookmarks,xwArticleContent($title)); # Hack so that Template:BOOKMARKS can be used in an XmlWiki link $text = preg_replace('//s','@@'.'@@$1@@'.'@@',$text); # Hack so that HTML comments in the wikitext are preserved $properties = xwArticleProperties($title); xwSetProperty($properties,'xpath:/properties:data','document.php'); # If no language specified in properties, guess from name and content

           xwGetProperty($properties,'language',$lang);
           if (!$lang) xwSetProperty($properties,'language',xwArticleType($title,$text));
    

    xwReduceTransformStack($text,$properties,$title,'data','xwXmlWikiParse/preParse/data'); $tmp = $wgHooks['PreParser']; $wgHooks['PreParser'] = array(); $parser = new Parser; $output = $parser->parse($text,$wgTitle,ParserOptions::newFromUser($wgUser),true,false); $wgHooks['PreParser'] = $tmp; $text = $output->getText(); $text = preg_replace('/@{4}([^@]+?)@{4}/s',,$text); # HTML comments hack xwSetProperty($properties,'xpath:/properties:view','tree-view.php'); xwReduceTransformStack($text,$properties,$title,'view','xwXmlWikiParse/preParse/view'); return $text; }