Difference between revisions of "Xmlwiki.php"
| Line 548: | Line 548: | ||
foreach ($data['navigation_urls'] as $navlink) { | foreach ($data['navigation_urls'] as $navlink) { | ||
$nav .= "<li id=\"".htmlspecialchars($navlink['id'])."\">"; | $nav .= "<li id=\"".htmlspecialchars($navlink['id'])."\">"; | ||
| + | $nav .= '<a href="'.htmlspecialchars($navlink['href']).'">'.htmlspecialchars($navlink['text']).'</a>'; | ||
Revision as of 22:12, 18 June 2005
<?php
- xmlWiki - MediaWiki XML Hack
- Nad - 2005-05-18
$xwDebug=1;
- INIT
- SECURITY
- INPUT HOOK
- OUTPUT HOOK
- PARSER HOOK
- SKIN HOOK
- ARTICLE FUNCTIONS
- UTILITY FUNCTIONS
- ---------------------------------------------------------------------------------------------------------------------- #
- TODO:
- - Undomificate: validation, xslt
- - Transform: check perms on php-execution
- Publish:
- - xml:article - holds article publish-list
- - 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:
- - 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 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 = ; $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 (ereg('^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 (ereg('^sys:(.+)$', $xwArticleTitle)) { # This is a sys-pseudo-namespace # - only admin can read/write sys: $xwArticleReadableBy = $articleWritableBy = 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) { xwMessage('no'); $action = 'view'; $xwSave = $xwEdit = false; $wgTitle = Title::newFromText('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):/', $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.*?>/", '
$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 = 'Contents
'.htmlspecialchars($data['title']).'
'; $content .= ''.htmlspecialchars($tmpl->translator->translate('tagline')).'
'; $content .= '$content .= "$xwArticle"; # <---- Actual article content inserted here
if ($data['catlinks']) $content .= '# Action-links (remove edit link if not writable)
$actions = 'Views
- ';
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";# } }
$actions .= '# Personal-links
$personal = ''.htmlspecialchars($tmpl->translator->translate('personaltools')).'
'; $personal .= '- ';
- '; $href = '<a href="'.htmlspecialchars($item['href']).'"'; if (!empty($item['class'])) $href .= 'class="'.htmlspecialchars($item['class']).'"'; $href .= '>'.htmlspecialchars($item['text']).'</a>'; $personal .= "$href ";
foreach ($data['personal_urls'] as $key => $item) {
$personal .= '}
$personal .= "# Logo
$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>$logo .= '<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>';
# Navigation
$nav = '


