Difference between revisions of "Xmlwiki.php"

From Organic Design wiki
m
 
(200 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<?php
+
{{legacy}}
 +
<source lang="php">
 
# xmlWiki - MediaWiki XML Hack
 
# xmlWiki - MediaWiki XML Hack
# Nad - 2005-05-18
+
# Nad - Started: 2005-05-18
  
# INIT HOOK
+
# Exit if not included from index.php
# INPUT HOOK
+
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!');
# OUTPUT HOOK
+
if (php(3)) die('xmlWiki cannot run on less than PHP4!');
# PARSER HOOK
+
 
# SKIN HOOK
+
# Add the rest of the hooks
# ARTICLE FUNCTIONS
+
$wgHooks['PreParser'][] = 'xwPreParserHook';
# TRANSFORMS
+
$wgHooks['ParserBeforeStrip'][] = 'xwPreParserHook';
# UTILITY FUNCTIONS
+
$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;
  
# TODO:
+
# Get default properties article
# Publish:
+
$xwDefaultProperties = $xwDefaultPropertiesXML = xwArticleContent( 'default-properties.xml', false );
# - xml:article - holds article publish-list
+
xwDomificateArticle( $xwDefaultProperties, 'defaults' );
# - mirror/wiki-sync
 
# Transforms:
 
# - xml:article - now transforms are in here (main article is now like <body>)
 
# - so we don't do the "its one of ours" thing anymore
 
# Raw as Default:
 
# - Need to mod the article-link so it has action=view
 
  
# LATER:
+
# Get user-system-article and extract users' groups from it if any
# - allow XIncludes to build large docs from others
+
$xwUserSYS = xwArticleContent( "sys:$xwUserName" );
# - allow docBook and DSSSL content
+
xwDomificateArticle( $xwUserSYS, "sys:$xwUserName" );
# - Maybe only use XML transforms and use PHP via PI's
+
$xwUserGroups = array_merge( $xwUserGroups, xwGetListByTagname( $xwUserSYS, 'groups' ) );
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
# Read user-prefs
# INIT HOOK
+
xwGetProperty( $xwUserSYS, 'xpath:/user/debug', $xwDebug );
  
# Exit if not included from index.php
+
# Get article-meta-file and extract article-perms
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!');
+
if ( $xwIsSystem ) $xwArticleReadableBy = $xwArticleWritableBy = array('admin');
 +
else {
  
# Allows Geshi to be installed by PARSER-HOOK if not installed th usual way already
+
# Get xml:article (self if already xml:*)
if (!@is_object($wgGeshiSyntaxSettings)) $wgGeshiSyntaxSettings = false;
+
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 =
$xwEdit = isset($_REQUEST['action']) && ($_REQUEST['action'] == 'edit');
+
'<?xml version="1.0" standalone="yes"?>
$xwView = (!isset($_REQUEST['action']) || (isset($_REQUEST['action']) && ($_REQUEST['action'] == 'view')));
+
<!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd">
$xwRaw = isset($_REQUEST['action']) && ($_REQUEST['action'] == 'raw');
+
<properties/>';
$xwPreview = isset($_REQUEST['wpPreview']);
+
xwDomificateArticle( $xwArticleProperties, 'CreateProperties' );
$xwSave = isset($_REQUEST['wpSave']);
+
xwMergeDOM( $xwArticleProperties, $xwDefaultProperties );
$xwUserName = $wgUser->mName;
+
xwMergeDOM( $xwArticleProperties, $p );
$xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false);
 
$xwArticleTitle = $wgTitle->getPrefixedURL();
 
$xwArticleCache = array();
 
$xwArticleLinks = array();
 
$xwUserLinks = array();
 
$xwMetaArticle = false;
 
$xwMsgToken = '<!--xwMsg-->';
 
$xwTemplate = null;
 
$xwParserHookCalled = false;
 
$xwSkinHookCalled = false;
 
  
# R/W by user, not currently used, but for xmlWiki prefs etc
+
$xwArticleReadableBy = xwGetListByTagname( $xwArticleProperties, 'read' );
if ($xwUserXML = xwArticleContent("xml:$xwUserName"))
+
$xwArticleWritableBy = xwGetListByTagname( $xwArticleProperties, 'write' );
xwDomificateArticle($xwUserXML, "xml:$xwUserName");
+
if ( $xwIsProperties ) $xwArticleProperties = false;
 +
}
 +
if ( !is_object( $xwArticleProperties ) ) $xwArticleProperties = $xwDefaultProperties;
 +
if ( $xwIsSpecial || !count( $xwArticleReadableBy ) ) $xwArticleReadableBy = array('anyone');
 +
if ( !count($xwArticleWritableBy) ) $xwArticleWritableBy = array('anyone');
  
# R/W by admin only, holds users groups and other system info like stats
+
# If no language specified in properties, guess from name and content
if ($xwUserSYS = xwArticleContent("sys:$xwUserName"))
+
if ( !xwGetProperty( $xwArticleProperties, 'language', $xwLanguage ) )
xwDomificateArticle($xwUserSYS, "sys:$xwUserName");
+
xwSetProperty( $xwArticleProperties, 'language', xwArticleType( $xwArticleTitle, $xwArticle ) );
  
# Security
+
# Set perms for this request
# - Security info is: owner, read-groups, write-groups (default values are "world")
+
if ($xwDebug) xwMessage('PERMISSIONS:','green');
# - Users and groups are interchangeable
+
if (in_array('admin', $xwUserGroups)) $xwIsAdmin = $xwReadable = $xwWritable = true;
# - Default user is "nobody", default group is "everybody"
+
else {
if (ereg('^xml:(.+)$', $xwArticleTitle)) {
+
$xwReadable = 0 < count(array_intersect($xwArticleReadableBy, $xwUserGroups));
# This is an xml-pseudo-namespace
+
$xwWritable = 0 < count(array_intersect($xwArticleWritableBy, $xwUserGroups));
# - get perms from its 'parent' article
 
$xwMetaArticle = true;
 
 
}
 
}
elseif (ereg('^sys:(.+)$', $xwArticleTitle)) {
+
if ($xwDebug) {
# This is an sys-pseudo-namespace
+
xwMessage('Groups: '.join(', ', $xwUserGroups));
# - only admin can read/write sys:
+
xwMessage('Readable ('.($xwReadable?'yes':'no').'): '.join(', ', $xwArticleReadableBy));
$xwMetaArticle = true;
+
xwMessage('Writable ('.($xwWritable?'yes':'no').'): '.join(', ', $xwArticleWritableBy));
 
}
 
}
else {
+
 
# xml:article holds article perms
+
# Divert to access-denied article if not readable
# - it doesn't have perms of its own
+
if (!$xwReadable) {
if ($xwArticleXML = xwArticleContent("xml:$xwArticleTitle"))
+
$action = 'view';
xwDomificateArticle($xwArticleXML, "xml:$xwArticleTitle");
+
$xwSave = $xwEdit = false;
 
}
 
}
if (!is_object($xwArticleXML)) {
+
 
$xwReadable = $xwWritable = true;
+
# Handle security for Move
if (strtolower($xwArticleTitle) == 'xmlwiki.php') $xwWritable = strtolower($xwUserName) == 'nad';
+
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' );
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
Line 96: Line 141:
 
function xwInputHook() {
 
function xwInputHook() {
  
global $xwArticle, $xwArticleTitle, $xwMetaArticle, $xwReadable, $xwWritable;
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST;
global $xwDebug, $xwSave;
+
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle;
if ($xwDebug) xwMessage('xwInputHook()','green');
+
global $xwUserGroups, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
 +
if ($xwDebug) xwMessage('INPUT-HOOK:','green');
  
# If an article-change is being attempted,
+
# If not writable, change action to view
if ($xwSave) {
+
if ( !$xwWritable && !in_array($action, array('view','history','raw')) ) {
if (!$xwReadable || !$xwWritable) {
+
xwMessage('Sorry, article not writable, action changed to "view".', 'red');
# Not writable, change action to view
+
$action = 'view';
xwMessage('Cannot access article for writing, changing action to "view"', 'red');
+
$xwSave = $xwEdit = false;
global $action;
+
}
$action='view';
 
}
 
else {
 
# Parse POST for xmlWiki form input
 
global $_REQUEST;
 
foreach ($_REQUEST as $k => $v) {
 
  
if (is_object($xwArticle) && preg_match("/^xw:(@?)(\\w*):(.+)$/", $k, $m)) {
+
# Scan POST and apply any XPath inputs
# Form-input-name is "xw:[[@]node]:XPathQuery" which directs the value into xwArticle (if DOM)
+
foreach ($_REQUEST as $query => $value) {
# - if "node" exists, then a new element (or att if "@node") is created for the value
+
if (ereg('^xpath.3A.+.3A', $query)) $query = urldecode( $query );
# - hidden-inputs can direct input-handlers into the transform list
+
if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, str_replace('&', '%26', $value));
list(, $att, $node, $xpath) = $m;
+
}
foreach (xwXPathQuery($xwArticleXML, $xpath) as $result) {
 
if ($att && $node) {
 
# create attribute for value
 
}
 
elseif ($node) {
 
# create element for value
 
}
 
else {
 
# overwrite existing node content
 
}
 
}
 
}
 
  
elseif ($xwMetaArticle && ($k == 'wpSave')) {
+
# if saving a pseudo-namespace...
# if it's a pseudo-namespace, wpSave should be valid XML to be saved
+
if ($xwSave) {
xwDomificate($wpSave);
+
if ( $xwIsProperties || $xwIsSystem ) {
if (!is_object($wpSave)) {
+
# wpTextbox1 should be valid XML to be saved
xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red');
+
$tb = $_REQUEST['wpTextbox1'];
global $action;
+
xwDomificateArticle( $tb, 'POST-DATA' );
$action='edit';
+
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
 
# OUTPUT HOOK
  
# Generate output to send back to client
+
# Post-process and output article
# - if article is not XML, then wiki's parsed content is used
 
 
function xwOutputHook() {
 
function xwOutputHook() {
  
global $wgOut, $wgUser, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug;
+
global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
global $xwArticle, $xwArticleXML, $xwArticleTitle, $xwReadable, $xwWritable;
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit, $action;
global $xwView, $xwPreview, $xwSave, $xwRaw, $xwUserLinks, $xwArticleLinks;
+
if ($action == 'raw') return;
if ($xwDebug) xwMessage('xwOutputHook()','green');
+
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
  
# If readable, get wiki-environment, else clear content
+
# Activate the skin-hook and generate wiki's output
if ($xwReadable) {
+
$wgUser->setOption('skin', 'xwskin');
$wgUser->setOption('skin', 'xwskin');
+
$wgOut->output();
$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];
 +
}
 
}
 
}
else {
+
elseif ( $xwEdit ) {
$xwArticle = '';
+
# Editing normally, apply transforms in edit-list
xwMessage('Cannot access article for reading!', 'red');
+
xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'edit', 'OUTPUT-HOOK' );
 
}
 
}
  
# If no write-access, remove edit link
+
if ( $xwDebug ) {
if (!$xwWritable) unset($xwArticleLinks['Edit']);
+
global $xwParserHookCalled, $xwSkinHookCalled;
 +
if ( !$xwParserHookCalled ) xwMessage( 'PARSER-HOOK was not called', 'green' );
 +
if ( !$xwSkinHookCalled ) xwMessage( 'SKIN-HOOK was not called', 'green' );
 +
}
  
# NOTE - Why not move this into INPUT-HOOK????????
+
# Insert stylesheets and messages into output
# If saving, check the publishing list
+
xwReplaceTokens( $xwArticle );
if ($xwSave) {
 
 
# Check publish-list and process if any
 
if (false) {
 
xwMessage('Publishing...');
 
# Publish each item
 
foreach ($xwArticlePublish as $url) {
 
if ($FH = fopen($url, 'a')) {
 
# ftp example: ftp://user:password@example.com/file.txt
 
fwrite($FH, $xwArticle);
 
fclose($FH);
 
xwMessage("Article published to $url");
 
}
 
else xwMessage("Failed to publish to $url", 'red');
 
}
 
}
 
 
# Check mirror-list and process if any
 
# - allows articles in separate wiki's to be syncronised
 
# - does rudimentary security by sending a salt and response being a hash of the password generated from the salt
 
# - this could be done for login too using ECMA-MD5 on browser
 
if (false) {
 
xwMessage('Mirroring...');
 
}
 
}
 
  
 +
# 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
 +
);
  
# Insert messages into final html and output
+
# Output final result
if ($xwDebug && !$xwParserHookCalled) xwMessage('PARSER-HOOK was not called','green');
 
if ($xwDebug && !$xwSkinHookCalled) xwMessage('SKIN-HOOK was not called','green');
 
xwTransformAddMessages();
 
 
print $xwArticle;
 
print $xwArticle;
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
# PARSER HOOK
+
# PRE-PARSER HOOK
  
function xwParserHook(&$text) {
+
function xwPreParserHook( &$text ) {
 +
global $xwArticleTitle, $xwArticle, $xwArticleProperties, $xwSave;
 +
global $xwParserHookCalled, $xwDebug, $xwReadable, $xwScript;
 +
if ( $xwDebug ) xwMessage( 'PREPARSER-HOOK:', 'green' );
 +
$xwParserHookCalled = true;
  
global $xwRaw, $xwParserHookCalled, $xwDebug, $xwArticleTitle, $xwArticle;
+
# Save clears all output, but we need to parse it for save-transforms
if ($xwDebug) xwMessage('xwParserHook(TEXT)','green');
+
if ( $xwSave ) $text = $_REQUEST['wpTextbox1'];
$xwParserHookCalled = true;
 
$transformed = false;
 
  
 
# If this text-fragment is our article, apply transforms
 
# If this text-fragment is our article, apply transforms
#xwMessage($text,'purple');
+
if ( $xwSave or is_object($xwArticle) or strncmp( $text, $xwArticle, 100 ) == 0 or strncmp( $text, xwArticleContent( $xwArticleTitle, false ), 100 ) == 0 ) {
#xwMessage($xwArticle,'purple');
+
if ( $xwDebug ) xwMessage( "Matching text-fragment intercepted." );
if ((strncmp($text, $xwArticle, 100) == 0) || (strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0)) {
+
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 == '';
 +
}
  
if ($xwDebug) xwMessage('- PARSER-HOOK has recognised the article');
 
  
# If attempted xml encountered, try to domificate and transform
+
# ---------------------------------------------------------------------------------------------------------------------- #
if (preg_match("/^<\\?xml/i", $text)) {
+
# POST-PARSER HOOK
$transformed = true;
+
# - this hook was not implemented or had been removed,
xwDomificateArticle($text, 'ParserHook/text');
+
#  I've put in on the official ArticleSaveComplete hook for now
xwTransformArticle($text);
+
#  - 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' );
  
# If not a 'raw' request, try some Geshi on
+
# Now apply the save-stack
if (!$xwRaw) {
+
$dummy = "";
global $wgGeshiSyntaxSettings;
+
xwReduceTransformStack( $dummy, $xwArticleProperties, $xwArticleTitle, 'save', 'POSTPARSER-HOOK' );
if (!is_object($wgGeshiSyntaxSettings)) require_once('extensions/geshi/geshi.php');
 
if ($lang = xwArticleType($xwArticleTitle, $text)) {
 
if ($xwDebug) xwMessage("- Geshi: This is \"$lang\".");
 
# Replace tabs with spaces (better than geshi)
 
$text = preg_replace_callback("/^(.*?)(\\t+)/m", 'xwReplaceTabs', $text);
 
# Set up a geshi-parser
 
$geshi = new GeSHi($text, $lang == 'xslt' ? 'xml' : $lang, 'extensions/geshi/geshi');
 
$geshi->set_header_type(GESHI_HEADER_DIV);
 
#$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
 
#@$geshi->set_line_style('color:#aaaaaa');
 
#@$geshi->set_code_style('color:black');
 
@$geshi->set_keyword_group_style(1, 'color:blue', true);
 
@$geshi->set_keyword_group_style(2, 'color:blue', true);
 
@$geshi->set_keyword_group_style(3, 'color:blue', true);
 
@$geshi->set_comments_style(1,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_comments_style(2,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_comments_style(3,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_escape_characters_style('color:red',true);
 
@$geshi->set_brackets_style('color:black',true);
 
@$geshi->set_strings_style('color:#008080',true);
 
@$geshi->set_numbers_style('color:black',true);
 
@$geshi->set_methods_style('color:black',true);
 
@$geshi->set_symbols_style('color:black',true);
 
@$geshi->set_regexps_style('color:green',true);
 
# Do the parse
 
$text = preg_replace("/^<div.*?>/", '<div class="xwcode">', $geshi->parse_code());
 
$transformed = true;
 
}
 
elseif ($xwDebug) xwMessage('- Geshi: Not my problem.');
 
}
 
}
 
elseif ($xwDebug) xwMessage('- Parsing other content');
 
 
  
# Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours
+
# Output should be empty for save
return $transformed;
+
#$text = '';
 
}
 
}
+
 
# Converts tabs to spaces better than geshi
+
 
function xwReplaceTabs($m) {
 
return $m[1].str_repeat(' ',strlen($m[2])*4-strlen($m[1])%4);
 
}
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# SKIN HOOK
 
# SKIN HOOK
Line 282: Line 314:
 
# If attempted xml encountered, try to domificate and transform
 
# If attempted xml encountered, try to domificate and transform
 
# - Raw requests don't get here
 
# - Raw requests don't get here
function xwSkinHook(&$tmpl) {
+
function xwSkinHook( &$tmpl ) {
  
global $xwArticle, $xwTemplate, $xwDebug, $xwSkinHookCalled;
+
global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw;
if ($xwDebug) xwMessage('xwSkinHook(TEMPLATE)','green');
+
global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;
 +
 
 +
# Extract article from existing template structure
 +
if ( $xwDebug ) xwMessage( 'SKIN-HOOK:', 'green' );
 
$xwSkinHookCalled = true;
 
$xwSkinHookCalled = true;
 
$xwTemplate = $tmpl;
 
$xwTemplate = $tmpl;
$xwArticle = $xwTemplate->data['bodytext'];
+
if ( $xwReadable ) $xwArticle = $xwTemplate->data['bodytext'];
#$xslt = xwArticleContent('xmlwiki.xslt');
 
 
# Do xmlWiki page-layout transform
 
  
xwTransformPageLayout(); # should be: xwApplyXSLT($xwArticle, $xslt, 'xmlwiki.xslt');
+
# 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
 
# 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
 
# Retreive wiki-article as raw text
function xwArticleContent($articleTitle) {
+
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
 
# Return with results if already cached
global $xwArticleCache, $xwDebug;
 
if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue');
 
 
if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle];
 
if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle];
 
# Get wiki article content (or use global if no article passed)
 
# Get wiki article content (or use global if no article passed)
Line 310: Line 372:
 
if ($article = new Article(Title::newFromText($articleTitle)))
 
if ($article = new Article(Title::newFromText($articleTitle)))
 
$article = $article->getContentWithoutUsingSoManyDamnGlobals();
 
$article = $article->getContentWithoutUsingSoManyDamnGlobals();
if ($article == '(There is currently no text in this page)') return false;
+
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;
 
if ($articleTitle) $xwArticleCache[$articleTitle] = $article;
 
return $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
 
# Decide kind of article from content and title
function xwArticleType($title, $article) {
+
function xwArticleType( $title, $article ) {
# Name based matches
+
if ( is_object($article) ) return 'xml';
if (ereg('\\.php$', $title)) return 'php';
+
if ( eregi('\\.php$', $title) ) return 'php';
if (ereg('\\.css$', $title)) return 'css';
+
if ( eregi('\\.css$', $title) ) return 'css';
if (ereg('\\.as$', $title)) return 'actionscript';
+
if ( eregi('^talk:', $title) ) return '';
if (ereg('\\.py$', $title)) return 'python';
+
if ( preg_match("/^<\\?xml.+?\\?>.*<xsl:stylesheet/is", $article) ) return 'xslt';
if (ereg('\\.java$', $title)) return 'java';
+
if ( eregi('^(sys|xml):.+$', $title) || ereg('^<\\?xml', $article) ) return 'xml';
if (ereg('\\.((cpp)|(h))$', $title)) return 'cpp';
+
if ( eregi('^<\\?html', $article ) || eregi('\\.html$', $title)) return 'html4strict';
if (ereg('\\.js$', $title)) return 'javascript';
+
if ( eregi('\\.p[lm]$', $title) ) return 'perl';
if (ereg('\\.css$', $title)) return 'css';
+
if ( ereg('^#![/a-zA-Z0-9]+\\/perl', $article) ) return 'perl';
# Content based matches
+
if ( eregi('\\.as$', $title) ) return 'actionscript';
if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt';
+
if ( eregi('\\.dtd$', $title) ) return 'xml';
if (ereg('^<\\?xml', $article)) return 'xml';
+
if ( eregi('\\.tex$', $title) ) return 'tex';
if (ereg('^<\\?html', $article)) return 'html4strict';
+
        if ( eregi('\\.nt$', $title) ) return 'dna';
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
+
if ( eregi('\\.r$', $title) ) return 'r';
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
+
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 '';
 
}
 
}
  
Line 338: Line 426:
 
function xwDomificateArticle(&$article, $id = '') {
 
function xwDomificateArticle(&$article, $id = '') {
 
global $xwDebug;
 
global $xwDebug;
if ($xwDebug) xwMessage("xwDomificatwArticle(XML,$id)",'blue');
+
if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue');
 
ob_start();
 
ob_start();
if ($dom = domxml_open_mem($article)) $article = $dom;
+
if ($article) {
 +
if (php(4)) $dom = domxml_open_mem($article);
 +
else $dom = DOMDocument::loadXML($article);
 +
}
 +
if (isset($dom) && is_object($dom)) $article = $dom;
 
else {
 
else {
 
# Could not convert, extract error messages from output
 
# Could not convert, extract error messages from output
xwMessage("- Failed :-(", 'red');
+
if ($xwDebug) xwMessage("Failed :-(", 'red');
 
xwExtractMessages();
 
xwExtractMessages();
 
}
 
}
 
ob_end_clean();
 
ob_end_clean();
 +
return $article;
 
}
 
}
  
Line 354: Line 447:
 
if (!is_object($article)) return false;
 
if (!is_object($article)) return false;
 
global $xwDebug;
 
global $xwDebug;
 
+
if ($xwDebug) xwMessage("xwUndomificate(\"$id\")",'blue');
if ($xwDebug) xwMessage("xwUndomificate(DOM,$id)",'blue');
+
# TODO:
 
# Validate, report errors
 
# Validate, report errors
 
# xwApplyXSLT($article, $xslt) if xml referrs one
 
# xwApplyXSLT($article, $xslt) if xml referrs one
 
# Convert DOM back to XML if still an object
 
# Convert DOM back to XML if still an object
if (is_object($article)) $article = $article->dump_mem(true);
+
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;
 
return true;
 
}
 
}
  
# Apply XSLT to article-dom
 
# - if output-method is html, article will get stringified
 
function xwApplyXSLT(&$article, &$xslt, $id = '') {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$id)",'blue');
 
ob_start();
 
$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);
 
xwExtractMessages();
 
ob_end_clean();
 
if ($tResult) $article = $tResult; else return false;
 
return true;
 
}
 
  
# Return result set of an XPath query on passed DOM-object
+
# ---------------------------------------------------------------------------------------------------------------------- #
function xwXPathQuery(&$dom, $query) {
+
# TRANSFORM FUNCTIONS
global $xwDebug;
 
if ($xwDebug) xwMessage("xwXPathQuery(DOM,\"$query\")",'blue');
 
ob_start();
 
$context = $dom->xpath_new_context();
 
if ($xpath = $context->xpath_eval($query)) $result = $xpath->nodeset;
 
else {
 
xwMessage('- Query Failed! :-(', 'red');
 
$result = array();
 
}
 
xwExtractMessages();
 
ob_end_clean();
 
return $result;
 
}
 
  
# Transform an article object
+
# Apply all transforms in passed stack
# - Article must be an xmlwiki:* DOM object to be transformable
+
function xwReduceTransformStack( &$article, &$properties, $title, $event, $id = '' ) {
# - Article is undomificated on exit
 
# - Article remains unchanged if not DOM
 
function xwTransformArticle(&$article) {
 
 
 
# Get doc info or exit if not one of ours
 
if (!is_object($article)) return false;
 
$aType = $article->doctype();
 
$aType = $aType->name;
 
$aRoot = $article->document_element();
 
#if (!ereg('^xmlwiki:', $aType)) return false; # this must change to meta article
 
 
global $xwDebug;
 
global $xwDebug;
if ($xwDebug) xwMessage("xwTransformArticle(xmlwiki:$aType)",'blue');
+
$GLOBALS['xwTransformID']++;
 
+
if ( $xwDebug ) xwMessage( "Applying \"$event\" transforms to \"$title\" from \"$id\".");
# Loop thru transforms applying each
+
if ( is_object( $properties ) ) {
foreach ($aRoot->get_elements_by_tagname('transform') as $tElement) {
+
if (php(4)) {
$tName = $tElement->get_content();
+
$docType = $properties->doctype();
if (ereg('^(.+)\\(\\)$', $tName, $m)) {
+
$root = $properties->document_element();
# Transform is "name()" format, try to execute from a local function declaration
 
# - probably this is not needed soon, as XSLT-PI's may be a better structure
 
$tName = $m[1];
 
if ($tResult = @call_user_func("xwTransform_$tName", $article)) $article = $tResult;
 
else xwMessage("- Unknown transform \"$tName\"!", 'red');
 
 
}
 
}
 
else {
 
else {
# Transform is not local, get transform-article and apply to current-article-object
+
$docType = $properties->doctype;
if ($tText = xwArticleContent($tName)) {
+
$root = $properties->documentElement;
# Get transform type
+
}
$tType = xwArticleType($tName, $tText);
+
if ( ereg( '^xmlwiki:properties', $docType->name ) ) {
if ($tType == 'xslt') xwApplyXSLT($article, $tText, $tName);
+
if (php(4)) {
elseif ($tType == 'php') {
+
while (count($tNodes = $root->get_elements_by_tagname($event))) {
# php, check perms and execute (trapping errors for reporting - unless fatal)
+
$tName = $tNodes[0]->get_content();
if ($perms_check_out_ok) {
+
xwApplyTransform($tName, $article, $properties, $title, $event);
ob_start();
+
$tNodes[0]->unlink_node();
eval($tText);
 
xwExtractMessages();
 
ob_end_clean();
 
}
 
 
}
 
}
elseif ($tType == 'css') {
+
}
# include CSS ref in content
+
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("- No such transform-article \"$tName\"!", 'red');
 
 
}
 
}
 +
else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red');
 
}
 
}
 +
}
  
# If article is still a DOM object, stringify it once and for all
+
# Apply a transform
xwUndomificateArticle($article);  
+
function xwApplyTransform($transform, &$article, &$properties, &$title, $event) {
return true;
+
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
# TRANSFORMS
+
function xwApplyPHP( &$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event ) {
# - these transforms are only temporarily here - they should be articles
+
global $xwDebug;
# - we should use DTD's for each kind: document, image, layout, table
+
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();
 +
}
  
# Surrounds the article with HTML structural page-layout containing a ref to xmlwiki.css
+
# Append CSS stylesheet link to output
# - This is temporary
+
function xwApplyCSS($title, $tName) {
# - It should return the page as a DOM object to be transformed by xmlwiki.xslt
+
global $xwDebug, $xwStyleSheets;
function xwTransformPageLayout() {
+
if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue');
 +
$xwStyleSheets[] = $tName;
 +
}
  
global $xwMsgToken, $xwArticle, $xwTemplate, $xwDebug;
+
# Apply XSLT to article-dom
if ($xwDebug) xwMessage("xwTransformPageLayout()",'blue');
+
# - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
$tmpl = &$xwTemplate;
+
# - if output-method is html, article will get stringified and language updated
$data = &$tmpl->data;
+
function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) {
$doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ';
+
global $xwDebug, $xwStyleSheets;
$doctype .= '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
+
if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue');
$html = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="';
+
if (is_object($article)) {
$html .= htmlspecialchars($data['lang']).'" lang="'.htmlspecialchars($data['lang']).'" dir="';
+
ob_start();
$html .= htmlspecialchars($data['dir']).'">';
+
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;
 +
}
  
# Head
 
$head = '<head><meta http-equiv="Content-Type" content="'.htmlspecialchars($data['mimetype']);
 
$head .= '; charset='.htmlspecialchars($data['charset']).'" />'.$data['headlinks'];
 
$head .= '<link rel="stylesheet" type="text/css" href="/wiki/index.php?title=xmlwiki.css&action=raw&gen=css" />';
 
  
$head .= '<title>'.htmlspecialchars($data['pagetitle']).'</title>';
+
# ---------------------------------------------------------------------------------------------------------------------- #
$head .= '</head>';
+
# DOM FUNCTIONS
  
# Body opening tag
+
# Return result set of an XPath query on passed DOM-object
$body = '<body ';
+
function xwXPathQuery(&$dom, $query) {
if ($data['body_ondblclick']) $body .= 'ondblclick="'.htmlspecialchars($data['body_ondblclick']).'" ';
+
$result = array();
if ($data['nsclass']) $body .= 'class="'.htmlspecialchars($data['nsclass']).'"';
+
if (is_object($dom)) {
$body .= '>';
+
global $xwDebug;
 
+
if ($xwDebug) xwMessage("xwXPathQuery( $query )",'blue');
# Content
+
ob_start();
$content = '<div id="column-content">';
+
if (php(4)) {
$content .= '<div id="content"><a name="top" id="contentTop"></a>';
+
$context = $dom->xpath_new_context();  
if ($data['sitenotice']) $content .= '<div id="siteNotice">'.$data['sitenotice'].'</div>';
+
if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset;
$content .= '<h1 class="firstHeading">'.htmlspecialchars($data['title']).'</h1>';
+
elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red');
$content .= '<div id="bodyContent">';
+
}
$content .= '<h3 id="siteSub">'.htmlspecialchars($tmpl->translator->translate('tagline')).'</h3>';
+
else if ($xpath = new DOMXPath($dom)) $result = $xpath->query($query);
$content .= '<div id="contentSub">'.$data['subtitle'].'</div>';
+
xwExtractMessages();
if ($data['undelete']) $content .= '<div id="contentSub">'.$data['undelete'].'</div>';
+
ob_end_clean();
if ($data['newtalk']) $content .= '<div class="usermessage">'.$data['newtalk'].'</div>';
 
$content .= "<!-- start content -->$xwArticle"; # <---- Actual article content inserted here
 
if ($data['catlinks']) $content .= '<div id="catlinks">'.$data['catlinks'].'</div>';
 
$content .= '<!-- end content --><div class="visualClear"></div>';
 
$content .= '</div></div></div>';
 
 
 
# Action-links
 
$actions = '<div id="p-cactions" class="portlet"><h5>Views</h5><ul>';
 
foreach ($data['content_actions'] as $key => $action) {
 
$actions .= '<li id="ca-'.htmlspecialchars($key).'"';
 
if ($action['class']) $actions .= ' class="'.htmlspecialchars($action['class']).'"';
 
$href = '<a href="'.htmlspecialchars($action['href']).'">'.htmlspecialchars($action['text']).'</a>';
 
$actions .= ">$href</li>";
 
 
}
 
}
$actions .= '</ul></div>';
+
if (php(4)) return is_array($result) ? $result : array();
 
+
else {
# Personal-links
+
$tmp = array();
$personal = '<div class="portlet" id="p-personal">';
+
foreach ($result as $node) $tmp[] = $node;
$personal .= '<h5>'.htmlspecialchars($tmpl->translator->translate('personaltools')).'</h5>';
+
return $tmp;
$personal .= '<div class="pBody"><ul>';
 
foreach ($data['personal_urls'] as $key => $item) {
 
$personal .= '<li id="pt-'.htmlspecialchars($key).'">';
 
$href = '<a href="'.htmlspecialchars($item['href']).'"';
 
if (!empty($item['class'])) $href .= 'class="'.htmlspecialchars($item['class']).'"';
 
$href .= '>'.htmlspecialchars($item['text']).'</a>';
 
$personal .= "$href</li>";
 
 
}
 
}
$personal .= "</ul></div></div>";
+
}
  
# Logo
+
function xwGetProperty(&$properties, $query, &$value) {
$logo = '<div class="portlet" id="p-logo">';
+
global $xwDebug;
$logo .= '<a style="background-image: url('.htmlspecialchars($data['logopath']).');" ';
+
if (!preg_match("/^xpath:(.+)$/", $query, $match)) $match = array('', "/properties/$query");
$logo .= 'href="'.htmlspecialchars($data['nav_urls']['mainpage']['href']).'" ';
+
if (count($results = xwXPathQuery($properties, $match[1])) == 0) return false;
$logo .= 'title="'.htmlspecialchars($tmpl->translator->translate('mainpage')).'"></a></div>';
+
if (php(4)) $value = $results[count($results)-1]->get_content();
$logo .= '<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>';
+
else $value = $results[count($results)-1]->nodeValue;
 +
if ($xwDebug) xwMessage("xwGetProperty( $query ) returned \"$value\"", 'purple');
 +
return true;
 +
}
  
# Navigation
+
# - Syntax:  xpath:XPathQuery:[[@]node][+] = value
$nav = '<div class="portlet" id="p-nav">';
+
# - if "node" exists, then a new element (or att if "@node") is created for the value
$nav .= '<h5>'.htmlspecialchars($tmpl->translator->translate('navigation')).'</h5>';
+
# - if "+" exists, content is appended, else replaced
$nav .= '<div class="pBody"><ul>';
+
function xwSetProperty(&$properties, $query, $value) {
foreach ($data['navigation_urls'] as $navlink) {
+
global $xwDebug;
$nav .= '<li id="'.htmlspecialchars($navlink['id']).'">';
+
if ($xwDebug) xwMessage("xwSetProperty( $query , $value )", 'purple');
$nav .= '<a href="'.htmlspecialchars($navlink['href']).'">'.htmlspecialchars($navlink['text']).'</a>';
+
if (!is_object($properties)) return false;
$nav .= '</li>';
+
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, '');
 
}
 
}
$nav .= '</ul></div></div>';
+
list(, $xpath, $att, $node, $append) = $match;
+
foreach (xwXPathQuery($properties, $xpath) as $result) {
# Search
+
if ($node) {
$search = '<div id="p-search" class="portlet">';
+
# create new element/attribute in result-node
$search .= '<h5><label for="searchInput">'.htmlspecialchars($tmpl->translator->translate('search')).'</label></h5>';
+
if ($att) {
$search .= '<div class="pBody">';
+
if (php(4)) $result->append_child($properties->create_attribute($node, $value));
$search .= '<form name="searchform" action="'.htmlspecialchars($data['searchaction']).'" id="searchform">';
+
else $result->setAttribute($node, $value);
$search .= '<input id="searchInput" name="search" type="text"';
+
}
if ($tmpl->haveMsg('accesskey-search')) $search .= ' accesskey="';
+
else {
$search .= htmlspecialchars($tmpl->translator->translate('accesskey-search')).'"';
+
if (php(4)) {
if (isset($data['search'])) $search .= ' value="'.htmlspecialchars($data['search']).'"';
+
$node = $properties->create_element($node);
$search .= ' />';
+
$node->set_content($value);
$search .= '<input type="submit" name="go" class="searchButton" id="searchGoButton" value="';
+
$result->append_child($node);
$search .= htmlspecialchars($tmpl->translator->translate('go')).'" />';
+
}
$search .= '&nbsp;<input type="submit" name="fulltext" class="searchButton" value="';
+
else $result->appendChild($properties->createElement($node, $value));
$search .= htmlspecialchars($tmpl->translator->translate('search')).'" />';
+
}
$search .= '</form></div></div>';
 
 
# Toolbox
 
$toolbox = '<div class="portlet" id="p-tb">';
 
$toolbox .= '<h5>'.htmlspecialchars($tmpl->translator->translate('toolbox')).'</h5>';
 
$toolbox .= '<div class="pBody"><ul>';
 
if ($data['notspecialpage']) {
 
foreach (array('whatlinkshere', 'recentchangeslinked') as $special ) {
 
$href = '<a href="'.htmlspecialchars($data['nav_urls'][$special]['href']).'">';
 
$href .= htmlspecialchars($tmpl->translator->translate($special)).'</a>';
 
$toolbox .= "<li id=\"t-'$special'\">$href</li>";
 
 
}
 
}
}
+
else {
if ($data['feeds']) {
+
# node not set, replace or append current-result-value
$toolbox .= '<li id="feedlinks">';
+
if ($append) {
foreach ($data['feeds'] as $key => $feed) {
+
if (php(4)) $result->set_content( ($result->get_content()).$value );
$toolbox .= '<span id="feed-'.htmlspecialchars($key).'">';
+
else $result->nodeValue .= $value;
$toolbox .= '<a href="'.htmlspecialchars($feed['href']).'">'.htmlspecialchars($feed['text']).'</a>';
+
}
$toolbox .= '&nbsp;</span>';
+
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);
 +
}
 
}
 
}
$toolbox .= '</li>';
 
 
}
 
}
foreach (array('contributions', 'emailuser', 'upload', 'specialpages') as $special ) {
+
return true;
if ($data['nav_urls'][$special]) {
+
}
$href = '<a href="'.htmlspecialchars($data['nav_urls'][$special]['href']).'">';
+
 
$href .= htmlspecialchars($tmpl->translator->translate($special)).'</a>';
+
# Return array of comma-separated-items in element content
$toolbox .= "<li id=\"t-$special\">$href</li>";
+
# - 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 {
$toolbox .= '</ul></div></div>';
+
foreach ($xml->documentElement->getElementsByTagname($tag) as $element)
 
+
if ($csv = $element->nodeValue) $list = array_merge($list, split(',', $csv));
# Language
 
$language = '';
 
if ($data['language_urls']) {
 
$language .= '<div id="p-lang" class="portlet">';
 
$language .= '<h5>'.htmlspecialchars($tmpl->translator->translate('otherlanguages')).'</h5>';
 
$language .= '<div class="pBody"><ul>';
 
foreach ($data['language_urls'] as $langlink) {
 
$href = '<a href="'.htmlspecialchars($langlink['href']).'">'.$langlink['text'].'</a>';
 
$language .= "<li>$href</li>";
 
 
}
 
}
$language .= '</ul></div></div>';
 
 
}
 
}
 
+
return $list;
# Footer
 
$footer = '<div id="footer"><ul id="f-list">';
 
if ($data['lastmod']) $footer .= '<li id="f-lastmod">'.$data['lastmod'].'</li>';
 
if ($data['viewcount']) $footer .= '<li id="f-viewcount">'.$data['viewcount'].'</li>';
 
if ($data['credits']) $footer .= '<li id="f-credits">'.$data['credits'].'</li>';
 
if ($data['copyright']) $footer .= '<li id="f-copyright">'.$data['copyright'].'</li>';
 
if ($data['about']) $footer .= '<li id="f-about">'.$data['about'].'</li>';
 
if ($data['disclaimer']) $footer .= '<li id="f-disclaimer">'.$data['disclaimer'].'</li>';
 
$footer .= '</ul></div>';
 
 
 
# Return assembled parts
 
$xwArticle = " $doctype
 
$html
 
$head
 
$body
 
<table border=\"0\" cellpadding=\"10\" cellspacing=\"1\">
 
<tr>
 
<td valign=\"top\" bgcolor=\"#cccccc\">
 
<img src=\"/wiki/xmlwiki.jpg\" />
 
$search
 
$nav
 
$toolbox
 
</td>
 
<td width=\"100%\" valign=\"top\" bgcolor=\"#cccccc\">
 
$personal$actions$xwMsgToken$content
 
</td>
 
</tr>
 
<td colspan=\"2\" bgcolor=\"#cccccc\">$footer</td>
 
<tr>
 
</table>
 
</body>
 
</html>
 
";
 
 
}
 
}
 
# Append messages to content
 
function xwTransformAddMessages() {
 
global $xwArticle, $xwMessages, $xwMsgToken;
 
if (count($xwMessages)) {
 
  
$msg = '<div class="portlet" id="p-xwmessages"><h5>There are Messages</h5></div>';
+
# Merge the two passed DOM objects
$msg .= '<div class="xwmessage"><ul><li>'.join("</li><li>", $xwMessages).'</li></ul></div>';
+
# - appends, does not overwrite
$xwArticle = ereg_replace($xwMsgToken, $msg, $xwArticle);
+
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;
 
}
 
}
  
function xwTransform_document_xxx() {
+
# Remove all occourences of a certain element
# documents are built from parts,
+
function xwRemoveElement( &$properties, $element ) {
# the parts are a tree in its body
+
$root = $properties->document_element();
# includes 'edit' link for each (like section, and also like table)
+
if (php(4)) {
}
+
while ( count($tNodes = $root->get_elements_by_tagname($element)) ) $tNodes[0]->unlink_node();
 
 
# A document object is finalised by transforming it to its body's text
 
function xwTransform_document_render() {
 
global $xwArticle;
 
$root = $xwArticle->document_element();
 
$body = $root->get_elements_by_tagname('body');
 
if (count($body)) $xwArticle = $body[0]->dump_mem(true);
 
}
 
 
 
function xwTransform_layout_xxx() {
 
# for layout container style article
 
}
 
 
 
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
 
global $xwArticleImage;
 
}
 
 
 
# Converts the in-memory-working image into article content
 
function xwTransform_image_render() {
 
global $xwArticle, $xwArticleTitle, $xwArticleImage, $xwRaw;
 
if ($xwRaw) {
 
# render article directly if 'raw' request
 
imagePng($xwArticleImage);
 
# Clear article or it will render as xml
 
$xwArticle = '';
 
# set header to image
 
header("Content-type: image/png");
 
 
}
 
}
 
 
else {
 
else {
# the raw image is not being requested, render image in-page with info instead
+
while ( ($tNodes = $root->getElementsByTagname($element)) && $tNodes->length ) {
$xwArticle = "<img src=\"$xwArticleTitle?action=raw\" alt=\"$xwArticleTitle\"/>";
+
$tNode = $tNodes->item(0);
 +
$tNode->parentNode->removeChild( $tNode );
 +
}
 
}
 
}
}
 
 
# A table object is an interface to a database table
 
# - this can't be implimented fully until the 'edit' hook is done
 
# - the query result is a list in document body, so any normal document transforms can follow it
 
function xwTransform_table_xxx() {
 
# - view lists the table/result with links to edit each (like a normal composition-doc)
 
# - 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,
 
# - and all have time/duration (or cycle?),
 
# - and we have master list of cols-to-table
 
# - then we could create a meta-query (resulting in pri-key-list of all matches)
 
 
}
 
 
# Transform the body content to a xtml/javascript tree
 
function xwTransform_tree() {
 
# import php-tree-component
 
 
}
 
}
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# UTILITY FUNCTIONS
 
# UTILITY FUNCTIONS
 
# Return a list of the hrefs found in the passed content
 
function xwExtractHrefs($content) {
 
preg_match_all('/(<a.+?>(.+)<\/a>)/', $content, $m);
 
$hrefs = array();
 
for ($i = 0; $i < count($m[0]); $i++) $hrefs[$m[2][$i]] = $m[1][$i];
 
return $hrefs;
 
}
 
  
 
# Extract error-messages from captured output and put in proper message-queue
 
# Extract error-messages from captured output and put in proper message-queue
function xwExtractMessages() {
+
function xwExtractMessages($where = false) {
 +
if ($where) $where = " of \"$where\"";
 
$err = preg_replace("/<.+?>/", "", ob_get_contents());
 
$err = preg_replace("/<.+?>/", "", ob_get_contents());
$err = preg_replace('/ in .+? on line.+?[0-9]+/', '', $err);
+
foreach (split("\n", $err) as $msg) if (trim($msg)) {
$err = preg_replace('/Warning.+?\\(\\): /', '', $err);
+
if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1]$where)";
foreach (split("\n", $err) as $msg) if ($msg) xwMessage("- $msg", 'red');
+
$msg = preg_replace('/ in .+? on line.+?([0-9]+)/', " (Line $1$where)", $msg);
 +
if (isset($line)) $msg .= $line;
 +
xwMessage($msg, 'red');
 +
}
 
}
 
}
 
 
Line 732: Line 743:
 
$msg = htmlentities($msg);
 
$msg = htmlentities($msg);
 
return $xwMessages[] = "<font color=\"$col\">$msg</font>";
 
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);
 +
}
 
}
 
}
  
Line 739: Line 768:
 
print strtoupper("<br>$obj properties:<br>");
 
print strtoupper("<br>$obj properties:<br>");
 
foreach (get_object_vars($obj) as $k=>$v) print htmlentities("$k => $v")."<br>";
 
foreach (get_object_vars($obj) as $k=>$v) print htmlentities("$k => $v")."<br>";
 
 
print strtoupper("<br>$obj methods:<br>");
 
print strtoupper("<br>$obj methods:<br>");
 
foreach (get_class_methods($obj) as $k=>$v) print "$v<br>";
 
foreach (get_class_methods($obj) as $k=>$v) print "$v<br>";
Line 749: Line 777:
 
die;
 
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;
	}