Difference between revisions of "XmlWiki"

From Organic Design wiki
Line 1: Line 1:
<?php
+
==What is xmlWiki?==
# xmlWiki - MediaWiki XML Hack
+
Articles in xmlWiki work just like the normal MediaWiki articles except, when their contents is xml. In this case it will be represented as a DOM object at runtime, and the content will be validated against any referenced DTD or xml-schema. If any XSLT's are referenced, xmlWiki will attempt to apply them to the article. If the XSLT-output-method is "html" then it will be reduced to a string.
# Nad - 2005-05-18
+
 
 +
==How is it Installed?==
 +
It's three scripts (source below) which are in an "xmlwiki" directory in the main wiki folder. the hack is applied or removed by running the xmlwiki/index.php script. If xmlWiki is disabled then the security layer is also disabled, so the xmlwiki directory should be accessible only by admin.
  
# INIT
+
==How does it tie in with MediaWiki?==
# SECURITY
+
Two pseudo-namespaces have been added "xml" and "sys". These are both XML articles, and their contents affects the article they're associated with. Any sys:article is readable and writable only by users in the "admin" group.
# INPUT HOOK
+
*The sys:article holds security information: owner, read-groups, write-groups
# OUTPUT HOOK
+
*An xml:article is an XML article of docType "xmlwiki.dtd"
# PARSER HOOK
+
*The xml:article contains the transform-list, publish-list and base-class-list
# SKIN HOOK
 
# ARTICLE FUNCTIONS
 
# UTILITY FUNCTIONS
 
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
==More Specifically==
 +
There are five hooks now! but it's most efficient like this :-)
  
# TODO:
+
*'''INIT:''' This hooks into ''index.php'' after the environment has been established, but before any article or input processing occurs. This does the ''require_once'' of ''xmlwiki.php''.
# - Undomificate: validation, xslt
+
*'''INPUT:''' This hooks into ''index.php'' before input processing so it can direct any XPath inputs into the article-DOM and transform them into a standard input for xmlWiki. This also handles write-permissions.
# - Transform: check perms on php-execution
+
*'''PARSER:''' This hooks into the parse-function in ''includes/Parser.php''. If the article is  XML, it is validated and domificated. If it includes XSLT references, or has a transform-list in its ''xml:article'' then those transforms are also applied here. The article will be back in string form again after this.
# Publish:
+
*'''SKIN:''' This is a new compulsory skin (''skins/xwskin.php'') which renders the page output as a DOM-object so xmlWiki can apply design transforms to it rather than PHP-based templates. If ''raw'' content is requested, then only the articles own transforms are applied, because skins and page-layout are part of the xmlWiki environment, not the article.
# - xml:article - holds article publish-list
+
*'''OUTPUT:''' This replaces the output rendering in ''index.php''. It handles read permissions and transforms the xwskin-generated DOM-output with ''xmlwiki.xslt''.
# - mirror/wiki-sync
 
# Transforms:
 
# - xml:article - now transforms are in here (main article is now like <body>)
 
# - so we don't do the "its one of ours" thing anymore
 
  
# LATER:
+
'''Current scripts:'''
# - allow XIncludes to build large docs from others
+
*[[xmlwiki.php|xmlWiki main script]]
# - allow docBook and DSSSL content
+
*[[xwadmin.php|xmlWiki admin script]] (installs and uninstalls the hooks)
# - Maybe only use XML transforms and use PHP via PI's
+
*[[xwskin.php|xmlWiki skin-hook]]
  
# ---------------------------------------------------------------------------------------------------------------------- #
+
'''Templates & Transforms:'''
# INIT
+
*[[xmlwiki.xslt]] (xmlWiki page layout - not functional yet)
 +
*[[xmlwiki.css]] (xmlWiki page style)
  
# Exit if not included from index.php
+
'''Wiki Objects available from OUTPUT-hook:'''
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!');
+
*[[Wiki Objects]]
 
 
# Allows Geshi to be installed by PARSER-HOOK if not installed th usual way already
 
if (!@is_object($wgGeshiSyntaxSettings)) $wgGeshiSyntaxSettings = false;
 
 
 
# Otherwise set up xmlWiki global environment ready for input and output processing
 
$xwMessages = array();
 
$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']);
 
$xwUserName = $wgUser->mName;
 
$xwUserGroups = array(strtolower($xwUserName), 'anyone', 'everyone', 'nobody', 'world');
 
$xwArticleReadableBy = array('anyone');
 
$xwArticleWritableBy = array('anyone');
 
$xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false);
 
$xwArticleTitle = $wgTitle->getPrefixedURL();
 
$xwArticleCache = array();
 
$xwArticleLinks = array();
 
$xwUserLinks = array();
 
$xwMetaArticle = false;
 
$xwMsgToken = '<!--xwMsg-->';
 
$xwTemplate = null;
 
$xwParserHookCalled = false;
 
$xwSkinHookCalled = false;
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# SECURITY
 
 
 
# Not currently used: R/W by user, for xmlWiki prefs etc
 
if ($xwUserXML = xwArticleContent(strtolower("xml:$xwUserName")))
 
xwDomificateArticle($xwUserXML, "xml:$xwUserName");
 
 
 
# R/W by admin only, holds users groups and other system info like stats
 
# - no sys:user means "anyone" in group "everyone" as far as perms goes
 
if ($xwUserSYS = xwArticleContent(strtolower("sys:$xwUserName")))
 
xwDomificateArticle($xwUserSYS, "sys:$xwUserName");
 
 
 
# If sys:user exists, get users' group-list
 
if (is_object($xwUserSYS)) {
 
$xwRoot = $xwUserSYS->document_element();
 
if (count($xwList = $xwRoot->get_elements_by_tagname('groups')))
 
$xwUserGroups = array_merge($xwUserGroups, split(',', strtolower($xwList[0]->get_content())));
 
}
 
 
 
# - Security info is: owner, read-groups, write-groups (default values are "world")
 
# - Users and groups are interchangeable
 
# - Default user is "anyone", default group is "everyone"
 
if (eregi('^xml:(.+)$', $xwArticleTitle)) {
 
# This is an xml-pseudo-namespace
 
# - its perms apply to its 'parent' and to itself aswell
 
$xwXML = $xwArticle;
 
xwDomificateArticle($xwXML, $xwArticleTitle);
 
if (is_object($xwXML)) {
 
$xwRoot = $xwXML->document_element();
 
if (count($xwList = $xwRoot->get_elements_by_tagname('read')))
 
$xwArticleReadableBy = split(',', strtolower($xwList[0]->get_content()));
 
if (count($xwList = $xwRoot->get_elements_by_tagname('write')))
 
$xwArticleWritableBy = split(',', strtolower($xwList[0]->get_content()));
 
}
 
}
 
elseif (eregi('^sys:(.+)$', $xwArticleTitle)) {
 
# This is a sys-pseudo-namespace
 
# - only admin can read/write sys:
 
$xwArticleReadableBy = $xwArticleWritableBy = array('admin');
 
}
 
else {
 
# This is a normal article, get xml:article
 
if ($xwArticleXML = xwArticleContent(strtolower("xml:$xwArticleTitle")))
 
xwDomificateArticle($xwArticleXML, "xml:$xwArticleTitle");
 
if (is_object($xwArticleXML)) {
 
$xwRoot = $xwArticleXML->document_element();
 
if (count($xwList = $xwRoot->get_elements_by_tagname('read')))
 
$xwArticleReadableBy = split(',', $xwList = strtolower($xwList[0]->get_content()));
 
if (count($xwList = $xwRoot->get_elements_by_tagname('write')))
 
$xwArticleWritableBy = split(',', $xwList = strtolower($xwList[0]->get_content()));
 
}
 
}
 
 
 
# Set perms for this request
 
$xwReadable = 0 < count(array_intersect($xwArticleReadableBy, $xwUserGroups));
 
$xwWritable = 0 < count(array_intersect($xwArticleWritableBy, $xwUserGroups));
 
if ($xwDebug) {
 
xwMessage('Groups: '.join(', ', $xwUserGroups));
 
xwMessage("Readable ($xwReadable): ".join(', ', $xwArticleReadableBy));
 
xwMessage("Writable ($xwWritable): ".join(', ', $xwArticleWritableBy));
 
}
 
 
 
# Divert to access-denied article if not readable
 
if (!$xwReadable) {
 
$action = 'view';
 
$xwSave = $xwEdit = false;
 
die('Sorry, the requested article was not readable.');
 
}
 
 
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# INPUT HOOK
 
 
 
# Parse and process input from forms
 
function xwInputHook() {
 
 
 
global $xwArticle, $xwArticleXML, $xwArticleTitle, $xwWritable, $_REQUEST;
 
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle;
 
if ($xwDebug) xwMessage('xwInputHook()','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;
 
}
 
 
 
# Parse POST for XPath inputs
 
# - Form-input-name is "xw:[[@]node]:XPathQuery" which directs the value into xwArticle (if DOM)
 
# - if "node" exists, then a new element (or att if "@node") is created for the value
 
# - hidden-inputs can direct input-handlers into the transform list
 
foreach ($_REQUEST as $k => $v) {
 
if (is_object($xwArticleXML) && preg_match("/^xw:(@?)(\\w*):(.+)$/", $k, $m)) {
 
list(, $att, $node, $xpath) = $m;
 
foreach (xwXPathQuery($xwArticleXML, $xpath) as $result)
 
if ($node) {
 
# create new element/attribute in result-node
 
if ($att) $node = $xwArticleXML->create_attribute($node, $v);
 
else {
 
$node = $xwArticleXML->create_element($node);
 
$node->set_content($v);
 
}
 
$result->append_child($node);
 
}
 
else $result->set_content($v); # node not set, just replace current-result-value
 
}
 
}
 
 
 
# if it's a pseudo-namespace...
 
if (preg_match('/^(xml)|(sys):/i', $xwArticleTitle)) {
 
 
# wpSave should be valid XML to be saved
 
if ($xwSave) {
 
xwDomificate($wpSave);
 
if (!is_object($wpSave)) {
 
xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red');
 
$action='edit';
 
}
 
}
 
 
}
 
 
 
}
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# OUTPUT HOOK
 
 
 
# Generate output to send back to client
 
# - if article is not XML, then wiki's parsed content is used
 
function xwOutputHook() {
 
 
 
global $wgOut, $wgUser, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug;
 
global $xwArticle, $xwArticleXML, $xwArticleTitle, $xwReadable, $xwWritable;
 
global $xwView, $xwPreview, $xwSave, $xwRaw, $xwUserLinks, $xwArticleLinks;
 
if ($xwDebug) xwMessage('xwOutputHook()','green');
 
 
 
# Generate output page
 
$wgUser->setOption('skin', 'xwskin');
 
$wgOut->output();
 
 
 
# NOTE - Why not move this into INPUT-HOOK???????? - or maybe not....?
 
# If saving, check the publishing list
 
if ($xwSave) {
 
 
# Check publish-list and process if any
 
if (false) {
 
xwMessage('Publishing...');
 
# Publish each item
 
foreach ($xwArticlePublish as $url) {
 
if ($FH = fopen($url, 'a')) {
 
# ftp example: ftp://user:password@example.com/file.txt
 
fwrite($FH, $xwArticle);
 
fclose($FH);
 
xwMessage("Article published to $url");
 
}
 
else xwMessage("Failed to publish to $url", 'red');
 
}
 
}
 
 
# Check mirror-list and process if any
 
# - allows articles in separate wiki's to be syncronised
 
# - does rudimentary security by sending a salt and response being a hash of the password generated from the salt
 
# - this could be done for login too using ECMA-MD5 on browser
 
if (false) {
 
xwMessage('Mirroring...');
 
}
 
}
 
 
 
 
 
# 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($xwArticleXML)) xwMessage($xwArticleXML->dump_mem(true),'purple');
 
}
 
xwAddMessages();
 
print $xwArticle;
 
}
 
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# PARSER HOOK
 
 
 
function xwParserHook(&$text) {
 
 
 
global $xwRaw, $xwParserHookCalled, $xwDebug, $xwArticleTitle, $xwArticle;
 
if ($xwDebug) xwMessage('xwParserHook(TEXT)','green');
 
$xwParserHookCalled = true;
 
$transformed = false;
 
 
 
# If this text-fragment is our article, apply transforms
 
if ((strncmp($text, $xwArticle, 100) == 0) || (strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0)) {
 
if ($xwDebug) xwMessage("PARSER-HOOK: \"$xwArticleTitle\" text-fragment recognised and intercepted.");
 
$xwArticle = $text; # needed because xwTransform works on global article
 
# If attempted xml encountered, try to domificate and transform
 
if (preg_match("/^<\\?xml/i", $xwArticle)) {
 
$transformed = true;
 
xwDomificateArticle($xwArticle, 'ParserHook/text');
 
xwTransformArticle();
 
}
 
# If not a 'raw' request, try some Geshi on
 
if (!$xwRaw) {
 
global $wgGeshiSyntaxSettings;
 
if (!is_object($wgGeshiSyntaxSettings)) require_once('extensions/geshi/geshi.php');
 
if ($lang = xwArticleType($xwArticleTitle, $xwArticle)) {
 
if ($xwDebug) xwMessage("Geshi: This is \"$lang\".");
 
# Replace tabs with spaces (better than geshi)
 
$xwArticle = preg_replace_callback("/^(.*?)(\\t+)/m", 'xwReplaceTabs', $xwArticle);
 
# Set up a geshi-parser
 
$geshi = new GeSHi($xwArticle, $lang == 'xslt' ? 'xml' : $lang, 'extensions/geshi/geshi');
 
$geshi->set_header_type(GESHI_HEADER_DIV);
 
#$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
 
#@$geshi->set_line_style('color:#aaaaaa');
 
#@$geshi->set_code_style('color:black');
 
@$geshi->set_keyword_group_style(1, 'color:blue', true);
 
@$geshi->set_keyword_group_style(2, 'color:blue', true);
 
@$geshi->set_keyword_group_style(3, 'color:blue', true);
 
@$geshi->set_comments_style(1,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_comments_style(2,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_comments_style(3,'font-style: italic; color:#d00000;',true);
 
@$geshi->set_escape_characters_style('color:red',true);
 
@$geshi->set_brackets_style('color:black',true);
 
@$geshi->set_strings_style('color:#008080',true);
 
@$geshi->set_numbers_style('color:black',true);
 
@$geshi->set_methods_style('color:black',true);
 
@$geshi->set_symbols_style('color:black',true);
 
@$geshi->set_regexps_style('color:green',true);
 
# Do the parse
 
$xwArticle = preg_replace("/^<div.*?>/", '<div class="xwcode">', $geshi->parse_code());
 
$transformed = true;
 
}
 
elseif ($xwDebug) xwMessage('Geshi: Not my problem.');
 
}
 
$text = $xwArticle;
 
}
 
elseif ($xwDebug) xwMessage('Parsing other content');
 
 
# Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours
 
return $transformed;
 
}
 
 
# Converts tabs to spaces better than geshi
 
function xwReplaceTabs($m) { return $m[1].str_repeat(' ',strlen($m[2])*4-strlen($m[1])%4); }
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# SKIN HOOK
 
 
 
# If attempted xml encountered, try to domificate and transform
 
# - Raw requests don't get here
 
function xwSkinHook(&$tmpl) {
 
 
 
global $xwArticle, $xwTemplate, $xwDebug, $xwSkinHookCalled;
 
if ($xwDebug) xwMessage('xwSkinHook(TEMPLATE)','green');
 
$xwSkinHookCalled = true;
 
$xwTemplate = $tmpl;
 
$xwArticle = $xwTemplate->data['bodytext'];
 
#$xslt = xwArticleContent('xmlwiki.xslt');
 
 
# Do xmlWiki page-layout transform
 
 
 
xwTransformPageLayout(); # should be: xwApplyXSLT($xwArticle, $xslt, 'xmlwiki.xslt');
 
 
 
}
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# 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 == '(There is currently no text in this page)') return false;
 
if ($articleTitle) $xwArticleCache[$articleTitle] = $article;
 
return $article;
 
}
 
 
 
# Decide kind of article from content and title
 
function xwArticleType($title, $article) {
 
# Name based matches
 
if (ereg('\\.php$', $title)) return 'php';
 
if (ereg('\\.css$', $title)) return 'css';
 
if (ereg('\\.as$', $title)) return 'actionscript';
 
if (ereg('\\.py$', $title)) return 'python';
 
if (ereg('\\.java$', $title)) return 'java';
 
if (ereg('\\.((cpp)|(h))$', $title)) return 'cpp';
 
if (ereg('\\.js$', $title)) return 'javascript';
 
if (ereg('\\.css$', $title)) return 'css';
 
# Content based matches
 
if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt';
 
if (ereg('^<\\?xml', $article)) return 'xml';
 
if (ereg('^<\\?html', $article)) return 'html4strict';
 
if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl';
 
if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash';
 
}
 
 
 
# Convert passed article to a DOM object
 
# - Article is unchanged if not valid XML
 
function xwDomificateArticle(&$article, $id = '') {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwDomificateArticle(XML,$id)",'blue');
 
ob_start();
 
if ($dom = domxml_open_mem($article)) $article = $dom;
 
else {
 
# Could not convert, extract error messages from output
 
xwMessage("Failed :-(", 'red');
 
xwExtractMessages();
 
}
 
ob_end_clean();
 
}
 
 
 
# 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(DOM,$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;
 
}
 
 
 
# Transform an article object
 
# - Article is undomificated on exit
 
# - Article remains unchanged if not DOM
 
function xwTransformArticle() {
 
global $xwDebug, $xwArticle, $xwArticleXML, $xwArticleTitle;
 
# xml:article must be an object...
 
if (is_object($xwArticleXML)) {
 
$docType = $xwArticleXML->doctype();
 
# ...and must be xmlwiki:* doctype
 
if (ereg('^xmlwiki:', $docType->name)) {
 
$root = $xwArticle->document_element();
 
if ($xwDebug) xwMessage("xwTransformArticle()",'blue');
 
# Loop thru transforms applying each
 
foreach ($root->get_elements_by_tagname('transform') as $tElement) {
 
$tName = $tElement->get_content();
 
# Transform is not local, get transform-article and apply to current-article-object
 
if ($tText = xwArticleContent($tName)) {
 
# Get transform type
 
$tType = xwArticleType($tName, $tText);
 
if ($tType == 'xslt') xwApplyXSLT($xwArticle, $tText, $tName);
 
elseif ($tType == 'php') {
 
# TODO: php, check perms and execute (trapping errors for reporting - unless fatal)
 
if ($perms_check_out_ok = true) {
 
ob_start();
 
eval("?>$tText<?");
 
xwExtractMessages();
 
ob_end_clean();
 
}
 
}
 
elseif ($tType == 'css') xwMessage("CSS's from transform-list aren't applied yet :-(", 'red');
 
}
 
else xwMessage("No such transform-article \"$tName\"!", 'red');
 
}
 
}
 
else xwMessage("Could not transform: xml:article must be doctype:xmlwiki!",'red');
 
}
 
else xwMessage("xwTransformArticle(NON-OBJECT)",'red');
 
# If article is still a DOM object, stringify it once and for all
 
if (is_object($xwArticle)) xwUndomificateArticle($xwArticle, $xwArticleTitle);
 
return true;
 
}
 
 
 
# Apply XSLT to article-dom
 
# - if output-method is html, article will get stringified
 
function xwApplyXSLT(&$article, &$xslt, $id = '') {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$id)",'blue');
 
ob_start();
 
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) {
 
global $xwDebug;
 
if ($xwDebug) xwMessage("xwXPathQuery(DOM,\"$query\")",'blue');
 
ob_start();
 
$context = $dom->xpath_new_context();
 
if ($xpath = $context->xpath_eval($query)) $result = $xpath->nodeset;
 
else {
 
xwMessage('Query Failed! :-(', 'red');
 
$result = array();
 
}
 
xwExtractMessages();
 
ob_end_clean();
 
return $result;
 
}
 
 
 
# Surrounds the article with HTML structural page-layout containing a ref to xmlwiki.css
 
# - TEMP: see skin-hook
 
function xwTransformPageLayout() {
 
 
 
global $xwMsgToken, $xwArticle, $xwTemplate, $xwDebug, $xwWritable;
 
if ($xwDebug) xwMessage("xwTransformPageLayout()",'blue');
 
$tmpl = &$xwTemplate;
 
$data = &$tmpl->data;
 
$doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ';
 
$doctype .= '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
 
$html = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="';
 
$html .= htmlspecialchars($data['lang']).'" lang="'.htmlspecialchars($data['lang']).'" dir="';
 
$html .= htmlspecialchars($data['dir']).'">';
 
 
 
# Head
 
$head = '<head><meta http-equiv="Content-Type" content="'.htmlspecialchars($data['mimetype']);
 
$head .= '; charset='.htmlspecialchars($data['charset']).'" />'.$data['headlinks'];
 
$head .= '<link rel="stylesheet" type="text/css" href="/wiki/index.php?title=xmlwiki.css&action=raw&gen=css" />';
 
 
 
$head .= '<title>'.htmlspecialchars($data['pagetitle']).'</title>';
 
$head .= '</head>';
 
 
 
# Body opening tag
 
$body = '<body ';
 
if ($data['body_ondblclick']) $body .= 'ondblclick="'.htmlspecialchars($data['body_ondblclick']).'" ';
 
if ($data['nsclass']) $body .= 'class="'.htmlspecialchars($data['nsclass']).'"';
 
$body .= '>';
 
 
 
# Content
 
$content = '<div id="column-content">';
 
$content .= '<div id="content"><a name="top" id="contentTop"></a>';
 
if ($data['sitenotice']) $content .= '<div id="siteNotice">'.$data['sitenotice'].'</div>';
 
$content .= '<h1 class="firstHeading">'.htmlspecialchars($data['title']).'</h1>';
 
$content .= '<div id="bodyContent">';
 
$content .= '<h3 id="siteSub">'.htmlspecialchars($tmpl->translator->translate('tagline')).'</h3>';
 
$content .= '<div id="contentSub">'.$data['subtitle'].'</div>';
 
if ($data['undelete']) $content .= '<div id="contentSub">'.$data['undelete'].'</div>';
 
if ($data['newtalk']) $content .= '<div class="usermessage">'.$data['newtalk'].'</div>';
 
$content .= "<!-- start content -->$xwArticle"; # <---- Actual article content inserted here
 
if ($data['catlinks']) $content .= '<div id="catlinks">'.$data['catlinks'].'</div>';
 
$content .= '<!-- end content --><div class="visualClear"></div>';
 
$content .= '</div></div></div>';
 
 
 
# Action-links (remove edit link if not writable)
 
$actions = '<div id="p-cactions" class="portlet"><h5>Views</h5><ul>';
 
foreach ($data['content_actions'] as $key => $action) {
 
#if ($key == '
 
#if (!(ereg('action=edit$', $action['href'])&&!$xwWritable)) {
 
$actions .= '<li id="ca-'.htmlspecialchars($key).'"';
 
if ($action['class']) $actions .= ' class="'.htmlspecialchars($action['class']).'"';
 
$href = '<a href="'.htmlspecialchars($action['href']).'">'.htmlspecialchars($action['text']).'</a>';
 
$actions .= ">$href</li>";
 
# }
 
}
 
$actions .= '</ul></div>';
 
 
 
# Personal-links
 
$personal = '<div class="portlet" id="p-personal">';
 
$personal .= '<h5>'.htmlspecialchars($tmpl->translator->translate('personaltools')).'</h5>';
 
$personal .= '<div class="pBody"><ul>';
 
foreach ($data['personal_urls'] as $key => $item) {
 
$personal .= '<li id="pt-'.htmlspecialchars($key).'">';
 
$href = '<a href="'.htmlspecialchars($item['href']).'"';
 
if (!empty($item['class'])) $href .= 'class="'.htmlspecialchars($item['class']).'"';
 
$href .= '>'.htmlspecialchars($item['text']).'</a>';
 
$personal .= "$href</li>";
 
}
 
$personal .= "</ul></div></div>";
 
 
 
# Logo
 
$logo = '<div class="portlet" id="p-logo">';
 
$logo .= '<a style="background-image: url('.htmlspecialchars($data['logopath']).');" ';
 
$logo .= 'href="'.htmlspecialchars($data['nav_urls']['mainpage']['href']).'" ';
 
$logo .= 'title="'.htmlspecialchars($tmpl->translator->translate('mainpage')).'"></a></div>';
 
$logo .= '<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>';
 
 
 
# Navigation
 
$nav = '<div class="portlet" id="p-nav">';
 
$nav .= '<h5>'.htmlspecialchars($tmpl->translator->translate('navigation')).'</h5>';
 
$nav .= '<div class="pBody"><ul>';
 
foreach ($data['navigation_urls'] as $navlink) {
 
$nav .= "<li id=\"".htmlspecialchars($navlink['id'])."\">";
 
$nav .= '<a href="'.htmlspecialchars($navlink['href']).'">'.htmlspecialchars($navlink['text']).'</a>';
 
$nav .= '</li>';
 
}
 
$nav .= '</ul></div></div>';
 
 
# Search
 
$search = '<div id="p-search" class="portlet">';
 
$search .= '<h5><label for="searchInput">'.htmlspecialchars($tmpl->translator->translate('search')).'</label></h5>';
 
$search .= '<div class="pBody">';
 
$search .= '<form name="searchform" action="'.htmlspecialchars($data['searchaction']).'" id="searchform">';
 
$search .= '<input id="searchInput" name="search" type="text"';
 
if ($tmpl->haveMsg('accesskey-search')) $search .= ' accesskey="';
 
$search .= htmlspecialchars($tmpl->translator->translate('accesskey-search')).'"';
 
if (isset($data['search'])) $search .= ' value="'.htmlspecialchars($data['search']).'"';
 
$search .= ' />';
 
$search .= '<input type="submit" name="go" class="searchButton" id="searchGoButton" value="';
 
$search .= htmlspecialchars($tmpl->translator->translate('go')).'" />';
 
$search .= '&nbsp;<input type="submit" name="fulltext" class="searchButton" value="';
 
$search .= htmlspecialchars($tmpl->translator->translate('search')).'" />';
 
$search .= '</form></div></div>';
 
 
# Toolbox
 
$toolbox = '<div class="portlet" id="p-tb">';
 
$toolbox .= '<h5>'.htmlspecialchars($tmpl->translator->translate('toolbox')).'</h5>';
 
$toolbox .= '<div class="pBody"><ul>';
 
if ($data['notspecialpage']) {
 
foreach (array('whatlinkshere', 'recentchangeslinked') as $special ) {
 
$href = '<a href="'.htmlspecialchars($data['nav_urls'][$special]['href']).'">';
 
$href .= htmlspecialchars($tmpl->translator->translate($special)).'</a>';
 
$toolbox .= "<li id=\"t-'$special'\">$href</li>";
 
}
 
}
 
if ($data['feeds']) {
 
$toolbox .= '<li id="feedlinks">';
 
foreach ($data['feeds'] as $key => $feed) {
 
$toolbox .= '<span id="feed-'.htmlspecialchars($key).'">';
 
$toolbox .= '<a href="'.htmlspecialchars($feed['href']).'">'.htmlspecialchars($feed['text']).'</a>';
 
$toolbox .= '&nbsp;</span>';
 
}
 
$toolbox .= '</li>';
 
}
 
foreach (array('contributions', 'emailuser', 'upload', 'specialpages') as $special ) {
 
if ($data['nav_urls'][$special]) {
 
$href = '<a href="'.htmlspecialchars($data['nav_urls'][$special]['href']).'">';
 
$href .= htmlspecialchars($tmpl->translator->translate($special)).'</a>';
 
$toolbox .= "<li id=\"t-$special\">$href</li>";
 
}
 
}
 
$toolbox .= '</ul></div></div>';
 
 
 
# Language
 
$language = '';
 
if ($data['language_urls']) {
 
$language .= '<div id="p-lang" class="portlet">';
 
$language .= '<h5>'.htmlspecialchars($tmpl->translator->translate('otherlanguages')).'</h5>';
 
$language .= '<div class="pBody"><ul>';
 
foreach ($data['language_urls'] as $langlink) {
 
$href = '<a href="'.htmlspecialchars($langlink['href']).'">'.$langlink['text'].'</a>';
 
$language .= "<li>$href</li>";
 
}
 
$language .= '</ul></div></div>';
 
}
 
 
 
# Footer
 
$footer = '<div id="footer"><ul id="f-list">';
 
if ($data['lastmod']) $footer .= '<li id="f-lastmod">'.$data['lastmod'].'</li>';
 
if ($data['viewcount']) $footer .= '<li id="f-viewcount">'.$data['viewcount'].'</li>';
 
if ($data['credits']) $footer .= '<li id="f-credits">'.$data['credits'].'</li>';
 
if ($data['copyright']) $footer .= '<li id="f-copyright">'.$data['copyright'].'</li>';
 
if ($data['about']) $footer .= '<li id="f-about">'.$data['about'].'</li>';
 
if ($data['disclaimer']) $footer .= '<li id="f-disclaimer">'.$data['disclaimer'].'</li>';
 
$footer .= '</ul></div>';
 
 
 
# Return assembled parts
 
$xwArticle = " $doctype
 
$html
 
$head
 
$body
 
<table border=\"0\" cellpadding=\"10\" cellspacing=\"1\">
 
<tr>
 
<td valign=\"top\" bgcolor=\"#cccccc\">
 
<img src=\"/wiki/xmlwiki.jpg\" />
 
$search
 
$nav
 
$toolbox
 
</td>
 
<td width=\"100%\" valign=\"top\" bgcolor=\"#cccccc\">
 
$personal$actions$xwMsgToken$content
 
</td>
 
</tr>
 
<td colspan=\"2\" bgcolor=\"#cccccc\">$footer</td>
 
<tr>
 
</table>
 
</body>
 
</html>
 
";
 
}
 
 
# ---------------------------------------------------------------------------------------------------------------------- #
 
# 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
 
function xwExtractMessages() {
 
$err = preg_replace("/<.+?>/", "", ob_get_contents());
 
$err = preg_replace('/ in .+? on line.+?[0-9]+/', '', $err);
 
$err = preg_replace('/Warning.+?\\(\\): /', '', $err);
 
foreach (split("\n", $err) as $msg) if ($msg) xwMessage("$msg", 'red');
 
}
 
 
# Add message to queue
 
function xwMessage($msg, $col = '#000080') {
 
global $xwMessages;
 
$msg = htmlentities($msg);
 
return $xwMessages[] = "<font color=\"$col\">$msg</font>";
 
}
 
 
 
# 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;
 
}
 
 
?>
 

Revision as of 22:37, 18 June 2005

What is xmlWiki?

Articles in xmlWiki work just like the normal MediaWiki articles except, when their contents is xml. In this case it will be represented as a DOM object at runtime, and the content will be validated against any referenced DTD or xml-schema. If any XSLT's are referenced, xmlWiki will attempt to apply them to the article. If the XSLT-output-method is "html" then it will be reduced to a string.

How is it Installed?

It's three scripts (source below) which are in an "xmlwiki" directory in the main wiki folder. the hack is applied or removed by running the xmlwiki/index.php script. If xmlWiki is disabled then the security layer is also disabled, so the xmlwiki directory should be accessible only by admin.

How does it tie in with MediaWiki?

Two pseudo-namespaces have been added "xml" and "sys". These are both XML articles, and their contents affects the article they're associated with. Any sys:article is readable and writable only by users in the "admin" group.

  • The sys:article holds security information: owner, read-groups, write-groups
  • An xml:article is an XML article of docType "xmlwiki.dtd"
  • The xml:article contains the transform-list, publish-list and base-class-list

More Specifically

There are five hooks now! but it's most efficient like this :-)

  • INIT: This hooks into index.php after the environment has been established, but before any article or input processing occurs. This does the require_once of xmlwiki.php.
  • INPUT: This hooks into index.php before input processing so it can direct any XPath inputs into the article-DOM and transform them into a standard input for xmlWiki. This also handles write-permissions.
  • PARSER: This hooks into the parse-function in includes/Parser.php. If the article is XML, it is validated and domificated. If it includes XSLT references, or has a transform-list in its xml:article then those transforms are also applied here. The article will be back in string form again after this.
  • SKIN: This is a new compulsory skin (skins/xwskin.php) which renders the page output as a DOM-object so xmlWiki can apply design transforms to it rather than PHP-based templates. If raw content is requested, then only the articles own transforms are applied, because skins and page-layout are part of the xmlWiki environment, not the article.
  • OUTPUT: This replaces the output rendering in index.php. It handles read permissions and transforms the xwskin-generated DOM-output with xmlwiki.xslt.

Current scripts:

Templates & Transforms:

Wiki Objects available from OUTPUT-hook: