Xmlwiki.php
<?php
- xmlWiki - MediaWiki XML Hack
- Nad - 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
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!');
- Allows Geshi to be installed by PARSER-HOOK if not installed the usual way already
- - still needs to be present in ./extensions though
if (!@is_object($wgGeshiSyntaxSettings)) die('xmlWiki requires the Geshi syntax-highlighting extension');
- System globals
$xwDebug = false; $xwMessages = array(); $xwArticleCache = array(); $xwStyleSheets = array(); $xwTemplate = null; $xwParserHookCalled = false; $xwSkinHookCalled = false; $xwMsgToken = ;
- 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']);
- Article globals
$xwArticleTitle = $wgTitle->getPrefixedURL(); $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); $xwIsAdmin = false; $xwArticleReadableBy = array('anyone'); $xwArticleWritableBy = array('anyone'); $xwLanguage = ;
- ---------------------------------------------------------------------------------------------------------------------- #
- SECURITY
- Get user-system-article and extract users' groups from it if any
if ($xwUserSYS = xwArticleContent("sys:$xwUserName")) xwDomificateArticle($xwUserSYS, "sys:$xwUserName"); $xwUserGroups = array_merge($xwUserGroups, xwGetListByTagname($xwUserSYS, 'groups'));
- Read user-prefs
$xwDebug = count($results = xwXPathQuery($xwUserSYS, '//debug')) ? $results[0]->get_content() : false;
- 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 ? $xwArticle : xwArticleContent($id = 'xml:'.ucwords($xwArticleTitle))) xwDomificateArticle($xwArticleProperties, $xwIsProperties ? $xwArticleTitle : $id); $xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read') + array('anyone'); $xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write') + array('anyone'); if ($xwIsProperties) $xwArticleProperties = false; }
- Merge this articles properties with default-properties
if (is_object($xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'),'defaults'))) $xwArticleProperties = xwMergeDOM($xwDefaultProperties, $xwArticleProperties);
- 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
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; }
- ---------------------------------------------------------------------------------------------------------------------- #
- INPUT HOOK
- Parse and process input from forms
function xwInputHook() {
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST; global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $xwIsProperties, $xwIsSystem; if ($xwDebug) xwMessage('INPUT-HOOK:','green');
# If not writable, change action to view if (!$xwWritable && ($action != 'view')) { 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) xwSetProperty($xwArticleProperties, $query, $value);
# if it's a pseudo-namespace... if ($xwIsProperties || $xwIsSystem) { # wpSave should be valid XML to be saved if ($xwSave) { xwDomificateArticle($wpSave, 'POST-DATA'); if (!is_object($wpSave)) { xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red'); $action='edit'; } } }
}
- ---------------------------------------------------------------------------------------------------------------------- #
- OUTPUT HOOK
- Post-process and output article
function xwOutputHook() {
global $wgUser, $wgOut, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug; global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwUserName; global $xwView, $xwPreview, $xwSave, $xwEdit, $xwRaw, $xwIsProperties, $xwIsSystem; if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');
# Activate the skin-hook and generate wiki's output $wgUser->setOption('skin', 'xwskin'); $wgOut->output();
# If saving, execute the transforms in the onChange list 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 ($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]; } 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\n\t<view></view>\n\t<onChange></onChange>\n</properties>\n".$m[2]; } }
# Insert messages into final html and output if ($xwDebug) { if (!$xwParserHookCalled) xwMessage('PARSER-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(); print $xwArticle; }
- ---------------------------------------------------------------------------------------------------------------------- #
- PARSER HOOK
function xwParserHook(&$text) {
global $xwArticleTitle, $xwArticle, $xwArticleProperties; global $xwRaw, $xwParserHookCalled, $xwDebug, $xwReadable, $xwLanguage; if ($xwDebug) xwMessage('PARSER-HOOK:','green'); $xwParserHookCalled = true;
# If this text-fragment is our article, apply transforms if (strncmp($text, $xwArticle, 100) == 0 or strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0) { if ($xwDebug) xwMessage("Matching text-fragment intercepted."); if (!$xwReadable) { xwMessage('Sorry, article not readable!','red'); return $text = false; } # Apply data-transforms # - domificate first if xml # - undomificate after if still an object if (preg_match("/^<\\?xml/i", $text)) xwDomificateArticle($text, 'ParserHook/text'); if (is_object($xwArticleProperties)) { $docType = $xwArticleProperties->doctype(); $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');
if ($xwDebug) xwMessage('Content language determined as '.($xwLanguage ? strtoupper($xwLanguage) : 'WIKI')); # Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours return $xwLanguage == ; }
- ---------------------------------------------------------------------------------------------------------------------- #
- SKIN HOOK
- If attempted xml encountered, try to domificate and transform
- - Raw requests don't get here
function xwSkinHook(&$tmpl) {
global $xwArticle, $xwArticleTitle, $xwArticleProperties; 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 if (is_object($xwArticleProperties)) { $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
- Retreive wiki-article as raw text
function xwArticleContent($articleTitle) { # Return with results if already cached global $xwArticleCache, $xwDebug; if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue'); 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; }
- Decide kind of article from content and title
function xwArticleType($title, $article) { if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt'; if (eregi('^(sys)|(xml):.+$', $title) || ereg('^<\\?xml', $article)) return 'xml'; if (eregi('\\.php$', $title)) return 'php'; if (eregi('\\.css$', $title)) return 'css'; if (eregi('\\.as$', $title)) return 'actionscript'; if (eregi('\\.py$', $title)) return 'python'; if (eregi('\\.java$', $title)) return 'java'; if (eregi('\\.((cpp)|(h))$', $title)) return 'cpp'; if (eregi('\\.js$', $title)) return 'javascript'; if (eregi('\\.css$', $title)) return 'css'; if (eregi('^<\\?html', $article)) 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'; }
- 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 ($dom = domxml_open_mem($article)) $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 (is_object($article)) $article = $article->dump_mem(true); return true; }
- Apply a transform
function xwApplyTransform($transform, &$article, $title, &$properties, $listName) { global $xwDebug; if ($transform) { if ($tText = xwArticleContent($transform)) { $tType = xwArticleType($transform, $tText); if ($tType == 'xslt') xwApplyXSLT($article, $tText, $transform); elseif ($tType == 'php') xwApplyPHP($article, $tText, $transform, $title, $properties, $listName); elseif ($tType == 'css') xwApplyCSS($transform); elseif ($tType == 'xml') xwMergeDOM($properties, xwDomificateArticle($tText, $transform)); } else xwMessage("Unknown transform \"$transform\" found in \"$listName\" list!", 'red'); } }
- Apply PHP-transform if article perms are only writable by admin or dev
function xwApplyPHP(&$article, &$code, $tName, $title, &$properties, $listName) { # Get transform perms if ($tProperties = xwArticleContent($id = 'xml:'.ucwords($tName))) xwDomificateArticle($tProperties, $id); 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 global $_SERVER; $path = '/properties/'.preg_replace("/\\.\\w+$/",,$tName); $script = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; ob_start(); eval("?>$code<?"); xwExtractMessages(); ob_end_clean(); }
- Append CSS stylesheet link to output
function xwApplyCSS($cssName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyCSS($cssName)",'blue'); $xwStyleSheets[] = $cssName; }
- Apply XSLT to article-dom
- - if output-method is html, article will get stringified
function xwApplyXSLT(&$article, &$xslt, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$id)",'blue'); ob_start(); 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); } xwExtractMessages(); ob_end_clean(); if (isset($tResult)) $article = $tResult; else return false; return true; }
- 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(); $context = $dom->xpath_new_context(); if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset; elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red'); xwExtractMessages(); ob_end_clean(); } return $result; }
- - 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) { if (!is_object($properties) or !preg_match("/^xpath:(.+?):(@?)(\\w*?)(\\+?)$/", $query, $match)) return false; list(, $xpath, $att, $node, $append) = $match; if (!is_array($results = xwXPathQuery($properties, $xpath))) $results = array(); foreach ($results as $result) { if ($node) { # create new element/attribute in result-node if ($att) $node = $properties->create_attribute($node, $value); else { $node = $properties->create_element($node); $node->set_content($value); } $result->append_child($node); } else { # node not set, replace or append current-result-value if ($append) $result->set_content($value); else { $newnode = $properties->create_element($result->tagname); $newnode->set_content($value); $result->replace_node($newnode); } } } return true; }
- ---------------------------------------------------------------------------------------------------------------------- #
- UTILITY FUNCTIONS
- 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)) { $root = $xml->document_element(); foreach ($root->get_elements_by_tagname($tag) as $element) if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv)); } return $list; }
- Merge the two passed DOM objects
- - appends, does not overwrite
function xwMergeDOM($dom1, $dom2) { global $xwDebug; if ($xwDebug) xwMessage('xwMergeDOM()','blue'); # Return if first DOM not a valid properties article if (!is_object($dom1)) return false; $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; $docType = $dom2->doctype(); if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1; # Both are ok, perform the merge if ($xwDebug) xwMessage('xwMergeDOM()','blue'); $root1 = $dom1->document_element(); $root2 = $dom2->document_element(); foreach ($root2->child_nodes() as $node) { $root1->append_child($node->clone_node(true)); } return $dom1; }
- Append messages to content
function xwAddMessages() { global $xwArticle, $xwMessages, $xwMsgToken; if (count($xwMessages)) {
$msg = '
There are Messages
'; $msg .= '
';
$xwArticle = ereg_replace($xwMsgToken, $msg, $xwArticle); } }
- Extract error-messages from captured output and put in proper message-queue
function xwExtractMessages() { #if (preg_matcheval()'d code on line 2 $err = preg_replace("/<.+?>/", "", ob_get_contents()); #$err = preg_replace('/Warning.+?\\(\\): /', , $err); foreach (split("\n", $err) as $msg) if (trim($msg)) { if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1])"; $msg = preg_replace('/ in .+? on line.+?[0-9]+/', , $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[] = "$msg"; }
- 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;
}
?>



