Difference between revisions of "Xmlwiki.php"

From Organic Design wiki
m
 
(148 intermediate revisions by 4 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
 
# SECURITY
 
# INPUT HOOK
 
# OUTPUT HOOK
 
# PARSER HOOK
 
# SKIN HOOK
 
# ARTICLE FUNCTIONS
 
# UTILITY FUNCTIONS
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
 
 
# TODO:
 
# - Must deal with page-move: security and also pseudo-namespace moving (maybe use proper namespace?)
 
# - setProperty needs to create element if not there so <output> not needed in swf properties
 
# - XPath bug with create element (is it the same setProperty bug?)
 
# - also language-override in ming-swf.php can then be simpler too
 
# - move geshi/wiki out to a default language.php (which uses the <language> element)
 
 
 
# - TransformArticle:
 
#  - VALIDATE: validate article-properties against xmlwiki:properties.dtd (does PHP4 do this?)
 
#  - TRAP: fatal errors in php-transform execution
 
# - Undomificate: validation, xslt
 
# - Check (later validate) doctype of user:sys (change users pages on site first!)
 
# - Check (later validate) doctype of user/properties on save (still save though)
 
 
 
# BUGS:
 
# - Rob is admin and can't write to Nad pages
 
# - Pages can be moved regardless of perms!!!
 
# - login error goes to old-wiki
 
# - tabReplace doesn't work for /^\t+.+\t.+$/
 
 
 
# LATER:
 
# - allow XIncludes to build large docs from others
 
# - allow docBook and DSSSL content
 
# - Maybe only use XML transforms and use PHP via PI's
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# INIT
 
  
 
# Exit if not included from index.php
 
# Exit if not included from index.php
 
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s 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!');
  
# Allows Geshi to be installed by PARSER-HOOK if not installed the usual way already
+
# Add the rest of the hooks
# - still needs to be present in ./extensions though
+
$wgHooks['PreParser'][] = 'xwPreParserHook';
if (!@is_object($wgGeshiSyntaxSettings)) die('xmlWiki requires the Geshi syntax-highlighting extension');
+
$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
 
# System globals
Line 58: Line 31:
 
$xwSkinHookCalled = false;
 
$xwSkinHookCalled = false;
 
$xwMsgToken = '<!--xwMsg-->';
 
$xwMsgToken = '<!--xwMsg-->';
 +
$xwCssToken = '<!--xwCss-->';
 +
$xwScript = $wgScript;
 +
$xwTransformID = 1;
  
 
# User globals
 
# User globals
$xwUserName = ucwords($wgUser->mName);
+
$xwUserName = ucwords( $wgUser->mName );
 +
$xwAnonymous = ereg( "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", $xwUserName );
 
$xwUserSYS = null;
 
$xwUserSYS = null;
 
$xwUserGroups = array($xwUserName, 'anyone');
 
$xwUserGroups = array($xwUserName, 'anyone');
Line 70: Line 47:
  
 
# Article globals
 
# Article globals
$xwArticleTitle = $wgTitle->getPrefixedURL();
+
$xwArticleTitle = $wgTitle->getPrefixedText();
 
$xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false);
 
$xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false);
 
$xwArticleProperties = null;
 
$xwArticleProperties = null;
Line 76: Line 53:
 
$xwIsSystem = preg_match('/^sys:.+$/i', $xwArticleTitle);
 
$xwIsSystem = preg_match('/^sys:.+$/i', $xwArticleTitle);
 
$xwIsUser = preg_match('/^user:.+$/i', $xwArticleTitle);
 
$xwIsUser = preg_match('/^user:.+$/i', $xwArticleTitle);
 +
$xwIsSpecial = preg_match('/^special:.+$/i', $xwArticleTitle);
 
$xwIsAdmin = false;
 
$xwIsAdmin = false;
$xwArticleReadableBy = array('anyone');
+
$xwArticleReadableBy = null;
$xwArticleWritableBy = array('anyone');
+
$xwArticleWritableBy = null;
$xwLanguage = '';
+
 
# ---------------------------------------------------------------------------------------------------------------------- #
+
# Get default properties article
# SECURITY
+
$xwDefaultProperties = $xwDefaultPropertiesXML = xwArticleContent( 'default-properties.xml', false );
 +
xwDomificateArticle( $xwDefaultProperties, 'defaults' );
  
 
# Get user-system-article and extract users' groups from it if any
 
# Get user-system-article and extract users' groups from it if any
if ($xwUserSYS = xwArticleContent("sys:$xwUserName")) xwDomificateArticle($xwUserSYS, "sys:$xwUserName");
+
$xwUserSYS = xwArticleContent( "sys:$xwUserName" );
$xwUserGroups = array_merge($xwUserGroups, xwGetListByTagname($xwUserSYS, 'groups'));
+
xwDomificateArticle( $xwUserSYS, "sys:$xwUserName" );
 +
$xwUserGroups = array_merge( $xwUserGroups, xwGetListByTagname( $xwUserSYS, 'groups' ) );
  
 
# Read user-prefs
 
# Read user-prefs
$xwDebug = count($results = xwXPathQuery($xwUserSYS, '//debug')) ? $results[0]->get_content() : false;
+
xwGetProperty( $xwUserSYS, 'xpath:/user/debug', $xwDebug );
  
 
# Get article-meta-file and extract article-perms
 
# Get article-meta-file and extract article-perms
if ($xwIsSystem) $xwArticleReadableBy = $xwArticleWritableBy = array('admin');
+
if ( $xwIsSystem ) $xwArticleReadableBy = $xwArticleWritableBy = array('admin');
 
else {
 
else {
 +
 
# Get xml:article (self if already xml:*)
 
# Get xml:article (self if already xml:*)
if ($xwArticleProperties = $xwIsProperties ? $xwArticle : xwArticleContent($id = 'xml:'.ucwords($xwArticleTitle)))
+
if ( $p = $xwIsProperties ? xwDomificateArticle( $xwArticle ) : xwArticleProperties( $xwArticleTitle ) )
xwDomificateArticle($xwArticleProperties, $xwIsProperties ? $xwArticleTitle : $id);
+
 
$xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read') + array('anyone');
+
# Start with default-properties and merge article props (so new transforms on top)
$xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write') + array('anyone');
+
$xwArticleProperties =
if ($xwIsProperties) $xwArticleProperties = false;
+
'<?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');
  
# Merge this articles properties with default-properties
+
# If no language specified in properties, guess from name and content
if (is_object($xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'),'defaults')))
+
if ( !xwGetProperty( $xwArticleProperties, 'language', $xwLanguage ) )
$xwArticleProperties = xwMergeDOM($xwDefaultProperties, $xwArticleProperties);
+
xwSetProperty( $xwArticleProperties, 'language', xwArticleType( $xwArticleTitle, $xwArticle ) );
 
 
# Get language from properties, or articleType()
 
$xwLanguage = count($results = xwXPathQuery($xwArticleProperties, '/properties/language'))
 
? strtolower($results[0]->get_content())
 
: xwArticleType($xwArticleTitle, $xwArticle);
 
  
 
# Set perms for this request
 
# Set perms for this request
Line 128: Line 116:
 
$xwSave = $xwEdit = false;
 
$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' );
  
  
Line 137: Line 142:
  
 
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST;
 
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST;
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $xwIsProperties, $xwIsSystem;
+
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle;
 +
global $xwUserGroups, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
 
if ($xwDebug) xwMessage('INPUT-HOOK:','green');
 
if ($xwDebug) xwMessage('INPUT-HOOK:','green');
  
 
# If not writable, change action to view
 
# If not writable, change action to view
if (!$xwWritable && ($action != 'view')) {
+
if ( !$xwWritable && !in_array($action, array('view','history','raw')) ) {
 
xwMessage('Sorry, article not writable, action changed to "view".', 'red');
 
xwMessage('Sorry, article not writable, action changed to "view".', 'red');
 
$action = 'view';
 
$action = 'view';
Line 148: Line 154:
  
 
# Scan POST and apply any XPath inputs
 
# Scan POST and apply any XPath inputs
foreach ($_REQUEST as $query => $value) xwSetProperty($xwArticleProperties, $query, $value);
+
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 it's a pseudo-namespace...
+
# if saving a pseudo-namespace...
if ($xwIsProperties || $xwIsSystem) {
+
if ($xwSave) {
# wpSave should be valid XML to be saved
+
if ( $xwIsProperties || $xwIsSystem ) {
if ($xwSave) {
+
# wpTextbox1 should be valid XML to be saved
xwDomificateArticle($wpSave, 'POST-DATA');
+
$tb = $_REQUEST['wpTextbox1'];
if (!is_object($wpSave)) {
+
xwDomificateArticle( $tb, 'POST-DATA' );
xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red');
+
if ( !is_object($tb) ) {
$action='edit';
+
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;
 +
}
 +
}
 
}
 
}
 
}
 
}
Line 163: Line 183:
  
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
Line 170: Line 191:
 
function xwOutputHook() {
 
function xwOutputHook() {
  
global $wgUser, $wgOut, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug;
+
global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwUserName;
+
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit, $action;
global $xwView, $xwPreview, $xwSave, $xwEdit, $xwRaw, $xwIsProperties, $xwIsSystem;
+
if ($action == 'raw') return;
 
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
 
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
  
Line 179: Line 200:
 
$wgOut->output();
 
$wgOut->output();
  
# If saving, execute the transforms in the onChange list
+
# Editing article
if ($xwSave) {
 
foreach (xwGetListByTagname($xwArticleProperties, $tListName = 'onChange') as $tName)
 
xwApplyTransform($tName, $xwArticle, $xwArticleTitle, $xwArticleProperties, $tListName);
 
}
 
 
 
# If editing an empty sys:article or xml:article, add default content
 
 
if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) {
 
if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) {
 +
# If editing an empty sys:article or xml:article, add default content
 
if ($xwIsSystem) {
 
if ($xwIsSystem) {
 
$xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n";
 
$xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n";
 
$xwArticle .= "<!DOCTYPE xmlwiki:user SYSTEM \"xmlwiki-user.dtd\">\n";
 
$xwArticle .= "<!DOCTYPE xmlwiki:user SYSTEM \"xmlwiki-user.dtd\">\n";
 
$xwArticle .= "<user>\n\t<groups></groups>\n</user>\n".$m[2];
 
$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) {
 
elseif ($xwIsProperties) {
$xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n";
+
$a = xwArticleContent( str_replace( 'Xml:', '', $xwArticleTitle ) );
$xwArticle .= "<!DOCTYPE xmlwiki:properties SYSTEM \"xmlwiki-properties.dtd\">\n";
+
if ( preg_match("/\\[\\[Category:(.+?)]]/", $a, $n) && $xwArticle = xwArticleProperties('Category:'.$n[1]) ) {
$xwArticle .= "<properties>\n\t<read>anyone</read>\n\t<write>$xwUserName</write>\n";
+
xwUndomificateArticle( $xwArticle );
$xwArticle .= "\t<data></data>\n\t<view></view>\n\t<onChange></onChange>\n</properties>\n".$m[2];
+
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' );
 
}
 
}
  
# Insert messages into final html and output
+
if ( $xwDebug ) {
if ($xwDebug) {
+
global $xwParserHookCalled, $xwSkinHookCalled;
if (!$xwParserHookCalled) xwMessage('PARSER-HOOK was not called','green');
+
if ( !$xwParserHookCalled ) xwMessage( 'PARSER-HOOK was not called', 'green' );
if (!$xwSkinHookCalled) xwMessage('SKIN-HOOK was not called','green');
+
if ( !$xwSkinHookCalled ) xwMessage( 'SKIN-HOOK was not called', 'green' );
if (is_object($xwArticleProperties)) xwMessage($xwArticleProperties->dump_mem(true),'purple');
 
 
}
 
}
xwAddMessages();
+
 
 +
# 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;
 
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 $xwArticleTitle, $xwArticle, $xwArticleProperties;
+
# Save clears all output, but we need to parse it for save-transforms
global $xwRaw, $xwParserHookCalled, $xwDebug, $xwReadable, $xwLanguage;
+
if ( $xwSave ) $text = $_REQUEST['wpTextbox1'];
if ($xwDebug) xwMessage('PARSER-HOOK:','green');
 
$xwParserHookCalled = true;
 
  
 
# If this text-fragment is our article, apply transforms
 
# If this text-fragment is our article, apply transforms
if (strncmp($text, $xwArticle, 100) == 0 or strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0) {
+
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 ( $xwDebug ) xwMessage( "Matching text-fragment intercepted." );
if (!$xwReadable) { xwMessage('Sorry, article not readable!','red'); return $text = false; }
+
if ( !$xwReadable ) {
 +
xwMessage( 'Sorry, article not readable!', 'red' );
 +
$text = $xwArticle = "<a href=\"$xwScript?title=Special:Userlogin\">Please Login</a>";
 +
return false;
 +
}
 
# Apply data-transforms
 
# Apply data-transforms
 
# - domificate first if xml
 
# - domificate first if xml
 
# - undomificate after if still an object
 
# - undomificate after if still an object
if (preg_match("/^<\\?xml/i", $text)) xwDomificateArticle($text, 'ParserHook/text');
+
if ( !is_object($xwArticle) and preg_match("/^<\\?xml/i", $text) ) xwDomificateArticle( $text, 'PreParserHook/text' );
if (is_object($xwArticleProperties)) {
+
xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK' );
$docType = $xwArticleProperties->doctype();
+
xwUndomificateArticle( $text, $xwArticleTitle );  
$root = $xwArticleProperties->document_element();
 
# If xmlwiki:properties doctype, apply data-transforms
 
if (ereg('^xmlwiki:properties', $docType->name)) {
 
if ($xwDebug) xwMessage('Applying "data" transforms.');
 
while (count($tNodes = $root->get_elements_by_tagname('data'))) {
 
$tName = $tNodes[0]->get_content();
 
xwApplyTransform($tName, $text, $xwArticleTitle, $xwArticleProperties, 'data');
 
$tNodes[0]->unlink_node();
 
}
 
}
 
else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red');
 
}
 
if (is_object($text)) xwUndomificateArticle($text, $xwArticleTitle);  
 
 
}
 
}
elseif ($xwDebug) xwMessage('Parsing other content');
+
elseif ( $xwDebug ) xwMessage( 'Parsing other content' );
+
# Get language and return true back to parser if lang wasn't ours
if ($xwDebug) xwMessage('Content language determined as '.($xwLanguage ? strtoupper($xwLanguage) : 'WIKI'));
+
xwGetProperty( $xwArticleProperties, 'language', $language );
# Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours
+
if ($xwDebug) xwMessage( 'Content language determined as '.($language ? strtoupper($language) : 'WIKI') );
return $xwLanguage == '';
+
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 = '';
 
}
 
}
+
 
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
Line 257: 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, $xwArticleTitle, $xwArticleProperties;
+
global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw;
 
global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;
 
global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;
  
 
# Extract article from existing template structure
 
# Extract article from existing template structure
if ($xwDebug) xwMessage('SKIN-HOOK:','green');
+
if ( $xwDebug ) xwMessage( 'SKIN-HOOK:', 'green' );
 
$xwSkinHookCalled = true;
 
$xwSkinHookCalled = true;
 
$xwTemplate = $tmpl;
 
$xwTemplate = $tmpl;
if ($xwReadable) $xwArticle = $xwTemplate->data['bodytext'];
+
if ( $xwReadable ) $xwArticle = $xwTemplate->data['bodytext'];
  
 
# Apply view-transforms
 
# Apply view-transforms
Line 272: Line 329:
 
# - "default-skin.xslt" transforms XML to HTML with table-layout
 
# - "default-skin.xslt" transforms XML to HTML with table-layout
 
# - "default-skin.css" transforms HTML with design
 
# - "default-skin.css" transforms HTML with design
if (is_object($xwArticleProperties)) {
+
xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK' );
$docType = $xwArticleProperties->doctype();
 
$root = $xwArticleProperties->document_element();
 
# If xmlwiki:properties doctype, apply view-transforms
 
if (ereg('^xmlwiki:properties', $docType->name)) {
 
if ($xwDebug) xwMessage('Applying "view" transforms.');
 
while (count($tNodes = $root->get_elements_by_tagname('view'))) {
 
$tName = $tNodes[0]->get_content();
 
xwApplyTransform($tName, $xwArticle, $xwArticleTitle, $xwArticleProperties, 'view');
 
$tNodes[0]->unlink_node();
 
}
 
}
 
else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red');
 
}
 
 
}
 
}
 +
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# 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 311: Line 382:
 
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 ) {
if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt';
+
if ( is_object($article) ) return 'xml';
if (eregi('^(sys)|(xml):.+$', $title) || ereg('^<\\?xml', $article)) return 'xml';
+
if ( eregi('\\.php$', $title) ) return 'php';
if (eregi('\\.php$', $title)) return 'php';
+
if ( eregi('\\.css$', $title) ) return 'css';
if (eregi('\\.css$', $title)) return 'css';
+
if ( eregi('^talk:', $title) ) return '';
if (eregi('\\.as$', $title)) return 'actionscript';
+
if ( preg_match("/^<\\?xml.+?\\?>.*<xsl:stylesheet/is", $article) ) return 'xslt';
if (eregi('\\.py$', $title)) return 'python';
+
if ( eregi('^(sys|xml):.+$', $title) || ereg('^<\\?xml', $article) ) return 'xml';
if (eregi('\\.java$', $title)) return 'java';
+
if ( eregi('^<\\?html', $article ) || eregi('\\.html$', $title)) return 'html4strict';
if (eregi('\\.((cpp)|(h))$', $title)) return 'cpp';
+
if ( eregi('\\.p[lm]$', $title) ) return 'perl';
if (eregi('\\.js$', $title)) return 'javascript';
+
if ( ereg('^#![/a-zA-Z0-9]+\\/perl', $article) ) return 'perl';
if (eregi('\\.css$', $title)) return 'css';
+
if ( eregi('\\.as$', $title) ) return 'actionscript';
if (eregi('^<\\?html', $article)) return 'html4strict';
+
if ( eregi('\\.dtd$', $title) ) return 'xml';
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
+
if ( eregi('\\.tex$', $title) ) return 'tex';
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
+
        if ( eregi('\\.nt$', $title) ) return 'dna';
if (eregi('\\.dtd$', $title)) return 'xml';
+
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 '';
 
}
 
}
  
Line 337: Line 428:
 
if ($xwDebug) xwMessage("xwDomificateArticle(\"$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
Line 357: Line 452:
 
# 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;
 +
}
 +
 +
 +
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# 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
 
# Apply a transform
function xwApplyTransform($transform, &$article, $title, &$properties, $listName) {
+
function xwApplyTransform($transform, &$article, &$properties, &$title, $event) {
 
global $xwDebug;
 
global $xwDebug;
if ($transform) {
+
if ( $transform ) {
if ($tText = xwArticleContent($transform)) {
+
if ( $tText = xwArticleContent($transform) ) {
$tType = xwArticleType($transform, $tText);
+
# Get transform language (take a guess if none specified)
if ($tType == 'xslt') xwApplyXSLT($article, $tText, $transform);
+
$tProperties = xwArticleProperties($transform);
elseif ($tType == 'php') xwApplyPHP($article, $tText, $transform, $title, $properties, $listName);
+
xwGetProperty( $tProperties, 'language', $tLang );
elseif ($tType == 'css') xwApplyCSS($transform);
+
if ( !$tLang ) $tLang = xwArticleType( $transform, $tText );
elseif ($tType == 'xml') xwMergeDOM($properties, xwDomificateArticle($tText, $transform));
+
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\" found in \"$listName\" list!", 'red');
+
else xwMessage( "Unknown transform \"$transform\" attached to \"$event\" event!", 'red' );
 
}
 
}
 
}
 
}
  
 
# Apply PHP-transform if article perms are only writable by admin or dev
 
# Apply PHP-transform if article perms are only writable by admin or dev
function xwApplyPHP(&$article, &$code, $tName, $title, &$properties, $listName) {
+
function xwApplyPHP( &$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event ) {
 +
global $xwDebug;
 +
if ( $xwDebug ) xwMessage( "xwApplyPHP( $title , $tTitle )", 'blue' );
 
# Get transform perms
 
# Get transform perms
if ($tProperties = xwArticleContent($id = 'xml:'.ucwords($tName))) xwDomificateArticle($tProperties, $id);
+
if ( !count( preg_grep( '/^(admin)|(dev)$/i', xwGetListByTagname( $tProperties, 'write' ) ) ) ) {
if (!count(preg_grep('/^(admin)|(dev)$/i', xwGetListByTagname($tProperties, 'write')))) {
+
xwMessage( "Failed to execute \"$tTitle\", must be writable only by dev or admin!", 'red' );
xwMessage('PHP-Transform will not execute unless it is writable by admin or dev only.','red');
 
 
return;
 
return;
 
}
 
}
 
# Permissions ok, execute the transform code and trap output
 
# Permissions ok, execute the transform code and trap output
global $_SERVER;
+
$path = '/properties/'.preg_replace( "/\\.\\w+$/", '', $tTitle );
$path = '/properties/'.preg_replace("/\\.\\w+$/",'',$tName);
+
$script = $GLOBALS['xwScript'];
$script = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
 
 
ob_start();
 
ob_start();
eval("?>$code<?");
+
eval( "?>$tCode<?" );
xwExtractMessages();
+
xwExtractMessages( $tTitle );
 
ob_end_clean();
 
ob_end_clean();
 
}
 
}
  
 
# Append CSS stylesheet link to output
 
# Append CSS stylesheet link to output
function xwApplyCSS($cssName) {
+
function xwApplyCSS($title, $tName) {
 
global $xwDebug, $xwStyleSheets;
 
global $xwDebug, $xwStyleSheets;
if ($xwDebug) xwMessage("xwApplyCSS($cssName)",'blue');
+
if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue');
$xwStyleSheets[] = $cssName;
+
$xwStyleSheets[] = $tName;
 
}
 
}
  
 
# Apply XSLT to article-dom
 
# Apply XSLT to article-dom
# - if output-method is html, article will get stringified
+
# - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
function xwApplyXSLT(&$article, &$xslt, $id = '') {
+
# - if output-method is html, article will get stringified and language updated
global $xwDebug;
+
function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) {
if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$id)",'blue');
+
global $xwDebug, $xwStyleSheets;
ob_start();
+
if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue');
if ($tObject = domxml_xslt_stylesheet($xslt)) {
+
if (is_object($article)) {
$tResult = $tObject->process($article);
+
ob_start();
# If output-method is html, use XSLT's dump_mem to create an html-string
+
if ($tObject = domxml_xslt_stylesheet($xslt)) {
if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt))
+
$tResult = $tObject->process($article);
$tResult = $tObject->result_dump_mem($tResult);
+
# 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;
 
}
 
}
xwExtractMessages();
+
else $xwStyleSheets[] = $tName;
ob_end_clean();
 
if (isset($tResult)) $article = $tResult; else return false;
 
return true;
 
 
}
 
}
 +
 +
 +
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# DOM FUNCTIONS
  
 
# Return result set of an XPath query on passed DOM-object
 
# Return result set of an XPath query on passed DOM-object
Line 424: Line 586:
 
if (is_object($dom)) {
 
if (is_object($dom)) {
 
global $xwDebug;
 
global $xwDebug;
if ($xwDebug) xwMessage("xwXPathQuery(\"$query\")",'blue');
+
if ($xwDebug) xwMessage("xwXPathQuery( $query )",'blue');
 
ob_start();
 
ob_start();
$context = $dom->xpath_new_context();  
+
if (php(4)) {
if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset;
+
$context = $dom->xpath_new_context();  
elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red');
+
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();
 
xwExtractMessages();
 
ob_end_clean();
 
ob_end_clean();
 
}
 
}
return $result;
+
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
 
# - Syntax:  xpath:XPathQuery:[[@]node][+] = value
Line 439: Line 619:
 
# - if "+" exists, content is appended, else replaced
 
# - if "+" exists, content is appended, else replaced
 
function xwSetProperty(&$properties, $query, $value) {
 
function xwSetProperty(&$properties, $query, $value) {
if (!is_object($properties) or !preg_match("/^xpath:(.+?):(@?)(\\w*?)(\\+?)$/", $query, $match)) return false;
+
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;
 
list(, $xpath, $att, $node, $append) = $match;
if (!is_array($results = xwXPathQuery($properties, $xpath))) $results = array();
+
foreach (xwXPathQuery($properties, $xpath) as $result) {
foreach ($results as $result) {
 
 
if ($node) {
 
if ($node) {
 
# create new element/attribute in result-node
 
# create new element/attribute in result-node
if ($att) $node = $properties->create_attribute($node, $value);
+
if ($att) {
 +
if (php(4)) $result->append_child($properties->create_attribute($node, $value));
 +
else $result->setAttribute($node, $value);
 +
}
 
else {
 
else {
$node = $properties->create_element($node);
+
if (php(4)) {
$node->set_content($value);
+
$node = $properties->create_element($node);
 +
$node->set_content($value);
 +
$result->append_child($node);
 +
}
 +
else $result->appendChild($properties->createElement($node, $value));
 
}
 
}
$result->append_child($node);
 
 
}
 
}
 
else {
 
else {
 
# node not set, replace or append current-result-value
 
# node not set, replace or append current-result-value
if ($append) $result->set_content($value);
+
if ($append) {
 +
if (php(4)) $result->set_content( ($result->get_content()).$value );
 +
else $result->nodeValue .= $value;
 +
}
 
else {
 
else {
$newnode = $properties->create_element($result->tagname);
+
if (php(4)) {
$newnode->set_content($value);
+
$newnode = $properties->create_element($result->tagname);
$result->replace_node($newnode);
+
$newnode->set_content($value);
 +
$result->replace_node($newnode);
 +
}
 +
else $result->parentNode->replaceChild($properties->createElement($result->nodeName, $value), $result);
 
}
 
}
 
}
 
}
Line 464: Line 664:
 
return true;
 
return true;
 
}
 
}
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# UTILITY FUNCTIONS
 
  
 
# Return array of comma-separated-items in element content
 
# Return array of comma-separated-items in element content
Line 474: Line 670:
 
$list = array();
 
$list = array();
 
if (is_object($xml)) {
 
if (is_object($xml)) {
$root = $xml->document_element();  
+
if (php(4)) {
foreach ($root->get_elements_by_tagname($tag) as $element)
+
$root = $xml->document_element();  
if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv));
+
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;
 
return $list;
Line 483: Line 685:
 
# Merge the two passed DOM objects
 
# Merge the two passed DOM objects
 
# - appends, does not overwrite
 
# - appends, does not overwrite
function xwMergeDOM($dom1, $dom2) {
+
function xwMergeDOM(&$dom1, &$dom2, $id1 = 'dom1', $id2 = 'dom2') {
 
global $xwDebug;
 
global $xwDebug;
if ($xwDebug) xwMessage('xwMergeDOM()','blue');
 
 
# Return if first DOM not a valid properties article
 
# Return if first DOM not a valid properties article
 
if (!is_object($dom1)) return false;
 
if (!is_object($dom1)) return false;
$docType = $dom1->doctype();
+
if (php(4)) $docType = $dom1->doctype(); else $docType = $dom1->doctype;
 
if (!ereg('^xmlwiki:properties', $docType->name)) {
 
if (!ereg('^xmlwiki:properties', $docType->name)) {
 
if ($xwDebug) xwMessage('xwMergeDOM: First parameter is not an xmlwiki:properties object!','red');
 
if ($xwDebug) xwMessage('xwMergeDOM: First parameter is not an xmlwiki:properties object!','red');
Line 495: Line 696:
 
# Return DOM1 if DOM2 not valid properties
 
# Return DOM1 if DOM2 not valid properties
 
if (!is_object($dom2)) return $dom1;
 
if (!is_object($dom2)) return $dom1;
$docType = $dom2->doctype();
+
if (php(4)) $docType = $dom2->doctype(); else $docType = $dom2->doctype;
 
if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1;
 
if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1;
 
# Both are ok, perform the merge
 
# Both are ok, perform the merge
if ($xwDebug) xwMessage('xwMergeDOM()','blue');
+
if ($xwDebug) xwMessage("xwMergeDOM( &$id1 , $id2 )",'blue');
$root1 = $dom1->document_element();
+
if (php(4)) {
$root2 = $dom2->document_element();
+
$root1 = $dom1->document_element();
foreach ($root2->child_nodes() as $node) {
+
$root2 = $dom2->document_element();
$root1->append_child($node->clone_node(true));
+
foreach ($root2->child_nodes() as $node) $root1->append_child($node->clone_node(true));
 
}
 
}
return $dom1;
+
else $dom1->documentElement->appendChild($dom1->importNode($dom2->documentElement, true));
 +
return true;
 
}
 
}
  
# Append messages to content
+
# Remove all occourences of a certain element
function xwAddMessages() {
+
function xwRemoveElement( &$properties, $element ) {
global $xwArticle, $xwMessages, $xwMsgToken;
+
$root = $properties->document_element();
if (count($xwMessages)) {
+
if (php(4)) {
$msg = '<div class="portlet" id="p-xwmessages"><h5>There are Messages</h5></div>';
+
while ( count($tNodes = $root->get_elements_by_tagname($element)) ) $tNodes[0]->unlink_node();
$msg .= '<div class="xwmessage"><ul><li>'.join("</li><li>", $xwMessages).'</li></ul></div>';
+
}
$xwArticle = ereg_replace($xwMsgToken, $msg, $xwArticle);
+
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
 
# Extract error-messages from captured output and put in proper message-queue
function xwExtractMessages() {
+
function xwExtractMessages($where = false) {
#if (preg_matcheval()'d code on line 2
+
if ($where) $where = " of \"$where\"";
 
$err = preg_replace("/<.+?>/", "", ob_get_contents());
 
$err = preg_replace("/<.+?>/", "", ob_get_contents());
#$err = preg_replace('/Warning.+?\\(\\): /', '', $err);
 
 
foreach (split("\n", $err) as $msg) if (trim($msg)) {
 
foreach (split("\n", $err) as $msg) if (trim($msg)) {
if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1])";
+
if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1]$where)";
$msg = preg_replace('/ in .+? on line.+?[0-9]+/', '', $msg);
+
$msg = preg_replace('/ in .+? on line.+?([0-9]+)/', " (Line $1$where)", $msg);
 
if (isset($line)) $msg .= $line;
 
if (isset($line)) $msg .= $line;
 
xwMessage($msg, 'red');
 
xwMessage($msg, 'red');
Line 535: 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 551: 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;
	}