Xmlwiki.php

From Organic Design wiki
Revision as of 10:21, 28 July 2005 by Nad (talk | contribs)

<?php

  1. xmlWiki - MediaWiki XML Hack
  2. Nad - 2005-05-18
  1. MEDIAWIKI HOOKS
  2. xwInputHook (post)
  3. xwOutputHook (save)
  4. xwParserHook (data)
  5. xwSkinHook (view, edit)
  6. ARTICLE FUNCTIONS
  7. xwArticleContent
  8. xwArticleType
  9. xwDomificateArticle
  10. xwUndomificateArticle
  11. TRANSFORM FUNCTIONS
  12. xwReduceTransformStack
  13. xwApplyTransform
  14. xwApplyPHP
  15. xwApplyCSS
  16. xwApplyXSLT
  17. DOM FUNCTIONS
  18. xwXPathQuery
  19. xwGetProperty
  20. xwSetProperty
  21. xwGetListByTagname
  22. xwMergeDOM
  23. UTILITY FUNCTIONS
  1. ---------------------------------------------------------------------------------------------------------------------- #
  1. TODO:
  2. - Must deal with page-move: security and also pseudo-namespace moving (maybe use proper namespace?)
  3. - setProperty needs to create element if not there so <output> not needed in swf properties
  4. - XPath bug with create element (is it the same setProperty bug?)
  5. - also language-override in ming-swf.php can then be simpler too
  6. - move geshi/wiki out to a default language.php (which uses the <language> element)
  1. - TransformArticle:
  2. - VALIDATE: validate article-properties against xmlwiki:properties.dtd (does PHP4 do this?)
  3. - TRAP: fatal errors in php-transform execution
  4. - Undomificate: validation, xslt
  5. - Check (later validate) doctype of user:sys (change users pages on site first!)
  6. - Check (later validate) doctype of user/properties on save (still save though)
  1. BUGS:
  2. - Rob is admin and can't write to Nad pages
  3. - Pages can be moved regardless of perms!!!
  4. - login error goes to old-wiki
  5. - tabReplace doesn't work for /^\t+.+\t.+$/
  1. LATER:
  2. - allow XIncludes to build large docs from others
  3. - allow docBook and DSSSL content
  4. - Maybe only use XML transforms and use PHP via PI's
  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. INIT
  1. 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!'); require_once('extensions/geshi/geshi.php');

  1. System globals

$xwDebug = false; $xwMessages = array(); $xwArticleCache = array(); $xwStyleSheets = array(); $xwTemplate = null; $xwParserHookCalled = false; $xwSkinHookCalled = false; $xwMsgToken = ;

  1. 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']);

  1. Article globals

$xwArticleTitle = $wgTitle->getPrefixedURL(); $xwArticle = $xwPreview ? $wgRequest->gettext('wpTextbox1') : $wgArticle->getContent(false); $xwArticleProperties = null; $xwIsProperties = preg_match('/^xml:.+$/i', $xwArticleTitle); $xwIsSystem = preg_match('/^sys:.+$/i', $xwArticleTitle); $xwIsUser = preg_match('/^user:.+$/i', $xwArticleTitle); $xwIsAdmin = false; $xwArticleReadableBy = array('anyone'); $xwArticleWritableBy = array('anyone'); $xwLanguage = ;

  1. Get user-system-article and extract users' groups from it if any

if ($xwUserSYS = xwArticleContent("sys:$xwUserName")) xwDomificateArticle($xwUserSYS, "sys:$xwUserName"); $xwUserGroups = array_merge($xwUserGroups, xwGetListByTagname($xwUserSYS, 'groups'));

  1. Read user-prefs

xwGetProperty($xwUserSYS, 'debug', $xwDebug);

  1. Get article-meta-file and extract article-perms

if ($xwIsSystem) $xwArticleReadableBy = $xwArticleWritableBy = array('admin'); else { # Get xml:article (self if already xml:*) if ($xwArticleProperties = $xwIsProperties ? $xwArticle : xwArticleContent($id = 'xml:'.ucwords($xwArticleTitle))) xwDomificateArticle($xwArticleProperties, $xwIsProperties ? $xwArticleTitle : $id); $xwArticleReadableBy = xwGetListByTagname($xwArticleProperties, 'read') + array('anyone'); $xwArticleWritableBy = xwGetListByTagname($xwArticleProperties, 'write') + array('anyone'); if ($xwIsProperties) $xwArticleProperties = false; }

  1. Merge default-properties into article-properties object

$xwDefaultProperties = xwDomificateArticle(xwArticleContent('default-properties.xml'), 'defaults'); if (is_object($xwArticleProperties)) $xwArticleProperties = xwMergeDOM($xwArticleProperties, $xwDefaultProperties); else $xwArticleProperties = $xwDefaultProperties;

  1. Get language from properties, or articleType()

if (!xwGetProperty($xwArticleProperties, 'language', $xwLanguage)) $xwLanguage = xwArticleType($xwArticleTitle, $xwArticle);

  1. 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)); }

  1. Divert to access-denied article if not readable

if (!$xwReadable) { $action = 'view'; $xwSave = $xwEdit = false; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. INPUT HOOK
  1. Parse and process input from forms

function xwInputHook() {

global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwWritable, $_REQUEST; global $xwDebug, $xwSave, $xwEdit, $action, $xwUserName, $wgArticle, $xwIsProperties, $xwIsSystem; if ($xwDebug) xwMessage('INPUT-HOOK:','green');

# If not writable, change action to view if (!$xwWritable && ($action != 'view')) { xwMessage('Sorry, article not writable, action changed to "view".', 'red'); $action = 'view'; $xwSave = $xwEdit = false; }

# Scan POST and apply any XPath inputs foreach ($_REQUEST as $query => $value) if (ereg('^xpath:.+:', $query)) xwSetProperty($xwArticleProperties, $query, $value);

# if it's a pseudo-namespace... if ($xwIsProperties || $xwIsSystem) { # wpSave should be valid XML to be saved if ($xwSave) { xwDomificateArticle($wpSave, 'POST-DATA'); if (!is_object($wpSave)) { xwMessage('A meta-article must be valid XML! changing action to "edit"', 'red'); $action='edit'; } } }

}

  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. OUTPUT HOOK
  1. Post-process and output article

function xwOutputHook() {

global $wgUser, $wgOut, $xwParserHookCalled, $xwSkinHookCalled, $xwDebug; global $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwUserName; global $xwView, $xwPreview, $xwSave, $xwEdit, $xwRaw, $xwIsProperties, $xwIsSystem; if ($xwDebug) xwMessage('OUTPUT-HOOK:','green');

# Activate the skin-hook and generate wiki's output $wgUser->setOption('skin', 'xwskin'); $wgOut->output();

# Saving article: apply transforms in the onChange list if ($xwSave) xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'save');

# 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]; } elseif ($xwIsProperties) { $xwArticle = $m[1]."<?xml version=\"1.0\" standalone=\"yes\"?>\n"; $xwArticle .= "<!DOCTYPE xmlwiki:properties SYSTEM \"xmlwiki-properties.dtd\">\n"; $xwArticle .= "<properties>\n\t<read>anyone</read>\n\t<write>$xwUserName</write>\n"; $xwArticle .= "\t\n\t<view></view>\n\t<edit></edit>\n\t<save></save>\n</properties>\n".$m[2]; } else { # Editing normally, apply transforms in edit-list xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'edit'); } }

# Insert messages into final html and output if ($xwDebug) { if (!$xwParserHookCalled) xwMessage('PARSER-HOOK was not called','green'); if (!$xwSkinHookCalled) xwMessage('SKIN-HOOK was not called','green'); if (is_object($xwArticleProperties)) xwMessage($xwArticleProperties->dump_mem(true),'purple'); } xwAddMessages(); print $xwArticle; }

  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. PARSER HOOK

function xwParserHook(&$text) {

global $xwArticleTitle, $xwArticle, $xwArticleProperties; global $xwParserHookCalled, $xwDebug, $xwReadable, $xwLanguage; if ($xwDebug) xwMessage('PARSER-HOOK:','green'); $xwParserHookCalled = true;

# If this text-fragment is our article, apply transforms if (strncmp($text, $xwArticle, 100) == 0 or strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0) { if ($xwDebug) xwMessage("Matching text-fragment intercepted."); if (!$xwReadable) { xwMessage('Sorry, article not readable!','red'); return $text = false; } # Apply data-transforms # - domificate first if xml # - undomificate after if still an object if (preg_match("/^<\\?xml/i", $text)) xwDomificateArticle($text, 'ParserHook/text'); xwReduceTransformStack($text, $xwArticleProperties, $xwArticleTitle, 'data'); if (is_object($text)) xwUndomificateArticle($text, $xwArticleTitle); } elseif ($xwDebug) xwMessage('Parsing other content');

if ($xwDebug) xwMessage('Content language determined as '.($xwLanguage ? strtoupper($xwLanguage) : 'WIKI')); # Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours return $xwLanguage == ; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. SKIN HOOK
  1. If attempted xml encountered, try to domificate and transform
  2. - Raw requests don't get here

function xwSkinHook(&$tmpl) {

global $xwArticle, $xwArticleTitle, $xwArticleProperties; global $xwTemplate, $xwDebug, $xwSkinHookCalled, $xwReadable, $xwRaw;

# Extract article from existing template structure if ($xwDebug) xwMessage('SKIN-HOOK:','green'); $xwSkinHookCalled = true; $xwTemplate = $tmpl; if ($xwReadable) $xwArticle = $xwTemplate->data['bodytext'];

# Apply view-transforms # - "default-skin.php" builds XML output structure # - "default-skin.xslt" transforms XML to HTML with table-layout # - "default-skin.css" transforms HTML with design xwReduceTransformStack($xwArticle, $xwArticleProperties, $xwArticleTitle, 'view'); }

  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. ARTICLE FUNCTIONS
  1. Retreive wiki-article as raw text

function xwArticleContent($articleTitle) { # Return with results if already cached global $xwArticleCache, $xwDebug; if ($xwDebug) xwMessage("xwArticleContent(\"$articleTitle\")",'blue'); if (isset($xwArticleCache[$articleTitle])) return $xwArticleCache[$articleTitle]; # Get wiki article content (or use global if no article passed) # - using getContentWithoutUsingSoManyDamnGlobals() because getContent() fucks up nested-article-reads if ($article = new Article(Title::newFromText($articleTitle))) $article = $article->getContentWithoutUsingSoManyDamnGlobals(); if ($article == ) { if ($xwDebug) xwMessage("Article \"$articleTitle\" not found."); return false; } elseif ($article == '(There is currently no text in this page)') { if ($xwDebug) xwMessage("Article \"$articleTitle\" not found."); return false; } if ($articleTitle) $xwArticleCache[$articleTitle] = $article; return $article; }

  1. Decide kind of article from content and title

function xwArticleType($title, $article) { if (preg_match("/^<\\?xml.+?\\?>\\s*<xsl:stylesheet/i", $article)) return 'xslt'; if (eregi('^(sys)|(xml):.+$', $title) || ereg('^<\\?xml', $article)) return 'xml'; if (eregi('\\.php$', $title)) return 'php'; if (eregi('\\.css$', $title)) return 'css'; if (eregi('\\.as$', $title)) return 'actionscript'; if (eregi('\\.py$', $title)) return 'python'; if (eregi('\\.java$', $title)) return 'java'; if (eregi('\\.((cpp)|(h))$', $title)) return 'cpp'; if (eregi('\\.js$', $title)) return 'javascript'; if (eregi('\\.css$', $title)) return 'css'; if (eregi('^<\\?html', $article)) return 'html4strict'; if (ereg('^#![/a-zA-Z0-9]+\\/perl', $article)) return 'perl'; if (ereg('^#![/a-zA-Z0-9]+sh', $article)) return 'bash'; if (eregi('\\.dtd$', $title)) return 'xml'; }

  1. Convert passed article to a DOM object
  2. - Article is unchanged if not valid XML

function xwDomificateArticle(&$article, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("xwDomificateArticle(\"$id\")",'blue'); ob_start(); if (php(4)) $dom = domxml_open_mem($article); else $dom = DOMDocument::loadXML($article); if (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; }

  1. Reduce article to a string if it's a DOM object
  2. - ie if all xslt's had xml-output-method

function xwUndomificateArticle(&$article, $id = ) { if (!is_object($article)) return false; global $xwDebug; if ($xwDebug) xwMessage("xwUndomificate(\"$id\")",'blue'); # TODO: # Validate, report errors # xwApplyXSLT($article, $xslt) if xml referrs one # Convert DOM back to XML if still an object if (is_object($article)) $article = $article->dump_mem(true); return true; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. TRANSFORM FUNCTIONS
  1. Apply all transforms in passed stack

function xwReduceTransformStack(&$article, &$properties, $title, $event) { global $xwDebug; if ($xwDebug) xwMessage("Applying \"$event\" transforms."); 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); $tNode->parentNode->removeChild($tNodes->item(0)); xwApplyTransform($tNode->nodeValue, $article, $properties, $title, $event); } } } else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red'); } }

  1. Apply a transform

function xwApplyTransform($transform, &$article, &$properties, $title, $event) { global $xwDebug; if ($transform) { if ($tText = xwArticleContent($transform)) { $tType = xwArticleType($transform, $tText); if ($tType == 'xslt') xwApplyXSLT($article, $tText, $transform); elseif ($tType == 'php') xwApplyPHP($article, $tText, $transform, $title, $properties, $event); elseif ($tType == 'css') xwApplyCSS($transform); elseif ($tType == 'xml') xwMergeDOM($properties, xwDomificateArticle($tText, $transform)); } else xwMessage("Unknown transform \"$transform\" found in \"$event\" list!", 'red'); } }

  1. Apply PHP-transform if article perms are only writable by admin or dev

function xwApplyPHP(&$article, &$code, $tName, $title, &$properties, $event) { global $xwDebug, $_SERVER; if ($xwDebug) xwMessage("xwApplyCSS($tName)",'blue'); # Get transform perms if ($tProperties = xwArticleContent($id = 'xml:'.ucwords($tName))) xwDomificateArticle($tProperties, $id); if (!count(preg_grep('/^(admin)|(dev)$/i', xwGetListByTagname($tProperties, 'write')))) { xwMessage('PHP-Transform will not execute unless it is writable by admin or dev only.','red'); return; } # Permissions ok, execute the transform code and trap output $path = '/properties/'.preg_replace("/\\.\\w+$/",,$tName); $script = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; ob_start(); eval("?>$code<?"); xwExtractMessages(); ob_end_clean(); }

  1. Append CSS stylesheet link to output

function xwApplyCSS($tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyCSS($tName)",'blue'); $xwStyleSheets[] = $tName; }

  1. Apply XSLT to article-dom
  2. - if article is not and object, the XSLT will be appended to the stylesheet-ref list like a CSS
  3. - if output-method is html, article will get stringified and xwLanguage updated

function xwApplyXSLT(&$article, &$xslt, $tName) { global $xwDebug, $xwStyleSheets; if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$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); global $xwLanguage; $xwLanguage = ; } } xwExtractMessages(); ob_end_clean(); if (isset($tResult)) $article = $tResult; } else $xwStyleSheets[] = $tName; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. DOM FUNCTIONS
  1. 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 { $xpath = new DOMXPath($dom); $result = $xpath->query($query); } xwExtractMessages(); ob_end_clean(); } if (!is_array($result)) return array(); return $result; }

function xwGetProperty(&$properties, $property, &$value) { if (count($results = xwXPathQuery($properties, "/properties/$property")) == 0) return false; $value = $results[0]->get_content(); return true; }

  1. - Syntax: xpath:XPathQuery:[[@]node][+] = value
  2. - if "node" exists, then a new element (or att if "@node") is created for the value
  3. - if "+" exists, content is appended, else replaced

function xwSetProperty(&$properties, $query, $value) { if (!is_object($properties)) return false; if (!preg_match("/^xpath:(.+?):(@?)(\\w*?)(\\+?)$/", $query, $match)) $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) $node = $properties->create_attribute($node, $value); else { $node = $properties->create_element($node); $node->set_content($value); } $result->append_child($node); } else { # node not set, replace or append current-result-value if ($append) $result->set_content($value); else { $newnode = $properties->create_element($result->tagname); $newnode->set_content($value); $result->replace_node($newnode); } } } return true; }

  1. Return array of comma-separated-items in element content
  2. - 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; }

  1. Merge the two passed DOM objects
  2. - appends, does not overwrite

function xwMergeDOM($dom1, $dom2) { global $xwDebug; if ($xwDebug) xwMessage('xwMergeDOM()','blue'); # Return if first DOM not a valid properties article if (!is_object($dom1)) return false; 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()','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 $dom1; }


  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. UTILITY FUNCTIONS
  1. Append messages to content

function xwAddMessages() { global $xwArticle, $xwMessages, $xwMsgToken; if (count($xwMessages)) {

$msg = '

There are Messages

'; $msg .= '

  • '.join("
  • ", $xwMessages).'

';

$xwArticle = ereg_replace($xwMsgToken, $msg, $xwArticle); } }

  1. Extract error-messages from captured output and put in proper message-queue

function xwExtractMessages() { #if (preg_matcheval()'d code on line 2 $err = preg_replace("/<.+?>/", "", ob_get_contents()); #$err = preg_replace('/Warning.+?\\(\\): /', , $err); foreach (split("\n", $err) as $msg) if (trim($msg)) { if (ereg("eval\\(\\)'d code on line ([0-9]+)", $msg, $m)) $line = " (Line $m[1])"; $msg = preg_replace('/ in .+? on line.+?[0-9]+/', , $msg); if (isset($line)) $msg .= $line; xwMessage($msg, 'red'); } }

  1. Add message to queue

function xwMessage($msg, $col = '#000080') { global $xwMessages; $msg = htmlentities($msg); return $xwMessages[] = "$msg"; }

  1. 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; }

  1. Return true if PHP major version equals passed int
  2. - needed due to different DOM implimentation on 4 and 5 (and none on 3)

function php($ver) { return substr(phpversion(),0,1) == "$ver"; }

?>