Difference between revisions of "Xmlwiki.php"

From Organic Design wiki
m
 
(225 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<php>
+
{{legacy}}
<?php
+
<source lang="php">
 
# xmlWiki - MediaWiki XML Hack
 
# xmlWiki - MediaWiki XML Hack
# Nad - 2005-05-18
+
# Nad - Started: 2005-05-18
  
# Maybe only use XML transforms
+
# Exit if not included from index.php
# - use PHP via processing instructions
+
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!');
  
# AdminPage should be moved out to a separate file again
+
# Add the rest of the hooks
# - so that hack can still be unhooked even if it has fatal errors
+
$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;
 +
}
  
# DTD's
+
# System globals
# - using xmlwiki:document, xmlwiki:image etc is for validation purposes
+
$xwDebug = false;
# - xwTransformArticle is generic and cares only for xmlwiki:*
+
$xwMessages = array();
 +
$xwArticleCache = array();
 +
$xwStyleSheets = array();
 +
$xwTemplate = null;
 +
$xwParserHookCalled = false;
 +
$xwSkinHookCalled = false;
 +
$xwMsgToken = '<!--xwMsg-->';
 +
$xwCssToken = '<!--xwCss-->';
 +
$xwScript = $wgScript;
 +
$xwTransformID = 1;
  
# CSS STYLES
+
# User globals
# the are accessed via http, so their article url can be referred to locally in the html
+
$xwUserName = ucwords( $wgUser->mName );
# - maybe make the default request 'raw' and must specifically request 'view'
+
$xwAnonymous = ereg( "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", $xwUserName );
# - the default CSS's should deal with geshi colour/tab etc
+
$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']);
  
# LATER:
+
# Article globals
# allow XIncludes to build large docs from others
+
$xwArticleTitle = $wgTitle->getPrefixedText();
# allow docBook and DSSSL content
+
$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;
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
# Get default properties article
# SCRIPT CONTENTS
+
$xwDefaultProperties = $xwDefaultPropertiesXML = xwArticleContent( 'default-properties.xml', false );
 +
xwDomificateArticle( $xwDefaultProperties, 'defaults' );
  
# INIT
+
# Get user-system-article and extract users' groups from it if any
# - do admin and exit if running directly
+
$xwUserSYS = xwArticleContent( "sys:$xwUserName" );
# - include geshi
+
xwDomificateArticle( $xwUserSYS, "sys:$xwUserName" );
# - get wiki & request environment
+
$xwUserGroups = array_merge( $xwUserGroups, xwGetListByTagname( $xwUserSYS, 'groups' ) );
# - get article (as an object if possible)
 
# - get user-info
 
# - get object-specific info (perms, doc-root, doc-type)
 
# - build env (if action=raw, env is empty)
 
# INPUT
 
# - deny access if !writable
 
# - currently not doing anything else
 
# OUTPUT
 
# - trap MediaWiki output
 
# - extract links (not used yet)
 
# - apply security
 
# - transform for raw, view, preview
 
# - if saving, do publishing
 
# - render output & messages
 
# TRANSFORM
 
# - if article is an object,
 
#  - process transforms if doctype is one of ours,
 
#  - validate and apply XSLT if not
 
# - apply geshi on article
 
# TRANSFORMS
 
# - document transforms
 
# - image transforms
 
# - layout transforms
 
# - database table transforms
 
# - transform object to php-nav-tree
 
# FUNCTIONS
 
# - get article
 
# - etxract hrefs from text
 
# - message/log
 
# - geshi syntax highlighting
 
# ADMIN
 
# - enable/disable patch
 
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
# Read user-prefs
# INIT
+
xwGetProperty( $xwUserSYS, 'xpath:/user/debug', $xwDebug );
  
# If xmlwiki.php requested directly, return admin page
+
# Get article-meta-file and extract article-perms
if (eregi("xmlwiki\\.php", $_SERVER['SCRIPT_NAME'])) xwAdminPage();
+
if ( $xwIsSystem ) $xwArticleReadableBy = $xwArticleWritableBy = array('admin');
 
else {
 
else {
  
# Geshi syntax highlighting is called from xmlWiki and should be removed from LocalSettings.php
+
# Get xml:article (self if already xml:*)
require_once("extensions/GeshiSyntaxHighlight.php");
+
if ( $p = $xwIsProperties ? xwDomificateArticle( $xwArticle ) : xwArticleProperties( $xwArticleTitle ) )
  
# Otherwise set up xmlWiki global environment ready for input and output processing
+
# Start with default-properties and merge article props (so new transforms on top)
$xwMessages = array();
+
$xwArticleProperties =
$xwArticleCache = array();
+
'<?xml version="1.0" standalone="yes"?>
$xwPostTransforms = array('geshi', 'default');
+
<!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd">
 +
<properties/>';
 +
xwDomificateArticle( $xwArticleProperties, 'CreateProperties' );
 +
xwMergeDOM( $xwArticleProperties, $xwDefaultProperties );
 +
xwMergeDOM( $xwArticleProperties, $p );
  
$xwArticle = $wgArticle->getContent(false);
+
$xwArticleReadableBy = xwGetListByTagname( $xwArticleProperties, 'read' );
$xwArticleTitle = $wgTitle->getPrefixedURL();
+
$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');
  
$xwEdit = isset($_REQUEST['action'])&&($_REQUEST['action'] == 'edit');
+
# If no language specified in properties, guess from name and content
$xwView = (!isset($_REQUEST['action'])||(isset($_REQUEST['action'])&&($_REQUEST['action'] == 'view')));
+
if ( !xwGetProperty( $xwArticleProperties, 'language', $xwLanguage ) )
$xwRaw = isset($_REQUEST['action'])&&($_REQUEST['action'] == 'raw');
+
xwSetProperty( $xwArticleProperties, 'language', xwArticleType( $xwArticleTitle, $xwArticle ) );
$xwPreview = isset($_REQUEST['wpPreview']);
 
$xwSave = isset($_REQUEST['wpSave']);
 
$xwUserLinks = array();
 
$xwArticleLinks = array();
 
  
$xwUserName = $wgUser->mName;
+
# Set perms for this request
$xwUserArticle = xwArticleContent("User:$xwUserName");
+
if ($xwDebug) xwMessage('PERMISSIONS:','green');
$xwReadable = true;
+
if (in_array('admin', $xwUserGroups)) $xwIsAdmin = $xwReadable = $xwWritable = true;
$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));
 +
}
  
# Try to convert articles from XML to a DOM object
+
# Divert to access-denied article if not readable
xwDomificateArticle($xwArticle);
+
if (!$xwReadable) {
xwDomificateArticle($xwUserArticle);
+
$action = 'view';
 +
$xwSave = $xwEdit = false;
 +
}
  
# If successfully converted, get perms (?)
+
# Handle security for Move
if (is_object($xwArticle)) {
+
if (!$xwIsAdmin and ($target = $_REQUEST['target']) ) {
$xwUserGroups[] = $xwUserName;
+
if ($xwArticleProperties = new Article(Title::newFromText("xml:$target")))
$xwReadable = null;
+
$xwArticleProperties = $xwArticleProperties->getContentWithoutUsingSoManyDamnGlobals();
$xwWritable = null;
+
$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);
 
}
 
}
 +
}
 +
 +
# Apply init transforms
 +
xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'init', 'INIT-HOOK' );
  
}
 
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# INPUT
+
# INPUT HOOK
 
 
# No input processing for now
 
# - used later for custom edit forms (database table article will need it)
 
  
 
# Parse and process input from forms
 
# Parse and process input from forms
function xwProcessInput() {
+
function xwInputHook() {
  
global $xwArticle;
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST;
global $xwArticleTitle;
+
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle;
 +
global $xwUserGroups, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
 +
if ($xwDebug) xwMessage('INPUT-HOOK:','green');
  
global $xwSave;
+
# 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;
 +
}
  
global $xwReadable;
+
# Scan POST and apply any XPath inputs
global $xwWritable;
+
foreach ($_REQUEST as $query => $value) {
global $xwMessages;
+
if (ereg('^xpath.3A.+.3A', $query)) $query = urldecode( $query );
 +
if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, str_replace('&', '%26', $value));
 +
}
  
# If article is being updated,
+
# if saving a pseudo-namespace...
 
if ($xwSave) {
 
if ($xwSave) {
#global $_POST;
+
if ( $xwIsProperties || $xwIsSystem ) {
#if (!$xwReadable || !$xwWritable) die("I'm sorry Dave, I'm afraid I can't do that.");
+
# 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;
 +
}
 +
}
 +
}
 +
}
 
}
 
}
  
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# OUTPUT
+
# OUTPUT HOOK
  
# Generate new output to send back to browser
+
# Post-process and output article
function xwProcessOutput() {
+
function xwOutputHook() {
  
global $wgOut;
+
global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit, $action;
global $xwArticle;
+
if ($action == 'raw') return;
global $xwArticleTitle;
+
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
  
global $xwView;
+
# Activate the skin-hook and generate wiki's output
global $xwPreview;
+
$wgUser->setOption('skin', 'xwskin');
global $xwSave;
 
global $xwRaw;
 
global $xwUserLinks;
 
global $xwArticleLinks;
 
 
 
global $xwUserName;
 
global $xwUserArticle;
 
global $xwReadable;
 
global $xwWritable;
 
global $xwMessages;
 
 
 
# First trap Wiki output and extract content and links (should use $ver like Admin Page)
 
ob_start();
 
 
$wgOut->output();
 
$wgOut->output();
$out = ob_get_clean();
 
$xwUserLinks = preg_match('/id="p-personal"(.+?)<\/div>/s', $out, $m) ? xwExtractHrefs($m[1]) : array();
 
$xwArticleLinks = preg_match('/id="p-cactions"(.+?)<\/div>/s', $out, $m) ? xwExtractHrefs($m[1]) : array();
 
  
# Security
+
# Editing article
if (!$xwReadable) {
+
if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) {
# change output to access denied message if !readable
+
# 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<data></data>\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 (!$xwWritable) {
+
 
# remove edit link
+
if ( $xwDebug ) {
 +
global $xwParserHookCalled, $xwSkinHookCalled;
 +
if ( !$xwParserHookCalled ) xwMessage( 'PARSER-HOOK was not called', 'green' );
 +
if ( !$xwSkinHookCalled ) xwMessage( 'SKIN-HOOK was not called', 'green' );
 
}
 
}
  
# If raw, output consists only of transformed article
+
# Insert stylesheets and messages into output
if ($xwRaw) $out = xwTransformArticle();
+
xwReplaceTokens( $xwArticle );
 +
 
 +
# Permhack: remove private info if search result
 +
if ( isset($_REQUEST['search']) && !$xwIsAdmin ) $xwArticle = preg_replace(
 +
'/<li><a href.+?>(.+?)<\\/a>.+?<\\/li>/ise',
 +
'xwArticleAccess($1,"read")?$0:""',
 +
$xwArticle
 +
);
 +
 
 +
# Output final result
 +
print $xwArticle;
 +
}
 +
 
 +
 
 +
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# PRE-PARSER HOOK
  
# If normal view, article must appear within wiki content
+
function xwPreParserHook( &$text ) {
elseif ($xwView) {
+
global $xwArticleTitle, $xwArticle, $xwArticleProperties, $xwSave;
if (preg_match('/^(.+<!-- start content -->)(.+)(<!-- end content -->.+)$/ms', $out, $m)) {
+
global $xwParserHookCalled, $xwDebug, $xwReadable, $xwScript;
$out = xwTransformArticle();
+
if ( $xwDebug ) xwMessage( 'PREPARSER-HOOK:', 'green' );
} else xwMessage('xmlWiki couldn\'t match view-output!', 'red');
+
$xwParserHookCalled = true;
}
 
  
# If preview, article must appear in wiki content preserving wiki editing content
+
# Save clears all output, but we need to parse it for save-transforms
elseif ($xwPreview) {
+
if ( $xwSave ) $text = $_REQUEST['wpTextbox1'];
if (preg_match('/^(.+<!-- start content -->.+Remember.+saved!.+?<\\/p>.+?<p>)(.+?)(<\\/p>.+<!-- end content -->.+)$/s', $out, $m)) {
 
# We have to transform the content in wikiOutput not the article itself
 
$xwArticle = html_entity_decode($m[2]);
 
xwDomificateArticle($xwArticle);
 
$out = xwTransformArticle();
 
} else xwMessage('xmlWiki couldn\'t match preview-output!', 'red');
 
}
 
  
# If saving, check the publishing list
+
# If this text-fragment is our article, apply transforms
elseif (0&& $xwSave && is_object($xwArticle)) {
+
if ( $xwSave or is_object($xwArticle) or strncmp( $text, $xwArticle, 100 ) == 0 or strncmp( $text, xwArticleContent( $xwArticleTitle, false ), 100 ) == 0 ) {
$transformedArticle = xwTransformArticle();
+
if ( $xwDebug ) xwMessage( "Matching text-fragment intercepted." );
# Get publish list
+
if ( !$xwReadable ) {
$publist = $xwDocRoot->get_elements_by_tagname('publish');
+
xwMessage( 'Sorry, article not readable!', 'red' );
if (count($publist)) $publist = $publist[0]->dump_mem(true);
+
$text = $xwArticle = "<a href=\"$xwScript?title=Special:Userlogin\">Please Login</a>";
$publist = split(',', $publist);
+
return false;
# Publish each item
 
foreach ($publist as $url) {
 
if ($FH = fopen($url, 'a')) {
 
fwrite($FH, $transformedArticle);
 
fclose($FH);
 
xwMessage("Article published to $url");
 
} else xwMessage("Failed to publish to $url", 'red');
 
 
}
 
}
 +
# 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' );
else {
+
# Get language and return true back to parser if lang wasn't ours
# This is probably a special page
+
xwGetProperty( $xwArticleProperties, 'language', $language );
# - we'll just extract the content and do only post-default-transform
+
if ($xwDebug) xwMessage( 'Content language determined as '.($language ? strtoupper($language) : 'WIKI') );
if (preg_match('/^(.+<!-- start content -->)(.+)(<!-- end content -->.+)$/ms', $out, $m))  
+
return $language == '';
$out = xwTransform_default($m[2]);
+
}
else xwMessage('xmlWiki couldn\'t match special-output!', 'red');
+
 
}
+
 
 +
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# POST-PARSER HOOK
 +
# - this hook was not implemented or had been removed,
 +
#  I've put in on the official ArticleSaveComplete hook for now
 +
#  - 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' );
  
# Add messages to output unless 'raw' requested
+
# Now apply the save-stack
if (!$xwRaw) $out = preg_replace('/<!-- start content -->/', '<!-- start content -->'.join("\n",$xwMessages), $out);
+
$dummy = "";
 +
xwReduceTransformStack( $dummy, $xwArticleProperties, $xwArticleTitle, 'save', 'POSTPARSER-HOOK' );
  
# Render final output and messages
+
# Output should be empty for save
xwMessage('Rendering output');
+
#$text = '';
print $out;
 
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# TRANSFORM
+
# SKIN HOOK
 +
 
 +
# If attempted xml encountered, try to domificate and transform
 +
# - Raw requests don't get here
 +
function xwSkinHook( &$tmpl ) {
 +
 
 +
global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw;
 +
global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;
  
# Transform an article object
+
# Extract article from existing template structure
# - non objects (strings) can't be transformed because they can't store any transform properties
+
if ( $xwDebug ) xwMessage( 'SKIN-HOOK:', 'green' );
# - but the environment post-transforms (incl. geshi) will be applied
+
$xwSkinHookCalled = true;
function xwTransformArticle($article = false) {
+
$xwTemplate = $tmpl;
 +
if ( $xwReadable ) $xwArticle = $xwTemplate->data['bodytext'];
  
global $xwArticle;
+
# Apply view-transforms
global $xwRaw;
+
# - "default-skin.php" builds XML output structure
global $xwPostTransforms;
+
# - "default-skin.xslt" transforms XML to HTML with table-layout
 +
# - "default-skin.css" transforms HTML with design
 +
xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK' );
 +
}
  
# Use global current article if none passed
+
if ($article === false) $article = $xwArticle;
+
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# ARTICLE FUNCTIONS
 +
# - these all use super-globals since they can be called from within evals
  
if (is_object($article)) {
+
# Return true if passed title readable/writable by current user
+
function xwArticleAccess( $title, $perm = 'read' ) {
# Get doc info
+
global $xwIsAdmin, $xwUserGroups, $xwDefaultProperties;
$docType = $article->doctype();
+
if ( !$access = $xwIsAdmin ) {
$docType = $docType->name;
+
$properties = xwArticleProperties( $title );
$docRoot = $article->document_element();  
+
xwMergeDOM( $properties, $xwDefaultProperties );
xwMessage("Transforming doctype \"$docType\"");
+
$access = xwGetListByTagname( $properties, $perm ) + array( 'anyone' );
 +
$access = count( array_intersect( $access, $xwUserGroups ) );
 +
}
 +
return $access > 0;
 +
}
  
# If it's one of ours,
+
# Retreive wiki-article as raw text
if (ereg('^xmlwiki:', $docType)) {
+
function xwArticleContent( $articleTitle, $secure = true ) {
# Get the documents transforms
+
global $xwArticleCache, $xwDebug, $xwIsAdmin, $xwUserGroups;
# - the first transforms adjust the document while its still a DOM object
+
if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue');
# - but at some point it would normally transform to text,
 
# - it may then have some further text-based transforms
 
xwMessage('Applying object\'s transforms...');
 
foreach ($docRoot->get_elements_by_tagname('transform') as $tElement) {
 
# Get transform name
 
$tName = $tElement->get_content();
 
# If name(), try to execute from a local function declaration
 
# - probably this is not needed soon, only accept article-transforms
 
if (ereg('^(.+)\\(\\)$', $tName, $m)) {
 
$tName = $m[1];
 
if ($tResult = @call_user_func("xwTransform_$tName", $article)) $article = $tResult;
 
else xwMessage("Unknown transform \"$tName\"!", 'red');
 
}
 
else {
 
# Transform is not name(), so try getting transform-article-object
 
if ($tText = xwArticleContent($tName)) {
 
$tType = xwArticleType($tName, $tText);
 
if ($tType == 'php') {
 
}
 
elseif ($tType == 'xslt') {
 
}
 
elseif ($tType == 'css') {
 
}
 
}
 
else xwMessage("No such article \"$tName\"!", 'red');
 
}
 
}
 
#xwMessage(join(',',$xwTransforms));
 
# - add php-transform-articles,CSS's & XSLT's to $xwEnv
 
}
 
  
# If object hasn't been transformed into a string yet (or it isn't one of our kind)...
+
# Temp: quick fix for slack security model :-(
if (!is_string($article)) {
+
# - should combine article and properties into getContent()
xwMessage('Dumping article-object to string');
+
if ($secure and !$xwIsAdmin) {
# validate, report errors
+
if ($properties = new Article(Title::newFromText('xml:'.ucwords($articleTitle))))
# apply stylesheet/XSLT if xml asks for one,
+
$properties = $properties->getContentWithoutUsingSoManyDamnGlobals();
# Convert DOM back to XML
+
$readableBy = xwGetListByTagname(xwDomificateArticle($properties), 'read') + array('anyone');
$article = $article->dump_mem(true);
+
if (!count(array_intersect($readableBy, $xwUserGroups))) {
 +
xwMessage("Sorry article \"$articleTitle\" not readable!",'red');
 +
return "[[Special:Userlogin|Please Login]]";
 
}
 
}
 
}
 
}
  
# Apply post transforms unless 'raw' requested
+
# Return with results if already cached
xwMessage('Applying xmlWiki post-transforms...');
+
if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle];
if (!$xwRaw) foreach ($xwPostTransforms as $tName)
+
# Get wiki article content (or use global if no article passed)
if ($tResult = @call_user_func("xwTransform_$tName", $article)) $article = $tResult;
+
# - using getContentWithoutUsingSoManyDamnGlobals() because getContent() fucks up nested-article-reads
else xwMessage("Unknown post-transform \"$tName\"!", 'red');
+
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;
 
return $article;
 
}
 
}
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
# Return properties object from title
# TRANSFORMS
+
# - returns self if already a properties article
# - these transforms are only temporarily here - they should be articles
+
function xwArticleProperties( $articleTitle, $xmlself = true ) {
# - we should use DTD's for each kind: document, image, layout, table
+
global $xwDebug;
 
+
if ($xwDebug) xwMessage("xwArticleProperties(\"$articleTitle\")",'blue');
# This is the default postTransform which surrounds the page
+
$id = ( $xmlself && eregi( '^xml:', $articleTitle ) ) ? $articleTitle : 'xml:'.ucfirst($articleTitle);
# - most transforms do not reference the global-current-article like this one
+
if ( !$properties = xwArticleContent( $id, false ) ) $properties =
# - this is a special transform, because it affects all pages, even special pages
+
'<?xml version="1.0" standalone="yes"?>
function xwTransform_default($article) {
+
<!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd">
xwMessage("Hi defaultTransform here :-)");
+
<properties/>';
global $xwArticle;
+
return xwDomificateArticle( $properties, $id );
global $xwArticleTitle;
 
global $xwUserLinks;
 
global $xwArticleLinks;
 
return "<html><head><title>xmlWiki - $xwArticleTitle</title></head><body bgcolor=#cccccc>
 
<table border=1 width=100% cellpadding=0 cellspacing=0>
 
<tr><td colspan=3 valign=top>".join('<br>',$xwArticleLinks)."<td align=center><table border=0 width=75%>
 
<tr><td align=center>
 
<a href=\"./index.php/xmlWiki\"><img src=\"/wiki/xmlwiki.jpg\" border=0 alt=\"xmlWiki\"></a>
 
<tr><td align=center bgcolor=#003366>
 
<b><font size=5 color=white face=helvetica>$xwArticleTitle</font></b>
 
<tr><td><!-- start content -->$article<!-- end content --></table></table></body></html>";
 
 
}
 
}
  
function xwTransform_document_xxx() {
+
# Decide kind of article from content and title
# documents are built from parts,
+
function xwArticleType( $title, $article ) {
# the parts are a tree in its body
+
if ( is_object($article) ) return 'xml';
# includes 'edit' link for each (like section, and also like table)
+
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 '';
 
}
 
}
  
# A document object is finalised by transforming it to its body's text
+
# Convert passed article to a DOM object
function xwTransform_document_render() {
+
# - Article is unchanged if not valid XML
global $xwArticle;
+
function xwDomificateArticle(&$article, $id = '') {
$root = $xwArticle->document_element();  
+
global $xwDebug;
$body = $root->get_elements_by_tagname('body');  
+
if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue');
if (count($body)) $xwArticle = $body[0]->dump_mem(true);
+
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;
 
}
 
}
  
function xwTransform_layout_xxx() {
+
# Reduce article to a string if it's a DOM object
# for layout container style article
+
# - 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;
 
}
 
}
  
function xwTransform_image_xxx() {
 
# object contains properties for loading and transforming an image
 
# image needs no body
 
 
  
# Get the global image on which imageTransforms work
+
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# TRANSFORM FUNCTIONS
  
global $xwArticleImage;
+
# 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');
 +
}
 
}
 
}
  
# Converts the in-memory-working image into article content
+
# Apply a transform
function xwTransform_image_render() {
+
function xwApplyTransform($transform, &$article, &$properties, &$title, $event) {
global $xwArticle;
+
global $xwDebug;
global $xwArticleTitle;
+
if ( $transform ) {
global $xwArticleImage;
+
if ( $tText = xwArticleContent($transform) ) {
global $xwRaw;
+
# Get transform language (take a guess if none specified)
if ($xwRaw) {
+
$tProperties = xwArticleProperties($transform);
# render article directly if 'raw' request
+
xwGetProperty( $tProperties, 'language', $tLang );
imagePng($xwArticleImage);
+
if ( !$tLang ) $tLang = xwArticleType( $transform, $tText );
# Clear article or it will render as xml
+
if ( $tLang == 'xslt' ) xwApplyXSLT( $article, $title, $properties, $tText, $transform );
$xwArticle = '';
+
elseif ( $tLang == 'php' ) xwApplyPHP( $article, $tText, $transform, $title, $properties, $tProperties, $event );
# set header to image
+
elseif ( $tLang == 'css' ) xwApplyCSS( $title, $transform );
header("Content-type: image/png");
+
elseif ( $tLang == 'xml' ) xwMergeDOM( $properties, xwDomificateArticle( $tText, $transform ), $title, $transform );
 +
}
 +
else xwMessage( "Unknown transform \"$transform\" attached to \"$event\" event!", 'red' );
 
}
 
}
else {
+
}
# the raw image is not being requested, render image in-page with info instead
+
 
$xwArticle = "<img src=\"$xwArticleTitle?action=raw\" alt=\"$xwArticleTitle\"/>";
+
# 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();
 
}
 
}
  
# A table object is an interface to a database table
+
# Append CSS stylesheet link to output
# - this can't be implimented fully until the 'edit' hook is done
+
function xwApplyCSS($title, $tName) {
# - the query result is a list in document body, so any normal document transforms can follow it
+
global $xwDebug, $xwStyleSheets;
function xwTransform_table_xxx() {
+
if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue');
# - view lists the table/result with links to edit each (like a normal composition-doc)
+
$xwStyleSheets[] = $tName;
# - also form for create/edit/edlete
+
}
# - result rows are in document body (just like a content-tree in a document would be)
 
 
# NODE-LIKE-DB
 
  
# - if all tables have GUID for pri-key,
+
# Apply XSLT to article-dom
# - and all have time/duration (or cycle?),
+
# - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
# - and we have master list of cols-to-table
+
# - if output-method is html, article will get stringified and language updated
# - then we could create a meta-query (resulting in pri-key-list of all matches)
+
function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) {
+
global $xwDebug, $xwStyleSheets;
}
+
if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue');
+
if (is_object($article)) {
# Transform the body content to a xtml/javascript tree
+
ob_start();
function xwTransform_tree() {
+
if ($tObject = domxml_xslt_stylesheet($xslt)) {
# import php-tree-component
+
$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;
 
}
 
}
  
# Include Geshi syntax highlighting
 
# - Remember, Geshi should be removed from LocalSettings.php
 
function xwTransform_geshi($article) {
 
xwMessage("Hi, Geshi here, sorry I'm not currently functional.");
 
return $article;
 
 
global $xwArticle;
 
global $xwArticleTitle;
 
  
# First check for entire article highlighting
+
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# DOM FUNCTIONS
  
# Highlight entire article if xwLang has been set
+
# Return result set of an XPath query on passed DOM-object
if ($xwLang) {
+
function xwXPathQuery(&$dom, $query) {
$xwGeshi = new GeSHi($xwArticle, $xwLang, "extensions/geshi/geshi");
+
$result = array();
$xwGeshi->set_tab_width(4);
+
if (is_object($dom)) {
$geshi->set_keywords_style(1, 'font-weight: 100;', true);
+
global $xwDebug;
#$geshi->set_comments_style
+
if ($xwDebug) xwMessage("xwXPathQuery( $query )",'blue');
#$geshi->set_escape_char_style
+
ob_start();
#$geshi->set_brackets_style
+
if (php(4)) {
#$geshi->set_strings_style
+
$context = $dom->xpath_new_context();  
#$geshi->set_numbers_style
+
if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset;
#$geshi->set_methods_style
+
elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red');
#$geshi->set_symbols_style
+
}
#$geshi->set_regexps_style
+
else if ($xpath = new DOMXPath($dom)) $result = $xpath->query($query);
#$geshi->set_script_style
+
xwExtractMessages();
#$xwArticle = $xwGeshi->parse_code();
+
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;
 
}
 
}
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
# - Syntax:  xpath:XPathQuery:[[@]node][+] = value
# FUNCTIONS
+
# - if "node" exists, then a new element (or att if "@node") is created for the value
 +
# - 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;
 +
}
  
# Retreive wiki-article
+
# Return array of comma-separated-items in element content
function xwArticleContent($articleTitle) {
+
# - if more than one element all are split and appended into the list
global $xwArticleCache;
+
function xwGetListByTagname(&$xml, $tag) {
# Return with results if already cached
+
$list = array();
if (isset($xwArticleCache[$articleTitle]))
+
if (is_object($xml)) {
return $xwArticleCache[$articleTitle];
+
if (php(4)) {
# Get wiki article content (or use global if no article passed)
+
$root = $xml->document_element();  
if ($article = new Article(Title::newFromText($articleTitle)))
+
foreach ($root->get_elements_by_tagname($tag) as $element)
$article = $article->getContent(false);
+
if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv));
if ($article == '(There is currently no text in this page)')
+
}
return false; # Return without caching
+
else {
if ($articleTitle) $xwArticleCache[$articleTitle] = $article;
+
foreach ($xml->documentElement->getElementsByTagname($tag) as $element)
return $article;
+
if ($csv = $element->nodeValue) $list = array_merge($list, split(',', $csv));
 +
}
 +
}
 +
return $list;
 
}
 
}
  
# Decide kind of article from content and title
+
# Merge the two passed DOM objects
function xwArticleType($title, $article) {
+
# - appends, does not overwrite
# Name based matches
+
function xwMergeDOM(&$dom1, &$dom2, $id1 = 'dom1', $id2 = 'dom2') {
if (ereg('\\.php$', $title)) return 'php';
+
global $xwDebug;
if (ereg('\\.css$', $title)) return 'css';
+
# Return if first DOM not a valid properties article
if (ereg('\\.as$', $title)) return 'actionscript';
+
if (!is_object($dom1)) return false;
if (ereg('\\.py$', $title)) return 'python';
+
if (php(4)) $docType = $dom1->doctype(); else $docType = $dom1->doctype;
if (ereg('\\.java$', $title)) return 'java';
+
if (!ereg('^xmlwiki:properties', $docType->name)) {
if (ereg('\\.(cpp)|(h)$', $title)) return 'cpp';
+
if ($xwDebug) xwMessage('xwMergeDOM: First parameter is not an xmlwiki:properties object!','red');
if (ereg('\\.js$', $title)) return 'javascript';
+
return false;
if (ereg('\\.css$', $title)) return 'css';
+
}
# Content based matches
+
# Return DOM1 if DOM2 not valid properties
if (preg_match('/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/', $article)) return 'xslt';
+
if (!is_object($dom2)) return $dom1;
if (ereg('^<\\?xml', $article)) return 'xml';
+
if (php(4)) $docType = $dom2->doctype(); else $docType = $dom2->doctype;
if (ereg('^<\\?html', $article)) return 'html4strict';
+
if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1;
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
+
# Both are ok, perform the merge
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
+
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;
 
}
 
}
  
# Convert passed article to a DOM object
+
# Remove all occourences of a certain element
# - Article is unchanged if not valid XML
+
function xwRemoveElement( &$properties, $element ) {
function xwDomificateArticle(&$article) {
+
$root = $properties->document_element();
ob_start();
+
if (php(4)) {
if ($dom = domxml_open_mem($article)) $article = $dom else {
+
while ( count($tNodes = $root->get_elements_by_tagname($element)) ) $tNodes[0]->unlink_node();
# Could not convert, extract error messages from output
+
}
xwMessage("Domification faled:", 'red');
+
else {
$err = preg_replace("/<.+?>/", "", ob_get_contents());
+
while ( ($tNodes = $root->getElementsByTagname($element)) && $tNodes->length ) {
$err = preg_replace('/ in .+? on line.+?[0-9]+/', '', $err);
+
$tNode = $tNodes->item(0);
$err = preg_replace('/Warning.+?\\(\\): /', '', $err);
+
$tNode->parentNode->removeChild( $tNode );
foreach (split("\n", $err) as $msg) if ($msg) xwMessage($msg, 'red');
+
}
 
}
 
}
ob_end_clean();
 
 
}
 
}
  
# Return a list of the hrefs found in the passed content
+
# ---------------------------------------------------------------------------------------------------------------------- #
function xwExtractHrefs($content) {
+
# UTILITY FUNCTIONS
preg_match_all('/(<a.+?<\/a>)/', $content, $match);
+
 
return $match[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');
 +
}
 
}
 
}
 
+
 
# Add message to queue
 
# Add message to queue
function xwMessage($msg, $col = 'blue') {
+
function xwMessage($msg, $col = '#000080') {
 
global $xwMessages;
 
global $xwMessages;
return $xwMessages[] = "<div class=\"xwMessage\"><font color=\"$col\">$msg</font></div>\n";
+
$msg = htmlentities($msg);
 +
return $xwMessages[] = "<font color=\"$col\">$msg</font>";
 
}
 
}
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
function xwReplaceTokens(&$article, $embed = false) {
# ADMIN
+
global $xwStyleSheets, $xwMessages, $xwCssToken, $xwMsgToken, $xwScript;
function xwAdminPage() {
 
  
?><html><head><title>xmlWiki Admin Page</title></head><body bgcolor=#cccccc>
+
# Stylesheets
<table border=0 width=100% cellpadding=0 cellspacing=0>
+
$stylesheets = '';
<tr><td align=center><table border=0 width=30%>
+
foreach ($xwStyleSheets as $title)
<tr><td align=center>
+
if ($embed) $stylesheets .= '<style type="text/css" media="screen">'.xwArticleContent($title).'</style>';
<a href="./index.php/xmlWiki"><img src="./xmlwiki.jpg" border=0 alt="xmlWiki"></a>
+
else $stylesheets = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$xwScript?title=$title&action=raw&ctype=text/css\" />\n$stylesheets";
<tr><td align=center bgcolor=#003366>
+
$article = str_replace($xwCssToken, $stylesheets, $article);
<b><font size=5 color=white face=helvetica>xmlWiki Admin Page</font></b>
 
</table></table><br><br><br><br><?
 
  
# back up index.php if doesn't already exist
+
# Messages
if (!file_exists('./index.bak')) {
+
if (count($xwMessages)) {
if (!copy('./index.bak', './index.php')) {
+
$msg = '<div class="portlet" id="p-xwmessages"><h5>There are Messages</h5></div>';
print xwMessage('Could not back up index.php!', '#d00000');
+
$msg .= '<div class="xwmessage"><ul><li>'.join("</li><li>", $xwMessages).'</li></ul></div>';
exit;
+
$article = str_replace($xwMsgToken, $msg, $article);
}
 
 
}
 
}
if (array_key_exists('enable', $_GET)) {
+
}
# Replace from backup before enabling
+
 
if (!copy('./index.bak', './index.php'))
+
# Print list of passed object's methods and properties
print xwMessage('Could not copy index.bak over index.php', '#d00000');
+
function xwCheckoutObject($obj) {
require_once('./Version.php');
+
if (is_object($obj)) {
$ver = $wgSoftwareRevision;
+
print strtoupper("<br>$obj properties:<br>");
# Matches for patch-points/MediaWiki versions
+
foreach (get_object_vars($obj) as $k=>$v) print htmlentities("$k => $v")."<br>";
$requireMatch['1002'] = "/^wfProfileOut\\( *'main-misc-setup' *\\);$/m";
+
print strtoupper("<br>$obj methods:<br>");
$inputMatch['1002'] = "/^wfProfileIn\\( *'main-action' *\\);$/m";
+
foreach (get_class_methods($obj) as $k=>$v) print "$v<br>";
$outputMatch['1002'] = "/\\\$wgOut->output\\(\\);/m";
 
# Apply patches
 
if (preg_match($requireMatch[$ver], $file = join('', file('./index.php')))
 
&&  preg_match($inputMatch[$ver], $file)
 
&&  preg_match($outputMatch[$ver], $file)) {
 
$file = preg_replace($requireMatch[$ver], "wfProfileOut( 'main-misc-setup' );\nrequire_once( './xmlwiki.php' );", $file);
 
$file = preg_replace($inputMatch[$ver], "xwProcessInput();\nwfProfileIn( 'main-action' );", $file);
 
$file = preg_replace($outputMatch[$ver], "xwProcessOutput();", $file);
 
if ($handle = fopen('./index.php', 'w')) {
 
fwrite($handle, $file);
 
fclose($handle);
 
} else print xwMessage('Could not open index.php for writing!', '#d00000');
 
} else print xwMessage('Could not match content of index.php script!', '#d00000');
 
 
}
 
}
elseif (array_key_exists('disable', $_GET)) {
+
if (is_array($obj)) {
# Replace from backup
+
print strtoupper("<br>Array content:<br>");
if (!copy('./index.bak', './index.php'))
+
foreach ($obj as $k=>$v) print "$k => $v<br>";
print xwMessage('Could not copy index.bak over index.php', '#d00000');
 
 
}
 
}
# Print current state
+
die;
if (filesize('./index.php') == filesize('./index.bak'))
+
}
print xwMessage('xmlWiki is currently disabled. [ <a href="./xmlwiki.php?enable">enable</a> ]', '#003366');
+
 
else print xwMessage('xmlWiki is currently enabled. [ <a href="./xmlwiki.php?disable">disable</a> ]', '#003366');
+
# Return true if PHP major version equals passed int
+
# - needed due to different DOM implementation on 4 and 5 (and none on 3)
 +
function php($ver) { return substr(phpversion(),0,1) == "$ver"; }
 +
 
 +
# 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);
 +
}
  
?><br><br><br><table border=0 width=100% cellpadding=0 cellspacing=0>
+
# Parse wikitext string and return HTML string
<tr><td align=center colspan=3><table border=0 width=50%>
+
function xwWikiParse(&$text) {
<td align=right valign=top><a href="http://w3c.org/"><img src="./xhtml.jpg" alt="XHTML1.0" border=0 /></a>
+
global $wgHooks,$wgTitle,$wgUser;
<td align=right valign=top><a href="http://www.php.net/"><img src="./php.jpg" alt="PHP" border=0 /></a>
+
$tmp = $wgHooks['PreParser'];
<td align=right valign=top><a href="http://www.mediawiki.org/"><img src="./mediawiki.jpg" alt="MediaWiki" border=0 /></a>
+
$wgHooks['PreParser'] = array();
</table></table><br><br></body></html><?
+
$parser = new Parser;
 +
$output = $parser->parse($text,$wgTitle,ParserOptions::newFromUser($wgUser),true,false);
 +
$wgHooks['PreParser'] = $tmp;
 +
return $output->getText();
 
}
 
}
  
?>
+
# Parse an XmlWiki article and return resulting HTML
</php>
+
function xwXmlWikiParse($title) {
 +
global $wgTitle,$wgHooks,$wgUser,$xwUserName,$xwAnonymous;
 +
$bookmarks = $xwAnonymous ? "Bookmarks:Default" : "Bookmarks:$xwUserName";
 +
$text = str_replace('{{BOOKMARKS}}',$bookmarks,xwArticleContent($title)); # Hack so that {{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','<!--$1-->',$text); # HTML comments hack
 +
xwSetProperty($properties,'xpath:/properties:view','tree-view.php');
 +
xwReduceTransformStack($text,$properties,$title,'view','xwXmlWikiParse/preParse/view');
 +
return $text;
 +
}
 +
</source>

Latest revision as of 22:18, 20 November 2019

Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, now this page is for historic record only.
# xmlWiki - MediaWiki XML Hack
# Nad - Started: 2005-05-18

# 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!');

# 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;
	}

# System globals
$xwDebug				= false;
$xwMessages				= array();
$xwArticleCache			= array();
$xwStyleSheets			= array();
$xwTemplate				= null;
$xwParserHookCalled		= false;
$xwSkinHookCalled		= false;
$xwMsgToken				= '<!--xwMsg-->';
$xwCssToken				= '<!--xwCss-->';
$xwScript				= $wgScript;
$xwTransformID			= 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']);

# 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;

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

# 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' ) );

# Read user-prefs
xwGetProperty( $xwUserSYS, 'xpath:/user/debug', $xwDebug );

# 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');

# If no language specified in properties, guess from name and content
if ( !xwGetProperty( $xwArticleProperties, 'language', $xwLanguage ) )
	xwSetProperty( $xwArticleProperties, 'language', xwArticleType( $xwArticleTitle, $xwArticle ) );

# 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));
	}

# Divert to access-denied article if not readable
if (!$xwReadable) {
	$action = 'view';
	$xwSave = $xwEdit = false;
	}

# 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);
		}
	}

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


# ---------------------------------------------------------------------------------------------------------------------- #
# INPUT HOOK

# 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;
						}
					}
				}
			}
		}

	}


# ---------------------------------------------------------------------------------------------------------------------- #
# OUTPUT HOOK

# 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<data></data>\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(
		'/<li><a href.+?>(.+?)<\\/a>.+?<\\/li>/ise',
		'xwArticleAccess($1,"read")?$0:""',
		$xwArticle
		);

	# Output final result
	print $xwArticle;
	}


# ---------------------------------------------------------------------------------------------------------------------- #
# 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 == '';
	}


# ---------------------------------------------------------------------------------------------------------------------- #
# POST-PARSER HOOK
# - this hook was not implemented or had been removed,
#   I've put in on the official ArticleSaveComplete hook for now
#   - 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 = '';
	}


# ---------------------------------------------------------------------------------------------------------------------- #
# SKIN HOOK

# If attempted xml encountered, try to domificate and transform
# - 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' );
	}

	
# ---------------------------------------------------------------------------------------------------------------------- #
# ARTICLE FUNCTIONS
# - these all use super-globals since they can be called from within evals

# 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;
	}

# 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 "[[Special:Userlogin|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;
	}

# Return properties object from title
# - 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 );
	}

# 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 '';
	}

# Convert passed article to a DOM object
# - 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;
	}

# Reduce article to a string if it's a DOM object
# - 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;
	}


# ---------------------------------------------------------------------------------------------------------------------- #
# TRANSFORM FUNCTIONS

# 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');
		}
	}

# 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' );
		}
	}

# 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();
	}

# Append CSS stylesheet link to output
function xwApplyCSS($title, $tName) {
	global $xwDebug, $xwStyleSheets;
	if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue');
	$xwStyleSheets[] = $tName;
	}

# Apply XSLT to article-dom
# - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
# - 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;
	}


# ---------------------------------------------------------------------------------------------------------------------- #
# DOM FUNCTIONS

# 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;
	}

# - Syntax:  xpath:XPathQuery:[[@]node][+] = value
# - if "node" exists, then a new element (or att if "@node") is created for the value
# - 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;
	}

# Return array of comma-separated-items in element content
# - 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;
	}

# Merge the two passed DOM objects
# - 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;
	}

# 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 );
			}
		}
	}

# ---------------------------------------------------------------------------------------------------------------------- #
# UTILITY FUNCTIONS

# 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');
		}
	}
	
# Add message to queue
function xwMessage($msg, $col = '#000080') {
	global $xwMessages;
	$msg = htmlentities($msg);
	return $xwMessages[] = "<font color=\"$col\">$msg</font>";
	}

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 = '<div class="portlet" id="p-xwmessages"><h5>There are Messages</h5></div>';
		$msg .= '<div class="xwmessage"><ul><li>'.join("</li><li>", $xwMessages).'</li></ul></div>';
		$article = str_replace($xwMsgToken, $msg, $article);
		}
	}

# Print list of passed object's methods and properties
function xwCheckoutObject($obj) {
	if (is_object($obj)) {
		print strtoupper("<br>$obj properties:<br>");
		foreach (get_object_vars($obj) as $k=>$v) print htmlentities("$k => $v")."<br>";
		print strtoupper("<br>$obj methods:<br>");
		foreach (get_class_methods($obj) as $k=>$v) print "$v<br>";
		}
	if (is_array($obj)) {
		print strtoupper("<br>Array content:<br>");
		foreach ($obj as $k=>$v) print "$k => $v<br>";
		}
	die;
	}

# Return true if PHP major version equals passed int
# - needed due to different DOM implementation on 4 and 5 (and none on 3)
function php($ver) { return substr(phpversion(),0,1) == "$ver"; }

# 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);
	}

# 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();
	}

# 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('{{BOOKMARKS}}',$bookmarks,xwArticleContent($title)); # Hack so that {{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','<!--$1-->',$text); # HTML comments hack
	xwSetProperty($properties,'xpath:/properties:view','tree-view.php');
	xwReduceTransformStack($text,$properties,$title,'view','xwXmlWikiParse/preParse/view');
	return $text;
	}