Difference between revisions of "Xmlwiki.php"
(Allow PERL to be *.pl or *.pm and incl *php3,4,5 *phtml for PHP) |
|||
| Line 1: | Line 1: | ||
| − | |||
# xmlWiki - MediaWiki XML Hack | # xmlWiki - MediaWiki XML Hack | ||
| − | # Nad - 2005-05-18 | + | # Nad - Started: 2005-05-18 |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
# 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!'); | ||
| − | + | ||
| + | # 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-> | + | $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 = | + | $xwArticleReadableBy = null; |
| − | $xwArticleWritableBy = | + | $xwArticleWritableBy = null; |
| − | |||
# Get user-system-article and extract users' groups from it if any | # 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')); | $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 : | + | if ($xwArticleProperties = $xwIsProperties ? xwDomificateArticle($xwArticle) : xwArticleProperties($xwArticleTitle)) |
| − | + | $xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read'); | |
| − | $xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read | + | $xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write'); |
| − | $xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write | ||
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)) | + | if (is_object($xwArticleProperties)) xwMergeDOM($xwArticleProperties, $xwDefaultProperties); |
else $xwArticleProperties = $xwDefaultProperties; | else $xwArticleProperties = $xwDefaultProperties; | ||
| − | # | + | # If no language specified in properties, guess from name and content |
if (!xwGetProperty($xwArticleProperties, 'language', $xwLanguage)) | if (!xwGetProperty($xwArticleProperties, 'language', $xwLanguage)) | ||
| − | $ | + | 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 | + | 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) { |
| − | + | 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) { | ||
| − | # | + | # wpTextbox1 should be valid XML to be saved |
if ($xwSave) { | if ($xwSave) { | ||
| − | xwDomificateArticle($ | + | $tb = $_REQUEST['wpTextbox1']; |
| − | if (!is_object($ | + | 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, $ | + | # Raw has already output the page, so exit |
| − | global $xwArticle, $xwArticleProperties, $xwArticleTitle | + | if ($GLOBALS['xwRaw']) return; |
| − | + | ||
| + | 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(); | ||
| − | |||
| − | |||
| − | |||
# 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'); | |
| − | |||
| − | |||
| − | |||
} | } | ||
| + | } | ||
| + | elseif ($xwEdit) { | ||
| + | # Editing normally, apply transforms in edit-list | ||
| + | xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'edit', 'OUTPUT-HOOK'); | ||
} | } | ||
| − | |||
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'); | ||
| − | |||
} | } | ||
| − | + | ||
| + | # Insert stylesheets and messages into output | ||
| + | xwReplaceTokens($xwArticle); | ||
| + | |||
| + | # Output final result | ||
print $xwArticle; | print $xwArticle; | ||
} | } | ||
| + | |||
# ---------------------------------------------------------------------------------------------------------------------- # | # ---------------------------------------------------------------------------------------------------------------------- # | ||
| − | # PARSER HOOK | + | # PRE-PARSER HOOK |
| − | function | + | 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 ($ | ||
| − | |||
# 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'); | + | 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, ' | + | 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 '.($ | + | # Get language and return true back to parser if lang wasn't ours |
| − | # | + | xwGetProperty($xwArticleProperties, 'language', $language); |
| − | + | if ($xwDebug) xwMessage('Content language determined as '.($language ? strtoupper($language) : 'WIKI')); | |
| + | return $language == ''; | ||
| + | } | ||
| + | |||
| + | |||
| + | # ---------------------------------------------------------------------------------------------------------------------- # | ||
| + | # POST-PARSER HOOK | ||
| + | |||
| + | 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 | ||
| − | |||
| − | |||
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('\\. | + | 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) { |
| − | + | 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 ( | + | 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); | ||
| − | |||
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)) { | ||
| − | $ | + | # Get transform language (take a guess if none specified) |
| − | if ($ | + | $tProperties = xwArticleProperties($transform); |
| − | elseif ($ | + | xwGetProperty($tProperties, 'language', $tLang); |
| − | elseif ($ | + | if (!$tLang) $tLang = xwArticleType($transform, $tText); |
| − | elseif ($ | + | 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\" | + | 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, &$ | + | function xwApplyPHP(&$article, &$tCode, $tTitle, &$title, &$properties, &$tProperties, $event) { |
global $xwDebug, $_SERVER; | global $xwDebug, $_SERVER; | ||
| − | if ($xwDebug) xwMessage(" | + | if ($xwDebug) xwMessage("xwApplyPHP( $title , $tTitle )",'blue'); |
# Get transform perms | # Get transform perms | ||
| − | |||
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+$/",'',$ | + | $path = '/properties/'.preg_replace("/\\.\\w+$/",'',$tTitle); |
| − | $script = | + | $script = $GLOBALS['xwScript']; |
ob_start(); | ob_start(); | ||
| − | eval("?>$ | + | 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 | + | # - 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( | + | 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); | ||
| − | + | xwSetProperty($properties, 'language', ''); | |
| − | |||
} | } | ||
} | } | ||
| Line 462: | Line 522: | ||
if (is_object($dom)) { | if (is_object($dom)) { | ||
global $xwDebug; | global $xwDebug; | ||
| − | if ($xwDebug) xwMessage("xwXPathQuery( | + | 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); |
| − | |||
| − | |||
| − | |||
xwExtractMessages(); | xwExtractMessages(); | ||
ob_end_clean(); | ob_end_clean(); | ||
} | } | ||
| − | if ( | + | if (php(4)) return is_array($result) ? $result : array(); |
| − | + | else { | |
| + | $tmp = array(); | ||
| + | foreach ($result as $node) $tmp[] = $node; | ||
| + | return $tmp; | ||
| + | } | ||
} | } | ||
| − | function xwGetProperty(&$properties, $ | + | function xwGetProperty(&$properties, $query, &$value) { |
| − | if (count($results = xwXPathQuery($properties, | + | global $xwDebug; |
| − | $value = $results[ | + | 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:(.+ | + | 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) $ | + | 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 = $properties->create_element($node); | |
| + | $node->set_content($value); | ||
| + | $result->append_child($node); | ||
| + | } | ||
| + | else $result->appendChild($properties->createElement($node, $value)); | ||
} | } | ||
| − | |||
} | } | ||
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 = $properties->create_element($result->tagname); | |
| − | + | $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; | ||
| − | |||
# 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( | + | 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 | + | return true; |
} | } | ||
| Line 565: | Line 648: | ||
# ---------------------------------------------------------------------------------------------------------------------- # | # ---------------------------------------------------------------------------------------------------------------------- # | ||
# UTILITY FUNCTIONS | # UTILITY FUNCTIONS | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
# Extract error-messages from captured output and put in proper message-queue | # Extract error-messages from captured output and put in proper message-queue | ||
| − | function xwExtractMessages() { | + | function xwExtractMessages($where = false) { |
| − | + | if ($where) $where = " of \"$where\""; | |
$err = preg_replace("/<.+?>/", "", ob_get_contents()); | $err = preg_replace("/<.+?>/", "", ob_get_contents()); | ||
| − | |||
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 = 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
- xmlWiki - MediaWiki XML Hack
- Nad - Started: 2005-05-18
- Exit if not included from index.php
defined('MEDIAWIKI') or die('xmlwiki.php must be included from MediaWiki\'s index.php!'); if (php(3)) die('xmlWiki cannot run on less than PHP4!');
- Add the rest of the hooks
$wgHooks['PreParser'][] = 'xwPreParserHook'; $wgHooks['ParserBeforeStrip'][] = 'xwPreParserHook'; $wgHooks['PostParser'][] = 'xwPostParserHook'; $wgHooks['ParserAfterTidy'][] = 'xwPostParserHook'; $wgHooks['Input'][] = 'xwInputHook'; $wgHooks['Output'][] = 'xwOutputHook';
- 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'];
- 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->getPrefixedText(); $xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false); $xwArticleProperties = null; $xwIsProperties = preg_match('/^xml:.+$/i', $xwArticleTitle); $xwIsSystem = preg_match('/^sys:.+$/i', $xwArticleTitle); $xwIsUser = preg_match('/^user:.+$/i', $xwArticleTitle); $xwIsSpecial = preg_match('/^special:.+$/i', $xwArticleTitle); $xwIsAdmin = false; $xwArticleReadableBy = null; $xwArticleWritableBy = null;
- Get user-system-article and extract users' groups from it if any
$xwUserSYS = xwArticleContent("sys:$xwUserName"); xwDomificateArticle($xwUserSYS, "sys:$xwUserName"); $xwUserGroups = array_merge($xwUserGroups, xwGetListByTagname($xwUserSYS, 'groups'));
- Read user-prefs
xwGetProperty($xwUserSYS, 'xpath:/user/debug', $xwDebug);
- Get article-meta-file and extract article-perms
if ($xwIsSystem) $xwArticleReadableBy = $xwArticleWritableBy = array('admin'); else { # Get xml:article (self if already xml:*) if ($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');
- Merge default-properties into article-properties object
$xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'), 'defaults'); if (is_object($xwArticleProperties)) xwMergeDOM($xwArticleProperties, $xwDefaultProperties); else $xwArticleProperties = $xwDefaultProperties;
- If no language specified in properties, guess from name and content
if (!xwGetProperty($xwArticleProperties, 'language', $xwLanguage)) xwSetProperty($xwArticleProperties, 'language', xwArticleType($xwArticleTitle, $xwArticle));
- Set perms for this request
if ($xwDebug) xwMessage('PERMISSIONS:','green'); if (in_array('admin', $xwUserGroups)) $xwIsAdmin = $xwReadable = $xwWritable = true; else { $xwReadable = 0 < count(array_intersect($xwArticleReadableBy, $xwUserGroups)); $xwWritable = 0 < count(array_intersect($xwArticleWritableBy, $xwUserGroups)); } if ($xwDebug) { xwMessage('Groups: '.join(', ', $xwUserGroups)); xwMessage('Readable ('.($xwReadable?'yes':'no').'): '.join(', ', $xwArticleReadableBy)); xwMessage('Writable ('.($xwWritable?'yes':'no').'): '.join(', ', $xwArticleWritableBy)); }
- Divert to access-denied article if not readable
if (!$xwReadable) { $action = 'view'; $xwSave = $xwEdit = false; }
- Handle security for Move
if (!$xwIsAdmin and ($target = $_REQUEST['target']) ) { if ($xwArticleProperties = new Article(Title::newFromText("xml:$target"))) $xwArticleProperties = $xwArticleProperties->getContentWithoutUsingSoManyDamnGlobals(); $writableBy = xwGetListByTagname(xwDomificateArticle($xwArticleProperties), 'write'); if (!count($writableBy)) $writableBy = array('anyone'); if (!count(array_intersect($writableBy, $xwUserGroups))) { $xwArticleTitle = $target; xwMessage("Sorry article \"$xwArticleTitle\" not movable!",'red'); $wgArticle = new Article($wgTitle = Title::newFromText($target)); $xwArticle = $wgArticle->getContent(false); } }
- ---------------------------------------------------------------------------------------------------------------------- #
- INPUT HOOK
- 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'; } } }
}
- ---------------------------------------------------------------------------------------------------------------------- #
- OUTPUT HOOK
- 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; }
- ---------------------------------------------------------------------------------------------------------------------- #
- 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 == ; }
- ---------------------------------------------------------------------------------------------------------------------- #
- 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 = ; }
- ---------------------------------------------------------------------------------------------------------------------- #
- SKIN HOOK
- If attempted xml encountered, try to domificate and transform
- - Raw requests don't get here
function xwSkinHook(&$tmpl) {
global $xwArticle, $xwArticleTitle, $xwArticleProperties, $xwRaw; global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;
# Extract article from existing template structure if ($xwDebug) xwMessage('SKIN-HOOK:','green'); $xwSkinHookCalled = true; $xwTemplate = $tmpl; if ($xwReadable) $xwArticle = $xwTemplate->data['bodytext'];
# Apply view-transforms # - "default-skin.php" builds XML output structure # - "default-skin.xslt" transforms XML to HTML with table-layout # - "default-skin.css" transforms HTML with design xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'view', 'SKIN-HOOK'); }
- ---------------------------------------------------------------------------------------------------------------------- #
- ARTICLE FUNCTIONS
- - these all use super-globals since they can be called from within evals
- 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; }
- 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
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 ; }
- Convert passed article to a DOM object
- - Article is unchanged if not valid XML
function xwDomificateArticle(&$article, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue'); ob_start(); if ($article) { if (php(4)) $dom = domxml_open_mem($article); else $dom = DOMDocument::loadXML($article); } if (isset($dom) && is_object($dom)) $article = $dom; else { # Could not convert, extract error messages from output if ($xwDebug) xwMessage("Failed :-(", 'red'); xwExtractMessages(); } ob_end_clean(); return $article; }
- Reduce article to a string if it's a DOM object
- - ie if all xslt's had xml-output-method
function xwUndomificateArticle(&$article, $id = ) { if (!is_object($article)) return false; global $xwDebug; if ($xwDebug) xwMessage("xwUndomificate(\"$id\")",'blue'); # TODO: # Validate, report errors # xwApplyXSLT($article, $xslt) if xml referrs one # Convert DOM back to XML if still an object if (php(4)) { # Attempt to fix PHP4's crap dump $xml = "<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $children = $article->child_nodes(); $xml .= trim($article->dump_node($children[0]))."\n"; for ($i = 1; $i < count($children); $i++) { $top = $children[$i]; $name = $top->node_name(); $xml .= "<$name>\n"; foreach ($top->child_nodes() as $child) { $line = trim($article->dump_node($child)); if ($line) $xml .= "\t$line\n"; } $xml .= "</$name>\n"; } $article = $xml; } else $article = $article->dump_mem(); return true; }
- ---------------------------------------------------------------------------------------------------------------------- #
- TRANSFORM FUNCTIONS
- 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'); } }
- 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'); } }
- 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(); }
- Append CSS stylesheet link to output
function xwApplyCSS($title, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyCSS( $title , $tName )",'blue'); $xwStyleSheets[] = $tName; }
- Apply XSLT to article-dom
- - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
- - if output-method is html, article will get stringified and language updated
function xwApplyXSLT(&$article, $title, $properties, &$xslt, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyXSLT( $title , $tName )",'blue'); if (is_object($article)) { ob_start(); if ($tObject = domxml_xslt_stylesheet($xslt)) { $tResult = $tObject->process($article); # If output-method is html, use XSLT's dump_mem to create an html-string if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt)) { $tResult = $tObject->result_dump_mem($tResult); xwSetProperty($properties, 'language', ); } } xwExtractMessages(); ob_end_clean(); if (isset($tResult)) $article = $tResult; } else $xwStyleSheets[] = $tName; }
- ---------------------------------------------------------------------------------------------------------------------- #
- DOM FUNCTIONS
- Return result set of an XPath query on passed DOM-object
function xwXPathQuery(&$dom, $query) { $result = array(); if (is_object($dom)) { global $xwDebug; if ($xwDebug) xwMessage("xwXPathQuery( $query )",'blue'); ob_start(); if (php(4)) { $context = $dom->xpath_new_context(); if ($xpath = $context->xpath_eval($query)) $result = @$xpath->nodeset; elseif ($xwDebug) xwMessage("Query \"$query\" failed :-(", 'red'); } else if ($xpath = new DOMXPath($dom)) $result = $xpath->query($query); xwExtractMessages(); ob_end_clean(); } if (php(4)) return is_array($result) ? $result : array(); else { $tmp = array(); foreach ($result as $node) $tmp[] = $node; return $tmp; } }
function xwGetProperty(&$properties, $query, &$value) { global $xwDebug; if (!preg_match("/^xpath:(.+)$/", $query, $match)) $match = array(, "/properties/$query"); if (count($results = xwXPathQuery($properties, $match[1])) == 0) return false; if (php(4)) $value = $results[count($results)-1]->get_content(); else $value = $results[count($results)-1]->nodeValue; if ($xwDebug) xwMessage("xwGetProperty( $query ) returned \"$value\"", 'purple'); return true; }
- - Syntax: xpath:XPathQuery:[[@]node][+] = value
- - if "node" exists, then a new element (or att if "@node") is created for the value
- - if "+" exists, content is appended, else replaced
function xwSetProperty(&$properties, $query, $value) { global $xwDebug; if ($xwDebug) xwMessage("xwSetProperty( $query , $value )", 'purple'); if (!is_object($properties)) return false; if (!preg_match("/^xpath:(.+):(@?)(\\w*?)(\\+?)$/", $query, $match)) { # If not xpath, remove property before creating $tNodes = xwXPathQuery($properties, "/properties/$query"); foreach ($tNodes as $tNode) if (php(4)) $tNode->unlink_node(); else $tNode->parentNode->removeChild($tNode); $match = array(, '/properties', , $query, ); } list(, $xpath, $att, $node, $append) = $match; foreach (xwXPathQuery($properties, $xpath) as $result) { if ($node) { # create new element/attribute in result-node if ($att) { if (php(4)) $result->append_child($properties->create_attribute($node, $value)); else $result->setAttribute($node, $value); } else { if (php(4)) { $node = $properties->create_element($node); $node->set_content($value); $result->append_child($node); } else $result->appendChild($properties->createElement($node, $value)); } } else { # node not set, replace or append current-result-value if ($append) { if (php(4)) $result->set_content( ($result->get_content()).$value ); else $result->nodeValue .= $value; } else { if (php(4)) { $newnode = $properties->create_element($result->tagname); $newnode->set_content($value); $result->replace_node($newnode); } else $properties->replaceChild($properties->createElement($result->nodeName, $value), $result); } } } return true; }
- Return array of comma-separated-items in element content
- - if more than one element all are split and appended into the list
function xwGetListByTagname(&$xml, $tag) { $list = array(); if (is_object($xml)) { if (php(4)) { $root = $xml->document_element(); foreach ($root->get_elements_by_tagname($tag) as $element) if ($csv = $element->get_content()) $list = array_merge($list, split(',', $csv)); } else { foreach ($xml->documentElement->getElementsByTagname($tag) as $element) if ($csv = $element->nodeValue) $list = array_merge($list, split(',', $csv)); } } return $list; }
- Merge the two passed DOM objects
- - appends, does not overwrite
function xwMergeDOM(&$dom1, &$dom2, $id1 = 'dom1', $id2 = 'dom2') { global $xwDebug; # Return if first DOM not a valid properties article if (!is_object($dom1)) return false; if (php(4)) $docType = $dom1->doctype(); else $docType = $dom1->doctype; if (!ereg('^xmlwiki:properties', $docType->name)) { if ($xwDebug) xwMessage('xwMergeDOM: First parameter is not an xmlwiki:properties object!','red'); return false; } # Return DOM1 if DOM2 not valid properties if (!is_object($dom2)) return $dom1; if (php(4)) $docType = $dom2->doctype(); else $docType = $dom2->doctype; if (!ereg('^xmlwiki:properties', $docType->name)) return $dom1; # Both are ok, perform the merge if ($xwDebug) xwMessage("xwMergeDOM( &$id1 , $id2 )",'blue'); if (php(4)) { $root1 = $dom1->document_element(); $root2 = $dom2->document_element(); foreach ($root2->child_nodes() as $node) $root1->append_child($node->clone_node(true)); } else $dom1->documentElement->appendChild($dom1->importNode($dom2->documentElement, true)); return true; }
- ---------------------------------------------------------------------------------------------------------------------- #
- UTILITY FUNCTIONS
- Extract error-messages from captured output and put in proper message-queue
function xwExtractMessages($where = false) { if ($where) $where = " of \"$where\""; $err = preg_replace("/<.+?>/", "", ob_get_contents()); foreach (split("\n", $err) as $msg) if (trim($msg)) { if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1]$where)"; $msg = preg_replace('/ in .+? on line.+?([0-9]+)/', " (Line $1$where)", $msg); if (isset($line)) $msg .= $line; xwMessage($msg, 'red'); } }
- Add message to queue
function xwMessage($msg, $col = '#000080') { global $xwMessages; $msg = htmlentities($msg); return $xwMessages[] = "$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 .= '
';
$article = str_replace($xwMsgToken, $msg, $article); } }
- 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;
}
- Return true if PHP major version equals passed int
- - needed due to different DOM implimentation on 4 and 5 (and none on 3)
function php($ver) { return substr(phpversion(),0,1) == "$ver"; }



