Difference between revisions of "Xmlwiki.php"

From Organic Design wiki
(Allow PERL to be *.pl or *.pm and incl *php3,4,5 *phtml for PHP)
Line 1: Line 1:
<?php
 
 
# xmlWiki - MediaWiki XML Hack
 
# xmlWiki - MediaWiki XML Hack
# Nad - 2005-05-18
+
# Nad - Started: 2005-05-18
 
 
# MEDIAWIKI HOOKS
 
# xwInputHook (post)
 
# xwOutputHook (save)
 
# xwParserHook (data)
 
# xwSkinHook (view, edit)
 
# ARTICLE FUNCTIONS
 
# xwArticleContent
 
# xwArticleType
 
# xwDomificateArticle
 
# xwUndomificateArticle
 
# TRANSFORM FUNCTIONS
 
# xwReduceTransformStack
 
# xwApplyTransform
 
# xwApplyPHP
 
# xwApplyCSS
 
# xwApplyXSLT
 
# DOM FUNCTIONS
 
# xwXPathQuery
 
# xwGetProperty
 
# xwSetProperty
 
# xwGetListByTagname
 
# xwMergeDOM
 
# 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!');
 
if (php(3)) die('xmlWiki cannot run on less than PHP4!');
require_once('extensions/geshi/geshi.php');
+
 
 +
# Add the rest of the hooks
 +
$wgHooks['PreParser'][] = 'xwPreParserHook';
 +
$wgHooks['ParserBeforeStrip'][] = 'xwPreParserHook';
 +
$wgHooks['PostParser'][] = 'xwPostParserHook';
 +
$wgHooks['ParserAfterTidy'][] = 'xwPostParserHook';
 +
$wgHooks['Input'][] = 'xwInputHook';
 +
$wgHooks['Output'][] = 'xwOutputHook';
  
 
# System globals
 
# System globals
Line 71: Line 23:
 
$xwSkinHookCalled = false;
 
$xwSkinHookCalled = false;
 
$xwMsgToken = '<!--xwMsg-->';
 
$xwMsgToken = '<!--xwMsg-->';
 +
$xwCssToken = '<!--xwCss-->';
 +
$xwScript = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
  
 
# User globals
 
# User globals
Line 83: Line 37:
  
 
# 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 89: Line 43:
 
$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 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");
 +
xwDomificateArticle($xwUserSYS, "sys:$xwUserName");
 
$xwUserGroups = array_merge($xwUserGroups, xwGetListByTagname($xwUserSYS, 'groups'));
 
$xwUserGroups = array_merge($xwUserGroups, xwGetListByTagname($xwUserSYS, 'groups'));
  
 
# Read user-prefs
 
# Read user-prefs
xwGetProperty($xwUserSYS, 'debug', $xwDebug);
+
xwGetProperty($xwUserSYS, 'xpath:/user/debug', $xwDebug);
  
 
# Get article-meta-file and extract article-perms
 
# Get article-meta-file and extract article-perms
Line 105: Line 60:
 
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 ($xwArticleProperties = $xwIsProperties ? xwDomificateArticle($xwArticle) : xwArticleProperties($xwArticleTitle))
xwDomificateArticle($xwArticleProperties, $xwIsProperties ? $xwArticleTitle : $id);
+
$xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read');
$xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read') + array('anyone');
+
$xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write');
$xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write') + array('anyone');
 
 
if ($xwIsProperties) $xwArticleProperties = false;
 
if ($xwIsProperties) $xwArticleProperties = false;
 
}
 
}
 +
if (!count($xwArticleReadableBy)) $xwArticleReadableBy = array('anyone');
 +
if (!count($xwArticleWritableBy)) $xwArticleWritableBy = array('anyone');
  
 
# Merge default-properties into article-properties object
 
# Merge default-properties into article-properties object
 
$xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'), 'defaults');
 
$xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'), 'defaults');
if (is_object($xwArticleProperties)) $xwArticleProperties = xwMergeDOM($xwArticleProperties, $xwDefaultProperties);
+
if (is_object($xwArticleProperties)) xwMergeDOM($xwArticleProperties, $xwDefaultProperties);
 
else $xwArticleProperties = $xwDefaultProperties;
 
else $xwArticleProperties = $xwDefaultProperties;
  
# Get language from properties, or articleType()
+
# If no language specified in properties, guess from name and content
 
if (!xwGetProperty($xwArticleProperties, 'language', $xwLanguage))
 
if (!xwGetProperty($xwArticleProperties, 'language', $xwLanguage))
$xwLanguage = xwArticleType($xwArticleTitle, $xwArticle);
+
xwSetProperty($xwArticleProperties, 'language', xwArticleType($xwArticleTitle, $xwArticle));
  
 
# Set perms for this request
 
# Set perms for this request
Line 138: Line 94:
 
$action = 'view';
 
$action = 'view';
 
$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);
 +
}
 
}
 
}
  
Line 148: Line 118:
  
 
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, $xwIsProperties, $xwIsSystem;
 
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 159: Line 129:
  
 
# Scan POST and apply any XPath inputs
 
# Scan POST and apply any XPath inputs
foreach ($_REQUEST as $query => $value) if (ereg('^xpath:.+:', $query))
+
foreach ($_REQUEST as $query => $value) {
xwSetProperty($xwArticleProperties, $query, $value);
+
if (ereg('^xpath.3A.+.3A', $query)) $query = urldecode($query);
 +
if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, $value);
 +
}
  
 
# if it's a pseudo-namespace...
 
# if it's a pseudo-namespace...
 
if ($xwIsProperties || $xwIsSystem) {
 
if ($xwIsProperties || $xwIsSystem) {
# wpSave should be valid XML to be saved
+
# wpTextbox1 should be valid XML to be saved
 
if ($xwSave) {
 
if ($xwSave) {
xwDomificateArticle($wpSave, 'POST-DATA');
+
$tb = $_REQUEST['wpTextbox1'];
if (!is_object($wpSave)) {
+
xwDomificateArticle($tb, 'POST-DATA');
 +
if (!is_object($tb)) {
 
xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red');
 
xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red');
 
$action='edit';
 
$action='edit';
Line 175: Line 148:
  
 
}
 
}
 +
  
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
Line 182: Line 156:
 
function xwOutputHook() {
 
function xwOutputHook() {
  
global $wgUser, $wgOut, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug;
+
# Raw has already output the page, so exit
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwUserName;
+
if ($GLOBALS['xwRaw']) return;
global $xwView, $xwPreview, $xwSave, $xwEdit, $xwRaw, $xwIsProperties, $xwIsSystem;
+
 
 +
global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem;
 +
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit;
 
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
 
if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
  
Line 191: Line 167:
 
$wgOut->output();
 
$wgOut->output();
  
# Saving article: apply transforms in the onChange list
 
if ($xwSave) xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'save');
 
 
 
# Editing article
 
# Editing article
 
if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) {
 
if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) {
Line 201: Line 174:
 
$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) {
Line 206: Line 180:
 
$xwArticle .= "<!DOCTYPE xmlwiki:properties SYSTEM \"xmlwiki-properties.dtd\">\n";
 
$xwArticle .= "<!DOCTYPE xmlwiki:properties SYSTEM \"xmlwiki-properties.dtd\">\n";
 
$xwArticle .= "<properties>\n\t<read>anyone</read>\n\t<write>$xwUserName</write>\n";
 
$xwArticle .= "<properties>\n\t<read>anyone</read>\n\t<write>$xwUserName</write>\n";
$xwArticle .= "\t<data></data>\n\t<view></view>\n\t<edit></edit>\n\t<save></save>\n</properties>\n".$m[2];
+
$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".$m[2];
}
+
xwMessage('Note: There is currently no content for this properties article, default content has been generated','red');
else {
 
# Editing normally, apply transforms in edit-list
 
xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'edit');
 
 
}
 
}
 +
}
 +
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);
 +
 
 +
# 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 $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 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 (preg_match("/^<\\?xml/i", $text)) xwDomificateArticle($text, 'PreParserHook/text');
xwReduceTransformStack($text, $xwArticleProperties, $xwArticleTitle, 'data');
+
xwReduceTransformStack($text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK');
 
if (is_object($text)) xwUndomificateArticle($text, $xwArticleTitle);  
 
if (is_object($text)) xwUndomificateArticle($text, $xwArticleTitle);  
 
}
 
}
 
elseif ($xwDebug) xwMessage('Parsing other content');
 
elseif ($xwDebug) xwMessage('Parsing other content');
 
 
if ($xwDebug) xwMessage('Content language determined as '.($xwLanguage ? strtoupper($xwLanguage) : 'WIKI'));
+
# Get language and return true back to parser if lang wasn't ours
# Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours
+
xwGetProperty($xwArticleProperties, 'language', $language);
return $xwLanguage == '';
+
if ($xwDebug) xwMessage('Content language determined as '.($language ? strtoupper($language) : 'WIKI'));
 +
return $language == '';
 +
}
 +
 
 +
 
 +
# ---------------------------------------------------------------------------------------------------------------------- #
 +
# POST-PARSER HOOK
 +
 
 +
function xwPostParserHook(&$text) {
 +
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');
 +
 
 +
# Insert stylesheets and messages into output
 +
#xwReplaceTokens($text);
 +
 
 +
# Now apply the save-stack
 +
xwReduceTransformStack($text, $xwArticleProperties, $xwArticleTitle, 'save', 'POSTPARSER-HOOK');
 +
 
 +
# Output should be empty for save
 +
$text = '';
 
}
 
}
  
Line 260: Line 268:
 
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;
  
Line 273: Line 281:
 
# - "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
xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'view');
+
xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK');
 
}
 
}
 +
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ARTICLE FUNCTIONS
 
# ARTICLE FUNCTIONS
 +
# - these all use super-globals since they can be called from within evals
  
 
# 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 299: Line 322:
 
if ($articleTitle) $xwArticleCache[$articleTitle] = $article;
 
if ($articleTitle) $xwArticleCache[$articleTitle] = $article;
 
return $article;
 
return $article;
 +
}
 +
 +
# Return properties object from title
 +
function xwArticleProperties($articleTitle) {
 +
global $xwDebug;
 +
if ($xwDebug) xwMessage("xwArticleProperties(\"$articleTitle\")",'blue');
 +
if (!$properties = xwArticleContent($id = 'xml:'.ucfirst($articleTitle), 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 (is_object($article)) return 'xml';
 +
if (eregi('^talk:', $title)) return '';
 
if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt';
 
if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt';
 
if (eregi('^(sys)|(xml):.+$', $title) || ereg('^<\\?xml', $article)) return 'xml';
 
if (eregi('^(sys)|(xml):.+$', $title) || ereg('^<\\?xml', $article)) return 'xml';
if (eregi('\\.php$', $title)) return 'php';
+
if (eregi('\\.tex$', $title)) return 'tex';
 +
if (eregi('\\.[r]$', $title)) return 'r';
 +
if (eregi('\\.ph(tml)|(p[3-5]?)$', $title)) return 'php';
 
if (eregi('\\.css$', $title)) return 'css';
 
if (eregi('\\.css$', $title)) return 'css';
 
if (eregi('\\.as$', $title)) return 'actionscript';
 
if (eregi('\\.as$', $title)) return 'actionscript';
 
if (eregi('\\.py$', $title)) return 'python';
 
if (eregi('\\.py$', $title)) return 'python';
 +
if (eregi('\\.p[lm]$', $title)) return 'perl';
 
if (eregi('\\.java$', $title)) return 'java';
 
if (eregi('\\.java$', $title)) return 'java';
if (eregi('\\.((cpp)|(h))$', $title)) return 'cpp';
+
if (eregi('\\.((c\\+\\+)|(cpp)|(h))$', $title)) return 'cpp';
 
if (eregi('\\.js$', $title)) return 'javascript';
 
if (eregi('\\.js$', $title)) return 'javascript';
 
if (eregi('\\.css$', $title)) return 'css';
 
if (eregi('\\.css$', $title)) return 'css';
if (eregi('^<\\?html', $article)) return 'html4strict';
+
if (eregi('^<\\?html', $article) || eregi('\\.html$', $title)) return 'html4strict';
 
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
 
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
 
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
 
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
 
if (eregi('\\.dtd$', $title)) return 'xml';
 
if (eregi('\\.dtd$', $title)) return 'xml';
 +
return '';
 
}
 
}
  
Line 325: Line 365:
 
if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue');
 
if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue');
 
ob_start();
 
ob_start();
if (php(4)) $dom = domxml_open_mem($article);
+
if ($article) {
else $dom = DOMDocument::loadXML($article);
+
if (php(4)) $dom = domxml_open_mem($article);
if (is_object($dom)) $article = $dom;
+
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 347: Line 389:
 
# 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->dump_mem();
 
return true;
 
return true;
 
}
 
}
Line 356: Line 415:
  
 
# Apply all transforms in passed stack
 
# Apply all transforms in passed stack
function xwReduceTransformStack(&$article, &$properties, $title, $event) {
+
function xwReduceTransformStack(&$article, &$properties, $title, $event, $id = '') {
 
global $xwDebug;
 
global $xwDebug;
if ($xwDebug) xwMessage("Applying \"$event\" transforms.");
+
if ($xwDebug) xwMessage("Applying \"$event\" transforms to \"$title\" from \"$id\".");
 
if (is_object($properties)) {
 
if (is_object($properties)) {
 
if (php(4)) {
 
if (php(4)) {
Line 379: Line 438:
 
while (($tNodes = $root->getElementsByTagname($event)) && $tNodes->length) {
 
while (($tNodes = $root->getElementsByTagname($event)) && $tNodes->length) {
 
$tNode = $tNodes->item(0);
 
$tNode = $tNodes->item(0);
$tNode->parentNode->removeChild($tNodes->item(0));
 
 
xwApplyTransform($tNode->nodeValue, $article, $properties, $title, $event);
 
xwApplyTransform($tNode->nodeValue, $article, $properties, $title, $event);
 +
$tNode->parentNode->removeChild($tNode);
 
}
 
}
 
}
 
}
Line 389: Line 448:
  
 
# Apply a transform
 
# Apply a transform
function xwApplyTransform($transform, &$article, &$properties, $title, $event) {
+
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, $event);
+
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 \"$event\" list!", 'red');
+
else xwMessage("Unknown transform \"$transform\" attched 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, $event) {
+
function xwApplyPHP(&$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event) {
 
global $xwDebug, $_SERVER;
 
global $xwDebug, $_SERVER;
if ($xwDebug) xwMessage("xwApplyCSS($tName)",'blue');
+
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('PHP-Transform will not execute unless it is writable by admin or dev only.','red');
 
xwMessage('PHP-Transform will not execute unless it is writable by admin or dev only.','red');
Line 414: Line 475:
 
}
 
}
 
# Permissions ok, execute the transform code and trap output
 
# Permissions ok, execute the transform code and trap output
$path = '/properties/'.preg_replace("/\\.\\w+$/",'',$tName);
+
$path = '/properties/'.preg_replace("/\\.\\w+$/",'',$tTitle);
$script = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
+
$script = $GLOBALS['xwScript'];
 
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($tName) {
+
function xwApplyCSS($title, $tName) {
 
global $xwDebug, $xwStyleSheets;
 
global $xwDebug, $xwStyleSheets;
if ($xwDebug) xwMessage("xwApplyCSS($tName)",'blue');
+
if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue');
 
$xwStyleSheets[] = $tName;
 
$xwStyleSheets[] = $tName;
 
}
 
}
Line 431: Line 492:
 
# Apply XSLT to article-dom
 
# 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 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 xwLanguage updated
+
# - if output-method is html, article will get stringified and language updated
function xwApplyXSLT(&$article, &$xslt, $tName) {
+
function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) {
 
global $xwDebug, $xwStyleSheets;
 
global $xwDebug, $xwStyleSheets;
if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$tName)",'blue');
+
if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue');
 
if (is_object($article)) {
 
if (is_object($article)) {
 
ob_start();
 
ob_start();
Line 442: Line 503:
 
if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt)) {
 
if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt)) {
 
$tResult = $tObject->result_dump_mem($tResult);
 
$tResult = $tObject->result_dump_mem($tResult);
global $xwLanguage;
+
xwSetProperty($properties, 'language', '');
$xwLanguage = '';
 
 
}
 
}
 
}
 
}
Line 462: Line 522:
 
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();
 
if (php(4)) {
 
if (php(4)) {
Line 469: Line 529:
 
elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red');
 
elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red');
 
}
 
}
else {
+
else if ($xpath = new DOMXPath($dom)) $result = $xpath->query($query);
$xpath = new DOMXPath($dom);
 
$result = $xpath->query($query);
 
}
 
 
xwExtractMessages();
 
xwExtractMessages();
 
ob_end_clean();
 
ob_end_clean();
 
}
 
}
if (!is_array($result)) return array();
+
if (php(4)) return is_array($result) ? $result : array();
return $result;
+
else {
 +
$tmp = array();
 +
foreach ($result as $node) $tmp[] = $node;
 +
return $tmp;
 +
}
 
}
 
}
  
function xwGetProperty(&$properties, $property, &$value) {
+
function xwGetProperty(&$properties, $query, &$value) {
if (count($results = xwXPathQuery($properties, "/properties/$property")) == 0) return false;
+
global $xwDebug;
$value = $results[0]->get_content();
+
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;
 
return true;
 
}
 
}
Line 490: Line 555:
 
# - if "+" exists, content is appended, else replaced
 
# - if "+" exists, content is appended, else replaced
 
function xwSetProperty(&$properties, $query, $value) {
 
function xwSetProperty(&$properties, $query, $value) {
 +
global $xwDebug;
 +
if ($xwDebug) xwMessage("xwSetProperty( $query , $value )", 'purple');
 
if (!is_object($properties)) return false;
 
if (!is_object($properties)) return false;
if (!preg_match("/^xpath:(.+?):(@?)(\\w*?)(\\+?)$/", $query, $match))
+
if (!preg_match("/^xpath:(.+):(@?)(\\w*?)(\\+?)$/", $query, $match)) {
$match = array('', '/properties', '', $query, '' );
+
# 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;
 
foreach (xwXPathQuery($properties, $xpath) as $result) {
 
foreach (xwXPathQuery($properties, $xpath) 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 $properties->replaceChild($properties->createElement($result->nodeName, $value), $result);
 
}
 
}
 
}
 
}
Line 537: Line 621:
 
# 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;
Line 552: Line 635:
 
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');
 
if (php(4)) {
 
if (php(4)) {
 
$root1 = $dom1->document_element();
 
$root1 = $dom1->document_element();
Line 559: Line 642:
 
}
 
}
 
else $dom1->documentElement->appendChild($dom1->importNode($dom2->documentElement, true));
 
else $dom1->documentElement->appendChild($dom1->importNode($dom2->documentElement, true));
return $dom1;
+
return true;
 
}
 
}
  
Line 565: Line 648:
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# UTILITY FUNCTIONS
 
# UTILITY FUNCTIONS
 
# Append messages to content
 
function xwAddMessages() {
 
global $xwArticle, $xwMessages, $xwMsgToken;
 
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>';
 
$xwArticle = ereg_replace($xwMsgToken, $msg, $xwArticle);
 
}
 
}
 
  
 
# 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 594: Line 666:
 
$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 614: Line 704:
 
# - needed due to different DOM implimentation on 4 and 5 (and none on 3)
 
# - needed due to different DOM implimentation on 4 and 5 (and none on 3)
 
function php($ver) { return substr(phpversion(),0,1) == "$ver"; }
 
function php($ver) { return substr(phpversion(),0,1) == "$ver"; }
 
?>
 

Revision as of 02:38, 8 November 2005

  1. xmlWiki - MediaWiki XML Hack
  2. Nad - Started: 2005-05-18
  1. Exit if not included from index.php

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

  1. Add the rest of the hooks

$wgHooks['PreParser'][] = 'xwPreParserHook'; $wgHooks['ParserBeforeStrip'][] = 'xwPreParserHook'; $wgHooks['PostParser'][] = 'xwPostParserHook'; $wgHooks['ParserAfterTidy'][] = 'xwPostParserHook'; $wgHooks['Input'][] = 'xwInputHook'; $wgHooks['Output'][] = 'xwOutputHook';

  1. System globals

$xwDebug = false; $xwMessages = array(); $xwArticleCache = array(); $xwStyleSheets = array(); $xwTemplate = null; $xwParserHookCalled = false; $xwSkinHookCalled = false; $xwMsgToken = ; $xwCssToken = ; $xwScript = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];

  1. User globals

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

  1. Article globals

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

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

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

  1. Read user-prefs

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

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

if ($xwIsSystem) $xwArticleReadableBy = $xwArticleWritableBy = array('admin'); else { # Get xml:article (self if already xml:*) if ($xwArticleProperties = $xwIsProperties ? xwDomificateArticle($xwArticle) : xwArticleProperties($xwArticleTitle)) $xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read'); $xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write'); if ($xwIsProperties) $xwArticleProperties = false; } if (!count($xwArticleReadableBy)) $xwArticleReadableBy = array('anyone'); if (!count($xwArticleWritableBy)) $xwArticleWritableBy = array('anyone');

  1. Merge default-properties into article-properties object

$xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'), 'defaults'); if (is_object($xwArticleProperties)) xwMergeDOM($xwArticleProperties, $xwDefaultProperties); else $xwArticleProperties = $xwDefaultProperties;

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

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

  1. Set perms for this request

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

  1. Divert to access-denied article if not readable

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

  1. Handle security for Move

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


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

function xwInputHook() {

global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST; global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle, $xwIsProperties, $xwIsSystem; 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, $value); }

# if it's a pseudo-namespace... if ($xwIsProperties || $xwIsSystem) { # wpTextbox1 should be valid XML to be saved if ($xwSave) { $tb = $_REQUEST['wpTextbox1']; xwDomificateArticle($tb, 'POST-DATA'); if (!is_object($tb)) { xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red'); $action='edit'; } } }

}


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

function xwOutputHook() {

# Raw has already output the page, so exit if ($GLOBALS['xwRaw']) return;

global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem; global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit; 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) { $xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $xwArticle .= "<!DOCTYPE xmlwiki:properties SYSTEM \"xmlwiki-properties.dtd\">\n"; $xwArticle .= "<properties>\n\t<read>anyone</read>\n\t<write>$xwUserName</write>\n"; $xwArticle .= "\t<language></language>\n\t<category></category>\n\t\n\t<view></view>\n\t<edit></edit>\n\t<save></save>\n</properties>\n".$m[2]; xwMessage('Note: There is currently no content for this properties article, default content has been generated','red'); } } 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);

# Output final result print $xwArticle; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. PRE-PARSER HOOK

function xwPreParserHook(&$text) { global $xwArticleTitle, $xwArticle, $xwArticleProperties, $xwSave; global $xwParserHookCalled, $xwDebug, $xwReadable, $xwScript; if ($xwDebug) xwMessage('PREPARSER-HOOK:','green'); $xwParserHookCalled = true;

# Save clears all output, but we need to parse it for save-transforms if ($xwSave) $text = $_REQUEST['wpTextbox1'];

# If this text-fragment is our article, apply transforms if ($xwSave or 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 (preg_match("/^<\\?xml/i", $text)) xwDomificateArticle($text, 'PreParserHook/text'); xwReduceTransformStack($text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK'); if (is_object($text)) xwUndomificateArticle($text, $xwArticleTitle); } elseif ($xwDebug) xwMessage('Parsing other content');

# Get language and return true back to parser if lang wasn't ours xwGetProperty($xwArticleProperties, 'language', $language); if ($xwDebug) xwMessage('Content language determined as '.($language ? strtoupper($language) : 'WIKI')); return $language == ; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. POST-PARSER HOOK

function xwPostParserHook(&$text) { 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');

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

# Now apply the save-stack xwReduceTransformStack($text, $xwArticleProperties, $xwArticleTitle, 'save', 'POSTPARSER-HOOK');

# Output should be empty for save $text = ; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. SKIN HOOK
  1. If attempted xml encountered, try to domificate and transform
  2. - Raw requests don't get here

function xwSkinHook(&$tmpl) {

global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw; global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;

# Extract article from existing template structure if ($xwDebug) xwMessage('SKIN-HOOK:','green'); $xwSkinHookCalled = true; $xwTemplate = $tmpl; if ($xwReadable) $xwArticle = $xwTemplate->data['bodytext'];

# Apply view-transforms # - "default-skin.php" builds XML output structure # - "default-skin.xslt" transforms XML to HTML with table-layout # - "default-skin.css" transforms HTML with design xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK'); }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. ARTICLE FUNCTIONS
  3. - these all use super-globals since they can be called from within evals
  1. Retreive wiki-article as raw text

function xwArticleContent($articleTitle, $secure = true) { global $xwArticleCache, $xwDebug, $xwIsAdmin, $xwUserGroups; if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue');

# Temp: quick fix for slack security model :-( # - should combine article and properties into getContent() if ($secure and !$xwIsAdmin) { if ($properties = new Article(Title::newFromText('xml:'.ucwords($articleTitle)))) $properties = $properties->getContentWithoutUsingSoManyDamnGlobals(); $readableBy = xwGetListByTagname(xwDomificateArticle($properties), 'read') + array('anyone'); if (!count(array_intersect($readableBy, $xwUserGroups))) { xwMessage("Sorry article \"$articleTitle\" not readable!",'red'); return "Please Login"; } }

# Return with results if already cached if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle]; # Get wiki article content (or use global if no article passed) # - using getContentWithoutUsingSoManyDamnGlobals() because getContent() fucks up nested-article-reads if ($article = new Article(Title::newFromText($articleTitle))) $article = $article->getContentWithoutUsingSoManyDamnGlobals(); if ($article == ) { if ($xwDebug) xwMessage("Article \"$articleTitle\" not found."); return false; } elseif ($article == '(There is currently no text in this page)') { if ($xwDebug) xwMessage("Article \"$articleTitle\" not found."); return false; } if ($articleTitle) $xwArticleCache[$articleTitle] = $article; return $article; }

  1. Return properties object from title

function xwArticleProperties($articleTitle) { global $xwDebug; if ($xwDebug) xwMessage("xwArticleProperties(\"$articleTitle\")",'blue'); if (!$properties = xwArticleContent($id = 'xml:'.ucfirst($articleTitle), false)) $properties = '<?xml version="1.0" standalone="yes"?> <!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd"> <properties/>'; return xwDomificateArticle($properties, $id); }

  1. Decide kind of article from content and title

function xwArticleType($title, $article) { if (is_object($article)) return 'xml'; if (eregi('^talk:', $title)) return ; if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt'; if (eregi('^(sys)|(xml):.+$', $title) || ereg('^<\\?xml', $article)) return 'xml'; if (eregi('\\.tex$', $title)) return 'tex'; if (eregi('\\.[r]$', $title)) return 'r'; if (eregi('\\.ph(tml)|(p[3-5]?)$', $title)) return 'php'; if (eregi('\\.css$', $title)) return 'css'; if (eregi('\\.as$', $title)) return 'actionscript'; if (eregi('\\.py$', $title)) return 'python'; if (eregi('\\.p[lm]$', $title)) return 'perl'; if (eregi('\\.java$', $title)) return 'java'; if (eregi('\\.((c\\+\\+)|(cpp)|(h))$', $title)) return 'cpp'; if (eregi('\\.js$', $title)) return 'javascript'; if (eregi('\\.css$', $title)) return 'css'; if (eregi('^<\\?html', $article) || eregi('\\.html$', $title)) return 'html4strict'; if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl'; if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash'; if (eregi('\\.dtd$', $title)) return 'xml'; return ; }

  1. Convert passed article to a DOM object
  2. - Article is unchanged if not valid XML

function xwDomificateArticle(&$article, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue'); ob_start(); if ($article) { if (php(4)) $dom = domxml_open_mem($article); else $dom = DOMDocument::loadXML($article); } if (isset($dom) && is_object($dom)) $article = $dom; else { # Could not convert, extract error messages from output if ($xwDebug) xwMessage("Failed :-(", 'red'); xwExtractMessages(); } ob_end_clean(); return $article; }

  1. Reduce article to a string if it's a DOM object
  2. - ie if all xslt's had xml-output-method

function xwUndomificateArticle(&$article, $id = ) { if (!is_object($article)) return false; global $xwDebug; if ($xwDebug) xwMessage("xwUndomificate(\"$id\")",'blue'); # TODO: # Validate, report errors # xwApplyXSLT($article, $xslt) if xml referrs one # Convert DOM back to XML if still an object if (php(4)) { # Attempt to fix PHP4's crap dump $xml = "<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $children = $article->child_nodes(); $xml .= trim($article->dump_node($children[0]))."\n"; for ($i = 1; $i < count($children); $i++) { $top = $children[$i]; $name = $top->node_name(); $xml .= "<$name>\n"; foreach ($top->child_nodes() as $child) { $line = trim($article->dump_node($child)); if ($line) $xml .= "\t$line\n"; } $xml .= "</$name>\n"; } $article = $xml; } else $article = $article->dump_mem(); return true; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. TRANSFORM FUNCTIONS
  1. Apply all transforms in passed stack

function xwReduceTransformStack(&$article, &$properties, $title, $event, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("Applying \"$event\" transforms to \"$title\" from \"$id\"."); if (is_object($properties)) { if (php(4)) { $docType = $properties->doctype(); $root = $properties->document_element(); } else { $docType = $properties->doctype; $root = $properties->documentElement; } if (ereg('^xmlwiki:properties', $docType->name)) { if (php(4)) { while (count($tNodes = $root->get_elements_by_tagname($event))) { $tName = $tNodes[0]->get_content(); xwApplyTransform($tName, $article, $properties, $title, $event); $tNodes[0]->unlink_node(); } } else { while (($tNodes = $root->getElementsByTagname($event)) && $tNodes->length) { $tNode = $tNodes->item(0); xwApplyTransform($tNode->nodeValue, $article, $properties, $title, $event); $tNode->parentNode->removeChild($tNode); } } } else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red'); } }

  1. Apply a transform

function xwApplyTransform($transform, &$article, &$properties, &$title, $event) { global $xwDebug; if ($transform) { if ($tText = xwArticleContent($transform)) { # Get transform language (take a guess if none specified) $tProperties = xwArticleProperties($transform); xwGetProperty($tProperties, 'language', $tLang); if (!$tLang) $tLang = xwArticleType($transform, $tText); if ($tLang == 'xslt') xwApplyXSLT($article, $title, $properties, $tText, $transform); elseif ($tLang == 'php') xwApplyPHP($article, $tText, $transform, $title, $properties, $tProperties, $event); elseif ($tLang == 'css') xwApplyCSS($title, $transform); elseif ($tLang == 'xml') xwMergeDOM($properties, xwDomificateArticle($tText, $transform), $title, $transform); } else xwMessage("Unknown transform \"$transform\" attched to \"$event\" event!", 'red'); } }

  1. Apply PHP-transform if article perms are only writable by admin or dev

function xwApplyPHP(&$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event) { global $xwDebug, $_SERVER; if ($xwDebug) xwMessage("xwApplyPHP( $title , $tTitle )",'blue'); # Get transform perms if (!count(preg_grep('/^(admin)|(dev)$/i', xwGetListByTagname($tProperties, 'write')))) { xwMessage('PHP-Transform will not execute unless it is writable by admin or dev only.','red'); return; } # Permissions ok, execute the transform code and trap output $path = '/properties/'.preg_replace("/\\.\\w+$/",,$tTitle); $script = $GLOBALS['xwScript']; ob_start(); eval("?>$tCode<?"); xwExtractMessages($tTitle); ob_end_clean(); }

  1. Append CSS stylesheet link to output

function xwApplyCSS($title, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue'); $xwStyleSheets[] = $tName; }

  1. Apply XSLT to article-dom
  2. - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
  3. - if output-method is html, article will get stringified and language updated

function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue'); if (is_object($article)) { ob_start(); if ($tObject = domxml_xslt_stylesheet($xslt)) { $tResult = $tObject->process($article); # If output-method is html, use XSLT's dump_mem to create an html-string if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt)) { $tResult = $tObject->result_dump_mem($tResult); xwSetProperty($properties, 'language', ); } } xwExtractMessages(); ob_end_clean(); if (isset($tResult)) $article = $tResult; } else $xwStyleSheets[] = $tName; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. DOM FUNCTIONS
  1. Return result set of an XPath query on passed DOM-object

function xwXPathQuery(&$dom, $query) { $result = array(); if (is_object($dom)) { global $xwDebug; if ($xwDebug) xwMessage("xwXPathQuery( $query )",'blue'); ob_start(); if (php(4)) { $context = $dom->xpath_new_context(); if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset; elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red'); } else if ($xpath = new DOMXPath($dom)) $result = $xpath->query($query); xwExtractMessages(); ob_end_clean(); } if (php(4)) return is_array($result) ? $result : array(); else { $tmp = array(); foreach ($result as $node) $tmp[] = $node; return $tmp; } }

function xwGetProperty(&$properties, $query, &$value) { global $xwDebug; if (!preg_match("/^xpath:(.+)$/", $query, $match)) $match = array(, "/properties/$query"); if (count($results = xwXPathQuery($properties, $match[1])) == 0) return false; if (php(4)) $value = $results[count($results)-1]->get_content(); else $value = $results[count($results)-1]->nodeValue; if ($xwDebug) xwMessage("xwGetProperty( $query ) returned \"$value\"", 'purple'); return true; }

  1. - Syntax: xpath:XPathQuery:[[@]node][+] = value
  2. - if "node" exists, then a new element (or att if "@node") is created for the value
  3. - if "+" exists, content is appended, else replaced

function xwSetProperty(&$properties, $query, $value) { global $xwDebug; if ($xwDebug) xwMessage("xwSetProperty( $query , $value )", 'purple'); if (!is_object($properties)) return false; if (!preg_match("/^xpath:(.+):(@?)(\\w*?)(\\+?)$/", $query, $match)) { # If not xpath, remove property before creating $tNodes = xwXPathQuery($properties, "/properties/$query"); foreach ($tNodes as $tNode) if (php(4)) $tNode->unlink_node(); else $tNode->parentNode->removeChild($tNode); $match = array(, '/properties', , $query, ); } list(, $xpath, $att, $node, $append) = $match; foreach (xwXPathQuery($properties, $xpath) as $result) { if ($node) { # create new element/attribute in result-node if ($att) { if (php(4)) $result->append_child($properties->create_attribute($node, $value)); else $result->setAttribute($node, $value); } else { if (php(4)) { $node = $properties->create_element($node); $node->set_content($value); $result->append_child($node); } else $result->appendChild($properties->createElement($node, $value)); } } else { # node not set, replace or append current-result-value if ($append) { if (php(4)) $result->set_content( ($result->get_content()).$value ); else $result->nodeValue .= $value; } else { if (php(4)) { $newnode = $properties->create_element($result->tagname); $newnode->set_content($value); $result->replace_node($newnode); } else $properties->replaceChild($properties->createElement($result->nodeName, $value), $result); } } } return true; }

  1. Return array of comma-separated-items in element content
  2. - if more than one element all are split and appended into the list

function xwGetListByTagname(&$xml, $tag) { $list = array(); if (is_object($xml)) { if (php(4)) { $root = $xml->document_element(); foreach ($root->get_elements_by_tagname($tag) as $element) if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv)); } else { foreach ($xml->documentElement->getElementsByTagname($tag) as $element) if ($csv = $element->nodeValue) $list = array_merge($list, split(',', $csv)); } } return $list; }

  1. Merge the two passed DOM objects
  2. - appends, does not overwrite

function xwMergeDOM(&$dom1, &$dom2, $id1 = 'dom1', $id2 = 'dom2') { global $xwDebug; # Return if first DOM not a valid properties article if (!is_object($dom1)) return false; if (php(4)) $docType = $dom1->doctype(); else $docType = $dom1->doctype; if (!ereg('^xmlwiki:properties', $docType->name)) { if ($xwDebug) xwMessage('xwMergeDOM: First parameter is not an xmlwiki:properties object!','red'); return false; } # Return DOM1 if DOM2 not valid properties if (!is_object($dom2)) return $dom1; if (php(4)) $docType = $dom2->doctype(); else $docType = $dom2->doctype; if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1; # Both are ok, perform the merge if ($xwDebug) xwMessage("xwMergeDOM( &$id1 , $id2 )",'blue'); if (php(4)) { $root1 = $dom1->document_element(); $root2 = $dom2->document_element(); foreach ($root2->child_nodes() as $node) $root1->append_child($node->clone_node(true)); } else $dom1->documentElement->appendChild($dom1->importNode($dom2->documentElement, true)); return true; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. UTILITY FUNCTIONS
  1. Extract error-messages from captured output and put in proper message-queue

function xwExtractMessages($where = false) { if ($where) $where = " of \"$where\""; $err = preg_replace("/<.+?>/", "", ob_get_contents()); foreach (split("\n", $err) as $msg) if (trim($msg)) { if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1]$where)"; $msg = preg_replace('/ in .+? on line.+?([0-9]+)/', " (Line $1$where)", $msg); if (isset($line)) $msg .= $line; xwMessage($msg, 'red'); } }

  1. Add message to queue

function xwMessage($msg, $col = '#000080') { global $xwMessages; $msg = htmlentities($msg); return $xwMessages[] = "$msg"; }

function xwReplaceTokens(&$article, $embed = false) { global $xwStyleSheets, $xwMessages, $xwCssToken, $xwMsgToken, $xwScript;

# Stylesheets $stylesheets = ; foreach ($xwStyleSheets as $title) if ($embed) $stylesheets .= '<style type="text/css" media="screen">'.xwArticleContent($title).'</style>'; else $stylesheets = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$xwScript?title=$title&action=raw&ctype=text/css\" />\n$stylesheets"; $article = str_replace($xwCssToken, $stylesheets, $article);

# Messages if (count($xwMessages)) {

$msg = '

There are Messages

'; $msg .= '

  • '.join("
  • ", $xwMessages).'

';

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

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

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

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

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