Xmlwiki.php

From Organic Design wiki
Revision as of 04:58, 22 June 2005 by Nad (talk | contribs)

<?php

  1. xmlWiki - MediaWiki XML Hack
  2. Nad - 2005-05-18

$xwDebug=1;

  1. INIT
  2. SECURITY
  3. INPUT HOOK
  4. OUTPUT HOOK
  5. PARSER HOOK
  6. SKIN HOOK
  7. ARTICLE FUNCTIONS
  8. UTILITY FUNCTIONS
  1. ---------------------------------------------------------------------------------------------------------------------- #
  1. TODO:
  2. - TransformArticle:
  3. - validate article-properties against xmlwiki:properties.dtd (does PHP4 do this?)
  4. - could make transform-list into a stack (so transforms can 'push' other transforms onto the list)
  5. - trap fatal errors in php-transform execution
  6. - remove global and file access from php-transforms (?)
  7. - Undomificate: validation, xslt
  8. - Check (later validate) doctype of user:sys (change users pages on site first!)
  9. - Check (later validate) doctype of user/properties on save (still save though)
  10. Publish:
  11. - article-properties - holds article publish-list
  12. - article-source (dynamic, periodic, on-notify)
  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!');

  1. Allows Geshi to be installed by PARSER-HOOK if not installed th usual way already

if (!@is_object($wgGeshiSyntaxSettings)) $wgGeshiSyntaxSettings = false;

  1. System globals

$xwMessages = array(); $xwArticleCache = 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');

  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. SECURITY
  1. Get user-system-article

if ($xwUserSYS = xwArticleContent("sys:$xwUserName")) xwDomificateArticle($xwUserSYS, "sys:$xwUserName");

  1. If sys:user exists, get users' group-list

if (is_object($xwUserSYS)) { $xwRoot = $xwUserSYS->document_element(); if (count($xwList = $xwRoot->get_elements_by_tagname('groups'))) $xwUserGroups = array_merge($xwUserGroups, split(',', strtolower($xwList[0]->get_content()))); }

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

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

  1. Set perms for this request

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): ".join(', ', $xwArticleReadableBy)); xwMessage("Writable ($xwWritable): ".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('xwInputHook()','green');

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

# Parse POST for XPath inputs # - 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 # - hidden-inputs can direct input-handlers into the transform list foreach ($_REQUEST as $k => $v) { if (is_object($xwArticleProperties) && preg_match("/^xpath:(.+):(@?)(\\w*)(\\+?)$/", $k, $m)) { list(, $xpath, $att, $node, $append) = $m; foreach (xwXPathQuery($xwArticleProperties, $xpath) as $result) { if ($node) { # create new element/attribute in result-node if ($att) $node = $xwArticleProperties->create_attribute($node, $v); else { $node = $xwArticleProperties->create_element($node); $node->set_content($v); } $result->append_child($node); } else { # node not set, replace or append current-result-value if ($append) $result->set_content($v); else { $newnode = $xwArticleProperties->create_element($result->tagname); $newnode->set_content($v); $result->replace_node($newnode); } } } } }

# 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('xwOutputHook()','green');

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

# If saving, check the publishing list if (0 && $xwSave) { xwMessage('Publishing...'); # Publish each item foreach ($xwArticlePublish as $url) { if ($FH = fopen($url, 'a')) { # ftp example: ftp://user:password@example.com/file.txt fwrite($FH, $xwArticle); fclose($FH); xwMessage("Article published to $url"); } else xwMessage("Failed to publish to $url", 'red'); } }

# If editing an empty sys:article or xml:article, add default content if ($xwEdit && preg_match("/^(.*<textarea .+?>)\\s*(<\\/textarea>.*)$/s", $xwArticle, $m)) { 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<transform></transform>\n\t<publish></publish>\n</properties>\n".$m[2]; } }

# 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 $xwRaw, $xwParserHookCalled, $xwDebug, $xwArticleTitle, $xwArticle, $xwReadable; if ($xwDebug) xwMessage('xwParserHook(TEXT)','green'); $xwParserHookCalled = true; $transformed = false;

# If this text-fragment is our article, apply transforms if ((strncmp($text, $xwArticle, 100) == 0) || (strncmp($text, xwArticleContent($xwArticleTitle), 100) == 0)) { if ($xwDebug) xwMessage("PARSER-HOOK: \"$xwArticleTitle\" text-fragment recognised and intercepted."); if (!$xwReadable) { $text=; xwMessage('Sorry, article not readable!','red'); } $xwArticle = $text; # needed because xwTransform works on global article # If attempted xml encountered, try to domificate and transform if (preg_match("/^<\\?xml/i", $xwArticle)) { $transformed = true; xwDomificateArticle($xwArticle, 'ParserHook/text'); } xwTransformArticle(); # If not a 'raw' request, try some Geshi on if (!$xwRaw) { global $wgGeshiSyntaxSettings; if (!is_object($wgGeshiSyntaxSettings)) require_once('extensions/geshi/geshi.php'); if ($lang = xwArticleType($xwArticleTitle, $xwArticle)) { if ($xwDebug) xwMessage("Geshi: This is \"$lang\"."); # Replace tabs with spaces (better than geshi) $xwArticle = preg_replace_callback("/^(.*?)(\\t+)/m", 'xwReplaceTabs', $xwArticle); # Set up a geshi-parser $geshi = new GeSHi($xwArticle, $lang == 'xslt' ? 'xml' : $lang, 'extensions/geshi/geshi'); $geshi->set_header_type(GESHI_HEADER_DIV); #$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS); #@$geshi->set_line_style('color:#aaaaaa'); #@$geshi->set_code_style('color:black'); @$geshi->set_keyword_group_style(1, 'color:blue', true); @$geshi->set_keyword_group_style(2, 'color:blue', true); @$geshi->set_keyword_group_style(3, 'color:blue', true); @$geshi->set_comments_style(1,'font-style: italic; color:#d00000;',true); @$geshi->set_comments_style(2,'font-style: italic; color:#d00000;',true); @$geshi->set_comments_style(3,'font-style: italic; color:#d00000;',true); @$geshi->set_escape_characters_style('color:red',true); @$geshi->set_brackets_style('color:black',true); @$geshi->set_strings_style('color:#008080',true); @$geshi->set_numbers_style('color:black',true); @$geshi->set_methods_style('color:black',true); @$geshi->set_symbols_style('color:black',true); @$geshi->set_regexps_style('color:green',true); # Do the parse

$xwArticle = preg_replace("/^<div.*?>/", '

', $geshi->parse_code());

$transformed = true; } elseif ($xwDebug) xwMessage('Geshi: Not my problem.'); } $text = $xwArticle; } elseif ($xwDebug) xwMessage('Parsing other content');

# Return this back to wiki-parser so it can do wiki-markup if it wasn't one of ours return $transformed; }

  1. Converts tabs to spaces better than geshi

function xwReplaceTabs($m) { return $m[1].str_repeat(' ',strlen($m[2])*4-strlen($m[1])%4); }

  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, $xwTemplate, $xwDebug, $xwSkinHookCalled; if ($xwDebug) xwMessage('xwSkinHook(TEMPLATE)','green'); $xwSkinHookCalled = true; $xwTemplate = $tmpl; $xwArticle = $xwTemplate->data['bodytext']; #$xslt = xwArticleContent('xmlwiki.xslt');

# Do xmlWiki page-layout transform

xwTransformPageLayout(); # should be: xwApplyXSLT($xwArticle, $xslt, 'xmlwiki.xslt');

}

  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 == '(There is currently no text in this page)') { if ($xwDebug) xwMessage("\"$articleTitle\" not found!",'red'); 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(XML,$id)",'blue'); ob_start(); if ($dom = domxml_open_mem($article)) $article = $dom; else { # Could not convert, extract error messages from output if ($xwDebug) xwMessage("Failed :-(", 'red'); xwExtractMessages(); } ob_end_clean(); }

  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(DOM,$id)",'blue'); # TODO: # Validate, report errors # xwApplyXSLT($article, $xslt) if xml referrs one # Convert DOM back to XML if still an object if (is_object($article)) $article = $article->dump_mem(true); return true; }

  1. Transform an article
  2. - Article is undomificated on exit

function xwTransformArticle() { global $xwDebug, $xwArticle, $xwArticleProperties, $xwArticleTitle, $xwIsProperties, $xwIsSystem; # Article can not be transformed if properties/system if (!$xwIsProperties && !$xwIsSystem) { # Article-properties must be an object... if (is_object($xwArticleProperties)) { $docType = $xwArticleProperties->doctype(); $root = $xwArticleProperties->document_element(); # ...and must be xmlwiki:properties doctype if (ereg('^xmlwiki:properties', $docType->name)) { if ($xwDebug) xwMessage("xwTransformArticle()",'blue'); # Loop thru transforms applying each foreach ($root->get_elements_by_tagname('transform') as $elem) if ($tName = $elem->get_content()) { if ($tText = xwArticleContent($tName)) { $tType = xwArticleType($tName, $tText); if ($tType == 'xslt') xwApplyXSLT($xwArticle, $tText, $tName); elseif ($tType == 'php') xwApplyPHP($xwArticle, $tText, $tName, $xwArticleTitle, $xwArticleProperties); elseif ($tType == 'css') xwMessage("CSS's from transform-list aren't applied yet :-(", 'red'); } else xwMessage("No such transform-article \"$tName\"!", 'red'); } } else xwMessage('Could not transform: article-properties must be xmlwiki:properties doctype!','red'); } else if ($xwDebug) xwMessage('Article properties not found.','red'); } # If article is still a DOM object, stringify it once and for all if (is_object($xwArticle)) xwUndomificateArticle($xwArticle, $xwArticleTitle); return true; }

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

function xwApplyPHP(&$article, &$code, $tName, $title, &$properties) { # Get transform perms if ($tProperties = xwArticleContent('xml:'.ucwords($tName))) xwDomificateArticle($tProperties, 'Transform-Properties'); 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 ob_start(); @eval("?>$code<?"); xwExtractMessages(); ob_end_clean(); }

  1. Apply XSLT to article-dom
  2. - if output-method is html, article will get stringified

function xwApplyXSLT(&$article, &$xslt, $id = ) { global $xwDebug; if ($xwDebug) xwMessage("xwApplyXSLT(DOM,$id)",'blue'); ob_start(); if ($tObject = domxml_xslt_stylesheet($xslt)) { $tResult = $tObject->process($article); # If output-method is html, use XSLT's dump_mem to create an html-string if (preg_match('/<xsl:output +?method *= *"html"/i', $xslt)) $tResult = $tObject->result_dump_mem($tResult); } xwExtractMessages(); ob_end_clean(); if (isset($tResult)) $article = $tResult; else return false; return true; }

  1. Return result set of an XPath query on passed DOM-object

function xwXPathQuery(&$dom, $query) { global $xwDebug; if ($xwDebug) xwMessage("xwXPathQuery(DOM,\"$query\")",'blue'); ob_start(); $context = $dom->xpath_new_context(); if ($xpath = $context->xpath_eval($query)) $result = $xpath->nodeset; else { xwMessage('Query Failed! :-(', 'red'); $result = array(); } xwExtractMessages(); ob_end_clean(); return $result; }

  1. Surrounds the article with HTML structural page-layout containing a ref to xmlwiki.css
  2. - TEMP: see skin-hook

function xwTransformPageLayout() {

global $xwMsgToken, $xwArticle, $xwArticleTitle, $xwTemplate, $xwDebug; global $xwWritable, $xwUserName, $xwIsAdmin, $xwIsUser, $xwIsProperties, $xwIsSystem; if ($xwDebug) xwMessage("xwTransformPageLayout()",'blue'); $tmpl = &$xwTemplate; $data = &$tmpl->data; $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '; $doctype .= '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'; $html = ''; # Add properties-action if not already a meta-article, or change article-href if it is if ($xwIsProperties) $data['content_actions']['nstab-main']['href'] = '/wiki/index.php?title='.preg_replace('/^.+?:/i', '', $xwArticleTitle); elseif ($xwIsSystem) $data['content_actions']['nstab-main']['href'] = '/wiki/index.php?title=user:'.preg_replace('/^.+?:/i', '', $xwArticleTitle); else $data['content_actions']['xml'] = array('href'=>"/wiki/index.php?title=xml:$xwArticleTitle&action=edit", 'text'=>'Properties'); # Add system-action if admin and a user-page if ($xwIsAdmin && $xwIsUser) $data['content_actions']['sys'] = array('href'=>'/wiki/index.php?title=sys:'.preg_replace('/^user:/i','',$xwArticleTitle).'&action=edit', 'text'=>'System'); # Head $head = ''.$data['headlinks']; $head .= ''; $head .= ''.htmlspecialchars($data['pagetitle']).''; $head .= ''; # Body opening tag $body = ''; $content .= '

'.htmlspecialchars($data['title']).'

'; $content .= '
'; $content .= '

'.htmlspecialchars($tmpl->translator->translate('tagline')).'

'; $content .= '
'.$data['subtitle'].'
'; if ($data['undelete']) $content .= '
'.$data['undelete'].'
'; if ($data['newtalk']) $content .= '
'.$data['newtalk'].'
'; $content .= "$xwArticle"; # <---- Actual article content inserted here if ($data['catlinks']) $content .= ''; $content .= '
'; $content .= '

'; # Action-links (remove edit link if not writable) $actions = '

Views
    '; foreach ($data['content_actions'] as $key => $action) { #if ($key == ' #if (!(ereg('action=edit$', $action['href'])&&!$xwWritable)) { $actions .= '
  • '.htmlspecialchars($action['text']).''; $actions .= ">$href
  • "; # } } $actions .= '

'; # Personal-links $personal = '

'; $personal .= '
'.htmlspecialchars($tmpl->translator->translate('personaltools')).'
'; $personal .= '

"; # Logo $logo = '

MastodonRSSCodeEmail

'; $logo .= '

'; # Navigation $nav = '

'; $nav .= '
'.htmlspecialchars($tmpl->translator->translate('navigation')).'
'; $nav .= '

'; # Search $search = '

'; # Toolbox $toolbox = '

'; $toolbox .= '
'.htmlspecialchars($tmpl->translator->translate('toolbox')).'
'; $toolbox .= '

'; # Language $language = ''; if ($data['language_urls']) { $language .= '

'; $language .= '
'.htmlspecialchars($tmpl->translator->translate('otherlanguages')).'
'; $language .= '
    '; foreach ($data['language_urls'] as $langlink) { $href = ''.$langlink['text'].''; $language .= "
  • $href
  • "; } $language .= '

'; } # Footer $footer = '

'; # Return assembled parts $xwArticle = " $doctype $html $head $body

$search $nav $toolbox $personal$actions$xwMsgToken$content
$footer

"; }

  1. ---------------------------------------------------------------------------------------------------------------------- #
  2. UTILITY FUNCTIONS
  1. Return array of comma-separated-items in element content

function xwGetListByTagname(&$xml, $tag) { if (is_object($xml)) { $root = $xml->document_element(); if (count($list = $root->get_elements_by_tagname($tag))) return split(',', $list[0]->get_content()); } return array(); }

  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() { $err = preg_replace("/<.+?>/", "", ob_get_contents()); $err = preg_replace('/ in .+? on line.+?[0-9]+/', , $err); $err = preg_replace('/Warning.+?\\(\\): /', , $err); foreach (split("\n", $err) as $msg) if (trim($msg)) 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; }

?>