Difference between revisions of "Xmlwiki.php"
From Organic Design wiki
m |
m |
||
(22 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
+ | {{legacy}} | ||
+ | <source lang="php"> | ||
# xmlWiki - MediaWiki XML Hack | # xmlWiki - MediaWiki XML Hack | ||
# Nad - Started: 2005-05-18 | # Nad - Started: 2005-05-18 | ||
Line 13: | Line 15: | ||
$wgHooks['Input'][] = 'xwInputHook'; | $wgHooks['Input'][] = 'xwInputHook'; | ||
$wgHooks['Output'][] = 'xwOutputHook'; | $wgHooks['Output'][] = 'xwOutputHook'; | ||
+ | $wgHooks['ParserFunctions'][] = 'xwTransclusionSecurity'; | ||
+ | function xwTransclusionSecurity(&$title,&$text,$args,$argc) { | ||
+ | $title = ereg_replace('^:','',$title); | ||
+ | if ($title && !xwArticleAccess($title)) { $text = 'Sorry, article not readable!'; $title = false; } | ||
+ | return true; | ||
+ | } | ||
# System globals | # System globals | ||
Line 146: | Line 154: | ||
# Scan POST and apply any XPath inputs | # Scan POST and apply any XPath inputs | ||
− | foreach ( $_REQUEST as $query => $value ) { | + | foreach ($_REQUEST as $query => $value) { |
− | if ( ereg('^xpath.3A.+.3A', $query) ) $query = urldecode( $query ); | + | if (ereg('^xpath.3A.+.3A', $query)) $query = urldecode( $query ); |
− | if ( ereg('^xpath:.+:', $query) ) xwSetProperty( $xwArticleProperties, $query, $value ); | + | if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, str_replace('&', '%26', $value)); |
} | } | ||
Line 232: | Line 240: | ||
# Permhack: remove private info if search result | # Permhack: remove private info if search result | ||
− | if ( isset($_REQUEST['search']) && !$xwIsAdmin ) $xwArticle = | + | if ( isset($_REQUEST['search']) && !$xwIsAdmin ) $xwArticle = preg_replace( |
− | '/<li><a href.+?>(.+?)<\\/a>.+?<\\/li>/ | + | '/<li><a href.+?>(.+?)<\\/a>.+?<\\/li>/ise', |
− | + | 'xwArticleAccess($1,"read")?$0:""', | |
$xwArticle | $xwArticle | ||
); | ); | ||
Line 256: | Line 264: | ||
# If this text-fragment is our article, apply transforms | # 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 ( $xwSave or is_object($xwArticle) 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 ) { | if ( !$xwReadable ) { | ||
Line 266: | Line 274: | ||
# - 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, 'PreParserHook/text' ); | + | if ( !is_object($xwArticle) and preg_match("/^<\\?xml/i", $text) ) xwDomificateArticle( $text, 'PreParserHook/text' ); |
xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK' ); | xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK' ); | ||
− | + | xwUndomificateArticle( $text, $xwArticleTitle ); | |
} | } | ||
elseif ( $xwDebug ) xwMessage( 'Parsing other content' ); | elseif ( $xwDebug ) xwMessage( 'Parsing other content' ); | ||
Line 338: | Line 346: | ||
$access = count( array_intersect( $access, $xwUserGroups ) ); | $access = count( array_intersect( $access, $xwUserGroups ) ); | ||
} | } | ||
− | return $access; | + | return $access > 0; |
} | } | ||
Line 407: | Line 415: | ||
if ( eregi('\\.py$', $title) ) return 'python'; | if ( eregi('\\.py$', $title) ) return 'python'; | ||
if ( eregi('\\.java$', $title) ) return 'java'; | if ( eregi('\\.java$', $title) ) return 'java'; | ||
− | if ( eregi('\\. | + | if ( eregi('\\.(c\\+\\+|cpp)$', $title)) return 'cpp'; |
− | if ( eregi('\\. | + | if ( eregi('\\.[ch]$', $title) ) return 'c'; |
if ( eregi('\\.js$', $title) ) return 'javascript'; | if ( eregi('\\.js$', $title) ) return 'javascript'; | ||
if ( ereg('^#![/a-zA-Z0-9]+sh', $article) ) return 'bash'; | if ( ereg('^#![/a-zA-Z0-9]+sh', $article) ) return 'bash'; | ||
Line 461: | Line 469: | ||
$article = $xml; | $article = $xml; | ||
} | } | ||
− | else $article = $article-> | + | else $article = $article->saveXML(); |
return true; | return true; | ||
} | } | ||
Line 650: | Line 658: | ||
$result->replace_node($newnode); | $result->replace_node($newnode); | ||
} | } | ||
− | else $ | + | else $result->parentNode->replaceChild($properties->createElement($result->nodeName, $value), $result); |
} | } | ||
} | } | ||
Line 773: | Line 781: | ||
# - needed due to different DOM implementation on 4 and 5 (and none on 3) | # - needed due to different DOM implementation 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"; } | ||
+ | |||
+ | # Add a log entry to a log article ([[[[XmlWiki Log]]]] is default) | ||
+ | function xwLog($comment, $title = 'XmlWiki Log') { | ||
+ | global $wgLang; | ||
+ | $ts = $wgLang->timeanddate(wfTimestampNow(),true); | ||
+ | $log = xwArticleContent($title)."\n*$ts : $comment"; | ||
+ | $a = new Article(Title::newFromText($title)); | ||
+ | $a->quickEdit($log); | ||
+ | } | ||
# Parse wikitext string and return HTML string | # Parse wikitext string and return HTML string | ||
Line 793: | Line 810: | ||
$properties = xwArticleProperties($title); | $properties = xwArticleProperties($title); | ||
xwSetProperty($properties,'xpath:/properties:data','document.php'); | xwSetProperty($properties,'xpath:/properties:data','document.php'); | ||
+ | # If no language specified in properties, guess from name and content | ||
+ | xwGetProperty($properties,'language',$lang); | ||
+ | if (!$lang) xwSetProperty($properties,'language',xwArticleType($title,$text)); | ||
xwReduceTransformStack($text,$properties,$title,'data','xwXmlWikiParse/preParse/data'); | xwReduceTransformStack($text,$properties,$title,'data','xwXmlWikiParse/preParse/data'); | ||
$tmp = $wgHooks['PreParser']; | $tmp = $wgHooks['PreParser']; | ||
Line 805: | Line 825: | ||
return $text; | return $text; | ||
} | } | ||
+ | </source> |
Latest revision as of 22:18, 20 November 2019
# 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['ArticleSaveComplete'][] = 'xwPostParserHook';
$wgHooks['ParserAfterTidy'][] = 'xwPostParserHook';
$wgHooks['Input'][] = 'xwInputHook';
$wgHooks['Output'][] = 'xwOutputHook';
$wgHooks['ParserFunctions'][] = 'xwTransclusionSecurity';
function xwTransclusionSecurity(&$title,&$text,$args,$argc) {
$title = ereg_replace('^:','',$title);
if ($title && !xwArticleAccess($title)) { $text = 'Sorry, article not readable!'; $title = false; }
return true;
}
# System globals
$xwDebug = false;
$xwMessages = array();
$xwArticleCache = array();
$xwStyleSheets = array();
$xwTemplate = null;
$xwParserHookCalled = false;
$xwSkinHookCalled = false;
$xwMsgToken = '<!--xwMsg-->';
$xwCssToken = '<!--xwCss-->';
$xwScript = $wgScript;
$xwTransformID = 1;
# User globals
$xwUserName = ucwords( $wgUser->mName );
$xwAnonymous = ereg( "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", $xwUserName );
$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 default properties article
$xwDefaultProperties = $xwDefaultPropertiesXML = xwArticleContent( 'default-properties.xml', false );
xwDomificateArticle( $xwDefaultProperties, 'defaults' );
# 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 ( $p = $xwIsProperties ? xwDomificateArticle( $xwArticle ) : xwArticleProperties( $xwArticleTitle ) )
# Start with default-properties and merge article props (so new transforms on top)
$xwArticleProperties =
'<?xml version="1.0" standalone="yes"?>
<!DOCTYPE xmlwiki:properties SYSTEM "xmlwiki-properties.dtd">
<properties/>';
xwDomificateArticle( $xwArticleProperties, 'CreateProperties' );
xwMergeDOM( $xwArticleProperties, $xwDefaultProperties );
xwMergeDOM( $xwArticleProperties, $p );
$xwArticleReadableBy = xwGetListByTagname( $xwArticleProperties, 'read' );
$xwArticleWritableBy = xwGetListByTagname( $xwArticleProperties, 'write' );
if ( $xwIsProperties ) $xwArticleProperties = false;
}
if ( !is_object( $xwArticleProperties ) ) $xwArticleProperties = $xwDefaultProperties;
if ( $xwIsSpecial || !count( $xwArticleReadableBy ) ) $xwArticleReadableBy = array('anyone');
if ( !count($xwArticleWritableBy) ) $xwArticleWritableBy = array('anyone');
# 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);
}
}
# Apply init transforms
xwReduceTransformStack( $xwArticle, $xwArticleProperties, $xwArticleTitle, 'init', 'INIT-HOOK' );
# ---------------------------------------------------------------------------------------------------------------------- #
# INPUT HOOK
# Parse and process input from forms
function xwInputHook() {
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST;
global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $wgTitle;
global $xwUserGroups, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
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, str_replace('&', '%26', $value));
}
# if saving a pseudo-namespace...
if ($xwSave) {
if ( $xwIsProperties || $xwIsSystem ) {
# wpTextbox1 should be valid XML to be saved
$tb = $_REQUEST['wpTextbox1'];
xwDomificateArticle( $tb, 'POST-DATA' );
if ( !is_object($tb) ) {
xwMessage('A meta-article must be valid XML! article not saved.', 'red');
$action = 'view';
$xwSave = $xwEdit = false;
}
elseif ( $xwIsProperties && !$xwIsAdmin ) {
foreach ( xwGetListByTagname($tb, 'write') as $perm ) {
if ( !in_array( $perm, $xwUserGroups ) ) {
xwMessage("Only members of \"$perm\" can set permissions for \"$perm\"! article not saved.", 'red');
$action = 'view';
$xwSave = $xwEdit = false;
}
}
}
}
}
}
# ---------------------------------------------------------------------------------------------------------------------- #
# OUTPUT HOOK
# Post-process and output article
function xwOutputHook() {
global $wgUser, $xwUserName, $wgOut, $xwDebug, $xwIsProperties, $xwIsSystem, $xwIsAdmin;
global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwEdit, $action;
if ($action == 'raw') return;
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) {
$a = xwArticleContent( str_replace( 'Xml:', '', $xwArticleTitle ) );
if ( preg_match("/\\[\\[Category:(.+?)]]/", $a, $n) && $xwArticle = xwArticleProperties('Category:'.$n[1]) ) {
xwUndomificateArticle( $xwArticle );
xwMessage('Note: This article has no properties, default content has been inherited from Xml:'.$n[1], 'red');
}
else {
xwMessage('Note: This article has no properties, a general default has been generated', 'red');
$xwArticle = "<?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<data></data>\n\t<view></view>\n\t<edit></edit>\n\t<save></save>\n</properties>\n";
}
$xwArticle = $m[1].$xwArticle.$m[2];
}
}
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 );
# Permhack: remove private info if search result
if ( isset($_REQUEST['search']) && !$xwIsAdmin ) $xwArticle = preg_replace(
'/<li><a href.+?>(.+?)<\\/a>.+?<\\/li>/ise',
'xwArticleAccess($1,"read")?$0:""',
$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 is_object($xwArticle) 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 ( !is_object($xwArticle) and preg_match("/^<\\?xml/i", $text) ) xwDomificateArticle( $text, 'PreParserHook/text' );
xwReduceTransformStack( $text, $xwArticleProperties, $xwArticleTitle, 'data', 'PREPARSER-HOOK' );
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
# - this hook was not implemented or had been removed,
# I've put in on the official ArticleSaveComplete hook for now
# - was function xwPostParserHook( &$text )
function xwPostParserHook( &$article, &$user, &$text, &$summary, &$minoredit, &$watchthis, &$sectionanchor ) {
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' );
# Now apply the save-stack
$dummy = "";
xwReduceTransformStack( $dummy, $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
# Return true if passed title readable/writable by current user
function xwArticleAccess( $title, $perm = 'read' ) {
global $xwIsAdmin, $xwUserGroups, $xwDefaultProperties;
if ( !$access = $xwIsAdmin ) {
$properties = xwArticleProperties( $title );
xwMergeDOM( $properties, $xwDefaultProperties );
$access = xwGetListByTagname( $properties, $perm ) + array( 'anyone' );
$access = count( array_intersect( $access, $xwUserGroups ) );
}
return $access > 0;
}
# 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 "[[Special:Userlogin|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
# - returns self if already a properties article
function xwArticleProperties( $articleTitle, $xmlself = true ) {
global $xwDebug;
if ($xwDebug) xwMessage("xwArticleProperties(\"$articleTitle\")",'blue');
$id = ( $xmlself && eregi( '^xml:', $articleTitle ) ) ? $articleTitle : 'xml:'.ucfirst($articleTitle);
if ( !$properties = xwArticleContent( $id, 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('\\.php$', $title) ) return 'php';
if ( eregi('\\.css$', $title) ) return 'css';
if ( eregi('^talk:', $title) ) return '';
if ( preg_match("/^<\\?xml.+?\\?>.*<xsl:stylesheet/is", $article) ) return 'xslt';
if ( eregi('^(sys|xml):.+$', $title) || ereg('^<\\?xml', $article) ) return 'xml';
if ( eregi('^<\\?html', $article ) || eregi('\\.html$', $title)) return 'html4strict';
if ( eregi('\\.p[lm]$', $title) ) return 'perl';
if ( ereg('^#![/a-zA-Z0-9]+\\/perl', $article) ) return 'perl';
if ( eregi('\\.as$', $title) ) return 'actionscript';
if ( eregi('\\.dtd$', $title) ) return 'xml';
if ( eregi('\\.tex$', $title) ) return 'tex';
if ( eregi('\\.nt$', $title) ) return 'dna';
if ( eregi('\\.r$', $title) ) return 'r';
if ( eregi('\\.py$', $title) ) return 'python';
if ( eregi('\\.java$', $title) ) return 'java';
if ( eregi('\\.(c\\+\\+|cpp)$', $title)) return 'cpp';
if ( eregi('\\.[ch]$', $title) ) return 'c';
if ( eregi('\\.js$', $title) ) return 'javascript';
if ( ereg('^#![/a-zA-Z0-9]+sh', $article) ) return 'bash';
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->saveXML();
return true;
}
# ---------------------------------------------------------------------------------------------------------------------- #
# TRANSFORM FUNCTIONS
# Apply all transforms in passed stack
function xwReduceTransformStack( &$article, &$properties, $title, $event, $id = '' ) {
global $xwDebug;
$GLOBALS['xwTransformID']++;
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\" attached 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;
if ( $xwDebug ) xwMessage( "xwApplyPHP( $title , $tTitle )", 'blue' );
# Get transform perms
if ( !count( preg_grep( '/^(admin)|(dev)$/i', xwGetListByTagname( $tProperties, 'write' ) ) ) ) {
xwMessage( "Failed to execute \"$tTitle\", must be writable only by dev or admin!", '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 $result->parentNode->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;
}
# Remove all occourences of a certain element
function xwRemoveElement( &$properties, $element ) {
$root = $properties->document_element();
if (php(4)) {
while ( count($tNodes = $root->get_elements_by_tagname($element)) ) $tNodes[0]->unlink_node();
}
else {
while ( ($tNodes = $root->getElementsByTagname($element)) && $tNodes->length ) {
$tNode = $tNodes->item(0);
$tNode->parentNode->removeChild( $tNode );
}
}
}
# ---------------------------------------------------------------------------------------------------------------------- #
# 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[] = "<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);
}
}
# 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;
}
# Return true if PHP major version equals passed int
# - needed due to different DOM implementation on 4 and 5 (and none on 3)
function php($ver) { return substr(phpversion(),0,1) == "$ver"; }
# Add a log entry to a log article ([[[[XmlWiki Log]]]] is default)
function xwLog($comment, $title = 'XmlWiki Log') {
global $wgLang;
$ts = $wgLang->timeanddate(wfTimestampNow(),true);
$log = xwArticleContent($title)."\n*$ts : $comment";
$a = new Article(Title::newFromText($title));
$a->quickEdit($log);
}
# Parse wikitext string and return HTML string
function xwWikiParse(&$text) {
global $wgHooks,$wgTitle,$wgUser;
$tmp = $wgHooks['PreParser'];
$wgHooks['PreParser'] = array();
$parser = new Parser;
$output = $parser->parse($text,$wgTitle,ParserOptions::newFromUser($wgUser),true,false);
$wgHooks['PreParser'] = $tmp;
return $output->getText();
}
# Parse an XmlWiki article and return resulting HTML
function xwXmlWikiParse($title) {
global $wgTitle,$wgHooks,$wgUser,$xwUserName,$xwAnonymous;
$bookmarks = $xwAnonymous ? "Bookmarks:Default" : "Bookmarks:$xwUserName";
$text = str_replace('{{BOOKMARKS}}',$bookmarks,xwArticleContent($title)); # Hack so that {{BOOKMARKS}} can be used in an XmlWiki link
$text = preg_replace('/<!--([^@]+?)-->/s','@@'.'@@$1@@'.'@@',$text); # Hack so that HTML comments in the wikitext are preserved
$properties = xwArticleProperties($title);
xwSetProperty($properties,'xpath:/properties:data','document.php');
# If no language specified in properties, guess from name and content
xwGetProperty($properties,'language',$lang);
if (!$lang) xwSetProperty($properties,'language',xwArticleType($title,$text));
xwReduceTransformStack($text,$properties,$title,'data','xwXmlWikiParse/preParse/data');
$tmp = $wgHooks['PreParser'];
$wgHooks['PreParser'] = array();
$parser = new Parser;
$output = $parser->parse($text,$wgTitle,ParserOptions::newFromUser($wgUser),true,false);
$wgHooks['PreParser'] = $tmp;
$text = $output->getText();
$text = preg_replace('/@{4}([^@]+?)@{4}/s','<!--$1-->',$text); # HTML comments hack
xwSetProperty($properties,'xpath:/properties:view','tree-view.php');
xwReduceTransformStack($text,$properties,$title,'view','xwXmlWikiParse/preParse/view');
return $text;
}