Difference between revisions of "Xmlwiki.php"

From Organic Design wiki
m
 
(237 intermediate revisions by 8 users not shown)
Line 1: Line 1:
<pre>
+
{{legacy}}
<?php
+
<source lang="php">
 
# xmlWiki - MediaWiki XML Hack
 
# xmlWiki - MediaWiki XML Hack
# Nad - 2005-05-18
+
# Nad - Started: 2005-05-18
#
 
# Output Hook: Transforms output generated by MediaWiki and
 
#
 
# - Adds basic content-management, permissions and publishing to the MediaWiki
 
# - All articles are XML
 
# - Impliment by calling this script directly in browser
 
  
# FOR NOW:
+
# Exit if not included from index.php
# - only change rendering, not editing
+
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!');
# - rendering an xwArticle,
+
if (php(3)) die('xmlWiki cannot run on less than PHP4!');
#  - compiles php-transform-articles,CSS's & XSLT's from env+doc (the result of each becomes the source of the next)
 
#  - execute php-transform-articles & XSLT's in order (CSS's are left to the end)
 
#  - returns html-description with a CSS containing includes to CSS list
 
#  - PAGE LAYOUT is just a php-transform-article which SURROUNDS the current DOM with raw layout
 
#    - layouts would come with a separate collection of CSS's
 
#    - CSS's need to themselves be collections of colours, headings etc
 
  
# LATER:
+
# Add the rest of the hooks
# use XInclude to build large docs from others
+
$wgHooks['PreParser'][] = 'xwPreParserHook';
# use docBook or DSSSL
+
$wgHooks['ParserBeforeStrip'][] = 'xwPreParserHook';
# <? is a processing instruction
+
$wgHooks['ArticleSaveComplete'][] = 'xwPostParserHook';
# eg. <?xml-stylesheet href="person.css" type="text/css"?>  or  <?php...
+
$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' );
  
# INIT
 
# - do admin and exit if running directly
 
# - get wiki/request env
 
# - get article
 
# - permissions
 
# - build env (if action=raw, env is empty but a header is added)
 
# INPUT
 
# - validate xml and report error or,
 
# - process form-input (backslashes etc if necessary)
 
# OUTPUT
 
# - trap MediaWiki output
 
# - extract content and links
 
# - if view/preview/raw, do transform:
 
#  - if docType is an xwArticle, see above
 
#  - replace view/preview with transformed article
 
# - render output
 
# - if saving, foreach in publish-list,
 
#  - do transform but with specified env
 
#  - publish
 
# FUNCTION
 
# - get article as dom
 
# - process dom-article (pass PT,XSLT list and DOM-article, returns DOM-result)
 
# - etxract hrefs from text
 
# - message
 
# ADMIN
 
# - enable/disable patch
 
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# INIT
+
# INPUT HOOK
  
# If xmlwiki.php requested directly, return admin page
+
# Parse and process input from forms
if (eregi("xmlwiki\\.php", $_SERVER['SCRIPT_NAME'])) xwAdminPage();
+
function xwInputHook() {
else {
 
  
# Otherwise set up xmlWiki global environment ready for input and output processing
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST;
$xwArticleCache = array();
+
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle;
$xwEditingArticle = isset($_REQUEST['action'])&&($_REQUEST['action'] == 'edit');
+
global $xwUserGroups, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
$xwEditingXML = isset($_REQUEST['action'])&&($_REQUEST['action'] == 'xwXML');
+
if ($xwDebug) xwMessage('INPUT-HOOK:','green');
$xwViewingArticle = null; # preview too
 
$xwSavingArticle = isset($_REQUEST['wpSave']);
 
$xwSpecialArticle = null; # do not treat content as xml
 
$xwArticleTitle = $wgTitle->mTextform;
 
$xwArticleDOM = xwGetArticle($xwArticleTitle); # DOM object of wiki article
 
$xwUserName = $wgUser->mName;
 
$xwUserDOM = xwGetArticle("User:$xwUserName");
 
$xwUserGroups = null; # Groups this user belongs to (incl. self)
 
  
# Get perms for this article
+
# If not writable, change action to view
$xwReadable = null; # True if this article is readable by this user
+
if ( !$xwWritable && !in_array($action, array('view','history','raw')) ) {
$xwWritable = null; # True if this article is writeable by this user
+
xwMessage('Sorry, article not writable, action changed to "view".', 'red');
# Get groups this user belongs to from users' special-article
+
$action = 'view';
$xwUserGroups[] = $xwUserName; // This allows users to be specified in permissions-lists in addition to groups
+
$xwSave = $xwEdit = false;
 +
}
  
# Build transform environment (later from prefs, site etc)
+
# Scan POST and apply any XPath inputs
if ($action == 'raw') {
+
foreach ($_REQUEST as $query => $value) {
# Raw article is requested, so env is empty
+
if (ereg('^xpath.3A.+.3A', $query)) $query = urldecode( $query );
$xwEnv = array();
+
if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, str_replace('&', '%26', $value));
# TODO: work out what kind of content it is (only if its an xwArticle can this work)
 
# header("Content-type: application/pdf");
 
# header("Content-Disposition: attachment; filename=downloaded.pdf");
 
 
}
 
}
else {
+
 
# Create default environment (the name of another article)
+
# if saving a pseudo-namespace...
$xwEnv = array('defaultTemplate.php');
+
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;
 +
}
 +
}
 +
}
 +
}
 
}
 
}
+
 
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# INPUT
+
# OUTPUT HOOK
 +
 
 +
# Post-process and output article
 +
function xwOutputHook() {
  
# Parse and process input from forms
+
global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
# - the form can just post changes for xml
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit, $action;
function xwProcessInput() {
+
if ($action == 'raw') return;
 +
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
  
global $xwEditingArticle;
+
# Activate the skin-hook and generate wiki's output
global $xwSavingArticle;
+
$wgUser->setOption('skin', 'xwskin');
global $xwReadable;
+
$wgOut->output();
global $xwWritable;
 
global $xwArticleDOM;
 
  
# If article is being updated,
+
# Editing article
if ($xwSavingArticle) {
+
if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) {
global $_POST;
+
# If editing an empty sys:article or xml:article, add default content
# If valid xml posted, replace articleDOM
+
if ($xwIsSystem) {
if ($inputDOM = @domxml_open_mem($_POST["xw.xml"])) $articleDOM = $inputDOM;
+
$xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n";
# later: parse POST updating articleDOM
+
$xwArticle .= "<!DOCTYPE xmlwiki:user SYSTEM \"xmlwiki-user.dtd\">\n";
foreach (preg_grep("/^xw\\.(.+)$/", array_keys($_POST)) as $key) {
+
$xwArticle .= "<user>\n\t<groups></groups>\n</user>\n".$m[2];
$inputPath = split('.', $key);
+
xwMessage('Note: There is currently no content for this system article, default content has been generated','red');
 
}
 
}
# If description posted, update
+
elseif ($xwIsProperties) {
if (isset($_POST["wpTextbox1"])) {
+
$a = xwArticleContent( str_replace( 'Xml:', '', $xwArticleTitle ) );
# write new description to articleDOM
+
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];
 
}
 
}
# Convert articleDOM to text and put into POST data for MediaWiki to process
 
$_POST["wpTextbox1"] = $articleDOM->dump_mem(false);
 
 
}
 
}
 +
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 == '';
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# OUTPUT
+
# 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' );
  
# Generate new output to send back to browser
+
# Apply view transforms first
function xwProcessOutput() {
+
#xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'view', 'POSTPARSER-HOOK' );
  
global $wgOut;
+
# Now apply the save-stack
+
$dummy = "";
global $xwEditingArticle;
+
xwReduceTransformStack( $dummy, $xwArticleProperties, $xwArticleTitle, 'save', 'POSTPARSER-HOOK' );
global $xwEditingXML;
+
 
global $xwViewingArticle;
+
# Output should be empty for save
global $xwSavingArticle;
+
#$text = '';
global $xwSpecialArticle;
+
}
global $xwArticleTitle;
+
 
global $xwArticleDOM;
+
 
global $xwUserName;
+
# ---------------------------------------------------------------------------------------------------------------------- #
global $xwUserDOM;
+
# SKIN HOOK
global $xwUserGroups;
 
global $xwReadable;
 
global $xwWritable;
 
  
$xwTransformedArticle = null;
+
# If attempted xml encountered, try to domificate and transform
$xwPublishList = array();
+
# - Raw requests don't get here
$xwOutput = '';
+
function xwSkinHook( &$tmpl ) {
  
# First trap Wiki output and extract content and links (should use $ver like Admin Page)
+
global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw;
ob_start();
+
global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;
$wgOut->output();
 
$xwWikiOutput = ob_get_clean();
 
$xwWikiContent = preg_match('/\<!-- start content -->(.+)<!-- end content -->/sm', $xwWikiOutput, $xwMatch)
 
? $xwMatch[1] : '';
 
$xwUserLinks = preg_match('/id="p-personal"(.+?)<\/div>/s', $xwWikiOutput, $xwMatch)
 
? xwExtractHrefs($xwMatch[1]) : array();
 
$xwArticleLinks = preg_match('/id="p-cactions"(.+?)<\/div>/s', $xwWikiOutput, $xwMatch)
 
? xwExtractHrefs($xwMatch[1]) : array();
 
$xwArticleLinks[] = "<a href=\"./index.php?title=$xwArticleTitle&action=xwXML\">xml</a>";
 
  
# Test output
+
# Extract article from existing template structure
?><center><table border cellpadding=0 cellspacing=0><?
+
if ( $xwDebug ) xwMessage( 'SKIN-HOOK:', 'green' );
print '<tr><td>'.join(' | ', $xwUserLinks);
+
$xwSkinHookCalled = true;
print '<tr><td>'.join(' | ', $xwArticleLinks);
+
$xwTemplate = $tmpl;
print '<tr><td>'.htmlentities($xwArticleDOM->dump_mem(true));
+
if ( $xwReadable ) $xwArticle = $xwTemplate->data['bodytext'];
print '<tr><td>'.$xwWikiContent;
 
?></table></center><?
 
  
# Transform
+
# Apply view-transforms
# - This env still needs to apply an outer layout etc
+
# - "default-skin.php" builds XML output structure
# - The local-env and the DTD determines transforming/rendering
+
# - "default-skin.xslt" transforms XML to HTML with table-layout
# - if DTD says its just text (or special page) render normal
+
# - "default-skin.css" transforms HTML with design
# - The DTD's need to work outside wiki, so we need to be able to specifiy lang in request
+
xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK' );
 +
}
  
# eg. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
#    <!DOCTYPE greeting SYSTEM "hello.dtd">
 
 
 
# - article/input-description/input-xml/input-tree-operation
+
# ---------------------------------------------------------------------------------------------------------------------- #
# NOTE: we need to handle edit/discuss/history/login/prefs...
+
# ARTICLE FUNCTIONS
if ($xwEditingXML) {
+
# - these all use super-globals since they can be called from within evals
# Editing XML, render textarea (later tree)
+
 
$xwOutput .= '\n<textarea name="xw.xml">'.$xwArticleDOM->dump_mem(true).'</textarea>\n';
+
# 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');
  
if ($xwEditingArticle || $xwSpecialArticle) {
+
# Temp: quick fix for slack security model :-(
# If not viewing output wiki content
+
# - should combine article and properties into getContent()
$xwOutput .= $wikiContent;
+
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]]";
 +
}
 
}
 
}
 
if ($xwViewingArticle) {
 
# render article in current context (transform article)
 
# - html, ....
 
  
# Apply stylesheets
+
# Return with results if already cached
$xwTransformedArticle = null;
+
if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle];
+
# Get wiki article content (or use global if no article passed)
# Replace output-buffer content with processed article
+
# - using getContentWithoutUsingSoManyDamnGlobals() because getContent() fucks up nested-article-reads
$xwOutput .= $xwTransformedArticle;
+
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;
 +
}
  
# Publish the article if it has just been updated
+
# Return properties object from title
if ($xwSavingArticle) {
+
# - returns self if already a properties article
foreach ($xwPublishList as $xwDestination) {
+
function xwArticleProperties( $articleTitle, $xmlself = true ) {
if ($xwHandle = fopen($xwDestination, 'a')) {
+
global $xwDebug;
fwrite($xwHandle, $xwTransformedArticle);
+
if ($xwDebug) xwMessage("xwArticleProperties(\"$articleTitle\")",'blue');
fclose($xwHandle);
+
$id = ( $xmlself && eregi( '^xml:', $articleTitle ) ) ? $articleTitle : 'xml:'.ucfirst($articleTitle);
$xwOutput .= xwMessage("Article published to $cmDestination", 'green');
+
if ( !$properties = xwArticleContent( $id, false ) ) $properties =
} else $xwOutput .= xwMessage("Failed to publish to $cmDestination", 'red');
+
'<?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;
 +
}
  
# Render output-buffer through page template
+
 
print $xwOutput;
+
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# 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
# FUNCTIONS
+
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' );
 +
}
 +
}
  
# Retreive XML wiki-article and return as a DOM object
+
# Apply PHP-transform if article perms are only writable by admin or dev
# - DOM articles are cached in $xwArticleCache for reuse in recursive processing
+
function xwApplyPHP( &$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event ) {
function xwGetArticle($articleTitle) {
+
global $xwDebug;
# Return with results if already cached
+
if ( $xwDebug ) xwMessage( "xwApplyPHP( $title , $tTitle )", 'blue' );
global $xwArticleCache;
+
# Get transform perms
if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle];
+
if ( !count( preg_grep( '/^(admin)|(dev)$/i', xwGetListByTagname( $tProperties, 'write' ) ) ) ) {
# Get wiki article content (XML)
+
xwMessage( "Failed to execute \"$tTitle\", must be writable only by dev or admin!", 'red' );
$articleXML = new Article(Title::newFromText($articleTitle));
+
return;
$articleXML = $articleXML->getContent(false);
+
}
# Convert to DOM object
+
# Permissions ok, execute the transform code and trap output
if (!$articleDOM = @domxml_open_mem($articleXML)) $articleDOM = domxml_new_doc("1.0");
+
$path = '/properties/'.preg_replace( "/\\.\\w+$/", '', $tTitle );
return $xwArticleCache[$articleTitle] = $articleDOM;
+
$script = $GLOBALS['xwScript'];
 +
ob_start();
 +
eval( "?>$tCode<?" );
 +
xwExtractMessages( $tTitle );
 +
ob_end_clean();
 
}
 
}
  
# Return a list of the hrefs found in the passed content
+
# Append CSS stylesheet link to output
function xwExtractHrefs($content) {
+
function xwApplyCSS($title, $tName) {
preg_match_all('/(<a.+?<\/a>)/', $content, $match);
+
global $xwDebug, $xwStyleSheets;
return $match[1];
+
if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue');
 +
$xwStyleSheets[] = $tName;
 
}
 
}
  
# Render a xmlWiki system message
+
# Apply XSLT to article-dom
function xwMessage($msg, $col) {
+
# - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
return "\n<table width=100% border=0><tr><td align=center>
+
# - if output-method is html, article will get stringified and language updated
<table width=50% border bordercolor=#bbbbbb borderwidth=1 bgcolor=#999999><tr><td align=center>
+
function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) {
<font face=arial size=2 color=$col><b>$msg</b></font>
+
global $xwDebug, $xwStyleSheets;
</table></table>\n";
+
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;
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# ADMIN
+
# DOM FUNCTIONS
function xwAdminPage() {
+
 
 +
# 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;
 +
}
 +
}
  
?><html><head><title>xmlWiki Admin Page</title></head><body bgcolor=#cccccc>
+
function xwGetProperty(&$properties, $query, &$value) {
<table border=0 width=100% cellpadding=0 cellspacing=0>
+
global $xwDebug;
<tr><td align=center><table border=0 width=30%>
+
if (!preg_match("/^xpath:(.+)$/", $query, $match)) $match = array('', "/properties/$query");
<tr><td align=center>
+
if (count($results = xwXPathQuery($properties, $match[1])) == 0) return false;
<a href="./index.php/xmlWiki"><img src="./xmlwiki.jpg" border=0 alt="xmlWiki"></a>
+
if (php(4)) $value = $results[count($results)-1]->get_content();
<tr><td align=center bgcolor=#003366>
+
else $value = $results[count($results)-1]->nodeValue;
<b><font size=5 color=white face=helvetica>xmlWiki Admin Page</font></b>
+
if ($xwDebug) xwMessage("xwGetProperty( $query ) returned \"$value\"", 'purple');
</table></table><br><br><br><br><?
+
return true;
 +
}
  
# back up index.php if doesn't already exist
+
# - Syntax:  xpath:XPathQuery:[[@]node][+] = value
if (!file_exists('./index.bak')) {
+
# - if "node" exists, then a new element (or att if "@node") is created for the value
if (!copy('./index.bak', './index.php')) {
+
# - if "+" exists, content is appended, else replaced
print xwMessage('Could not back up index.php!', '#d00000');
+
function xwSetProperty(&$properties, $query, $value) {
exit;
+
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);
 +
}
 
}
 
}
 
}
 
}
if (array_key_exists('enable', $_GET)) {
+
return true;
# Replace from backup before enabling
+
}
if (!copy('./index.bak', './index.php'))
+
 
print xwMessage('Could not copy index.bak over index.php', '#d00000');
+
# Return array of comma-separated-items in element content
require_once('./Version.php');
+
# - if more than one element all are split and appended into the list
$ver = $wgSoftwareRevision;
+
function xwGetListByTagname(&$xml, $tag) {
# Matches for patch-points/MediaWiki versions
+
$list = array();
$requireMatch['1002'] = "/^wfProfileOut\\( *'main-misc-setup' *\\);$/m";
+
if (is_object($xml)) {
$inputMatch['1002'] = "/^wfProfileIn\\( *'main-action' *\\);$/m";
+
if (php(4)) {
$outputMatch['1002'] = "/\\\$wgOut->output\\(\\);/m";
+
$root = $xml->document_element();  
# Apply patches
+
foreach ($root->get_elements_by_tagname($tag) as $element)
if (preg_match($requireMatch[$ver], $file = join('', file('./index.php')))
+
if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv));
&&  preg_match($inputMatch[$ver], $file)
+
}
&&  preg_match($outputMatch[$ver], $file)) {
+
else {
$file = preg_replace($requireMatch[$ver], "wfProfileOut( 'main-misc-setup' );\nrequire_once( './xmlwiki.php' );", $file);
+
foreach ($xml->documentElement->getElementsByTagname($tag) as $element)
$file = preg_replace($inputMatch[$ver], "xwProcessInput();\nwfProfileIn( 'main-action' );", $file);
+
if ($csv = $element->nodeValue) $list = array_merge($list, split(',', $csv));
$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)) {
+
return $list;
# Replace from backup
+
}
if (!copy('./index.bak', './index.php'))
+
 
print xwMessage('Could not copy index.bak over index.php', '#d00000');
+
# 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');
 
}
 
}
# Print current state
+
}
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');
 
 
 
 +
# Add message to queue
 +
function xwMessage($msg, $col = '#000080') {
 +
global $xwMessages;
 +
$msg = htmlentities($msg);
 +
return $xwMessages[] = "<font color=\"$col\">$msg</font>";
 +
}
  
?><br><br><br><table border=0 width=100% cellpadding=0 cellspacing=0>
+
function xwReplaceTokens(&$article, $embed = false) {
<tr><td align=center colspan=3><table border=0 width=50%>
+
global $xwStyleSheets, $xwMessages, $xwCssToken, $xwMsgToken, $xwScript;
<td align=right valign=top><a href="http://w3c.org/"><img src="./xhtml.jpg" alt="XHTML1.0" border=0 /></a>
+
 
<td align=right valign=top><a href="http://www.php.net/"><img src="./php.jpg" alt="PHP" border=0 /></a>
+
# Stylesheets
<td align=right valign=top><a href="http://www.mediawiki.org/"><img src="./mediawiki.jpg" alt="MediaWiki" border=0 /></a>
+
$stylesheets = '';
</table></table><br><br></body></html><?
+
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
</pre>
+
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;
 +
}
 +
</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;
	}