Difference between revisions of "Extension:Workflow.php"
(expansion of parser-function started) |
(0.0.4 - Add special page and start extended catlinks info) |
||
Line 7: | Line 7: | ||
if (!defined('MEDIAWIKI')) die('Not an entry point.'); | if (!defined('MEDIAWIKI')) die('Not an entry point.'); | ||
− | define('WORKFLOW_VERSION','0.0. | + | define('WORKFLOW_VERSION','0.0.3, 2007-10-24'); |
$wgWorkflowMagic = "workflow"; | $wgWorkflowMagic = "workflow"; | ||
− | $wgExtensionCredits['parserhook'][] = array( | + | $wgExtensionCredits['parserhook'][] = $wgExtensionCredits['specialpage'][] = array( |
'name' => 'Workflow', | 'name' => 'Workflow', | ||
'author' => '[http://www.organicdesign.co.nz/nad User:Nad]', | 'author' => '[http://www.organicdesign.co.nz/nad User:Nad]', | ||
Line 19: | Line 19: | ||
); | ); | ||
+ | # Define a new specialpage for displaying a list of workflows | ||
+ | require_once "$IP/includes/SpecialPage.php"; | ||
+ | class SpecialWorkflow extends SpecialPage { | ||
+ | |||
+ | function SpecialWorkflow() { | ||
+ | SpecialPage::SpecialPage(wfMsg('workflow'),'',true,false,false,false); | ||
+ | } | ||
+ | |||
+ | # Render list | ||
+ | function execute($param) { | ||
+ | global $wgParser,$wgOut; | ||
+ | $wgParser->disableCache(); | ||
+ | $this->setHeaders(); | ||
+ | $wgOut->addWikiText('Not done yet...',true); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | # Define main workflow class containing all the functionality | ||
class Workflow { | class Workflow { | ||
Line 26: | Line 44: | ||
var $pagename; | var $pagename; | ||
var $update; | var $update; | ||
+ | var $wf; | ||
# Constructor | # Constructor | ||
function Workflow() { | function Workflow() { | ||
− | global $wgHooks,$wgExtensionFunctions,$wgWorkflowMagic; | + | global $wgHooks,$wgExtensionFunctions,$wgWorkflowMagic,$wgExtraNamespaces; |
$wgExtensionFunctions[] = array($this,'init'); | $wgExtensionFunctions[] = array($this,'init'); | ||
$wgHooks['LanguageGetMagic'][] = array($this,'languageGetMagic'); | $wgHooks['LanguageGetMagic'][] = array($this,'languageGetMagic'); | ||
Line 35: | Line 54: | ||
$this->magic = $wgWorkflowMagic; | $this->magic = $wgWorkflowMagic; | ||
$this->pagename = $_REQUEST['title']; | $this->pagename = $_REQUEST['title']; | ||
+ | $this->wf = $wgExtraNamespaces[NS_WORKFLOW]; | ||
return true; | return true; | ||
} | } | ||
Line 40: | Line 60: | ||
# Called from $wgExtensionFunctions array when initialising extensions | # Called from $wgExtensionFunctions array when initialising extensions | ||
function init() { | function init() { | ||
+ | global $wgParser,$wgLanguageCode,$wgMessageCache,$wgSiteNotice; | ||
# Require NS_WORKFLOW to be defined before installing parser-function or javascript etc | # Require NS_WORKFLOW to be defined before installing parser-function or javascript etc | ||
if (!defined('NS_WORKFLOW')) { | if (!defined('NS_WORKFLOW')) { | ||
− | + | $wgSiteNotice .= '<div class="usermessage">'.wfMsg('workflowMissingNamespace').'</div>'; | |
− | $wgSiteNotice .= '<div class="usermessage"> | ||
return false; | return false; | ||
} | } | ||
− | + | ||
− | + | # Add the messages used (todo: move into i18n) | |
+ | if ($wgLanguageCode == 'en') { | ||
+ | $wgMessageCache->addMessages(array( | ||
+ | 'workflow' => $this->wf, | ||
+ | 'workflowMissingNamespace' => 'The NS_WORKFLOW namespace must be defined. The workflow extension is disabled.' | ||
+ | )); | ||
+ | } | ||
+ | |||
+ | # Add the specialpage to the environment | ||
+ | SpecialPage::addPage(new SpecialWorkflow()); | ||
+ | |||
+ | # Add the parser-function hook | ||
$wgParser->setFunctionHook($this->magic,array($this,'expandMagic')); | $wgParser->setFunctionHook($this->magic,array($this,'expandMagic')); | ||
+ | |||
+ | # Update a workflow state if flag set by Ajax processing | ||
if ($this->update) $this->updateState(); | if ($this->update) $this->updateState(); | ||
+ | |||
+ | # Add the client-side scripts for changing states | ||
$this->addJS(); | $this->addJS(); | ||
} | } | ||
Line 57: | Line 92: | ||
# - note the hidden states mustn't be rendered because they contain categorisation links which shouldn't be processed | # - note the hidden states mustn't be rendered because they contain categorisation links which shouldn't be processed | ||
function expandMagic(&$parser) { | function expandMagic(&$parser) { | ||
− | global $wgUser; | + | global $wgUser,$namespaceNames; |
+ | $tmpl = $namespaceNames[NS_TEMPLATE]; | ||
+ | $cat = $namespaceNames[NS_CATEGORY]; | ||
# Extend catlinks information to include workflows | # Extend catlinks information to include workflows | ||
Line 76: | Line 113: | ||
# Get the list of workflow states from the Workflow article | # Get the list of workflow states from the Workflow article | ||
$states = array(); | $states = array(); | ||
− | $text = "[[ | + | $text = "[[{$this->wf}:$name]]"; |
$title = Title::newFromtext($name,NS_WORKFLOW); | $title = Title::newFromtext($name,NS_WORKFLOW); | ||
if (is_object($title) && $title->exists()) { | if (is_object($title) && $title->exists()) { | ||
Line 82: | Line 119: | ||
$links = $psr->parse($article->getContent(),$title,$opt,true,true)->getLinks(); | $links = $psr->parse($article->getContent(),$title,$opt,true,true)->getLinks(); | ||
if (is_array($links[NS_MAIN])) $states = $links[NS_MAIN]; | if (is_array($links[NS_MAIN])) $states = $links[NS_MAIN]; | ||
− | else $text = "[[ | + | else $text = "[[{$this->wf}:$name|{$this->wf}:$name (".wfMsg('workflowMissingNamespace').")]]"; |
} | } | ||
Line 89: | Line 126: | ||
$text = ''; | $text = ''; | ||
foreach ($states as $dbk => $id) { | foreach ($states as $dbk => $id) { | ||
− | $ | + | $title = Title::newFromText($dbk,NS_TEMPLATE); |
− | $text .= $ | + | $text .= $title->exists() ? "<br>($name)" : "<br>[[$template:$dbk]]"; |
− | #$text .= "[[ | + | #$text .= "[[$category:$name]]"; # if state is current |
} | } | ||
} | } | ||
Line 131: | Line 168: | ||
eval("class WorkflowSkin extends {$class} ".'{ | eval("class WorkflowSkin extends {$class} ".'{ | ||
function getCategoryLinks() { | function getCategoryLinks() { | ||
− | $ | + | global $wgWorkflow; |
− | + | return $wgWorkflow->renderWorkflowInfo(parent::getCategoryLinks()); | |
− | |||
} | } | ||
}'); | }'); | ||
Line 140: | Line 176: | ||
$wgUser->mSkin = new WorkflowSkin(); | $wgUser->mSkin = new WorkflowSkin(); | ||
foreach (array_keys(get_class_vars($class)) as $k) $wgUser->mSkin->$k = $skin->$k; | foreach (array_keys(get_class_vars($class)) as $k) $wgUser->mSkin->$k = $skin->$k; | ||
+ | } | ||
+ | |||
+ | # Render the workflow info which appears in the catlinks area | ||
+ | function renderWorkflowInfo(&$catlinks) { | ||
+ | $title = Title::makeTitle(NS_SPECIAL,wfMsg('workflow')); | ||
+ | $catlinks .= "<br /><a href='{$title->getLocalURL()}'>".wfMsg('workflow')."</a>:\n"; | ||
+ | return $catlinks; | ||
} | } | ||
Revision as of 07:02, 24 October 2007
<?php
- Extension:Workflow
In computer programming, "Voodoo", or "Magic" refers to techniques that are secret or not widely known, and may be deliberately kept secret. The Jargon File makes a distinction between "deep magic", which refers to code based on esoteric theoretical knowledge; "black magic" (voodoo), which refers to code based on techniques that appear to work but which lack a theoretical explanation; and "heavy wizardry", which refers to code based on obscure or undocumented intricacies of particular hardware or software.
At Organic Design the most common of these is extending an instance's class at runtime after it has been instantiated, a technique that can be used to provide additional hooks into existing code without requiring modification of code-base files. Another is reading in a class file, declaring it under a different name, then sub-classing that with a new class of the original name - that way the environment uses the new extended class thinking it's the original one.
See also
- Adding a variable to a class instance at runtime - demonstrated in many different languagesTemplate:Php
- - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
- - Author: User:NadCategory:Extensions created with Template:Extension
- - Started: 2007-10-06
if (!defined('MEDIAWIKI')) die('Not an entry point.');
define('WORKFLOW_VERSION','0.0.3, 2007-10-24');
$wgWorkflowMagic = "workflow";
$wgExtensionCredits['parserhook'][] = $wgExtensionCredits['specialpage'][] = array( 'name' => 'Workflow', 'author' => 'User:Nad', 'description' => 'Adds the ability for articles to be part of workflow sequences and easily moved dynamically between phases in the sequence using AJAX.', 'url' => 'http://www.mediawiki.org/wiki/Extension:Workflow', 'version' => WORKFLOW_VERSION );
- Define a new specialpage for displaying a list of workflows
require_once "$IP/includes/SpecialPage.php"; class SpecialWorkflow extends SpecialPage {
function SpecialWorkflow() { SpecialPage::SpecialPage(wfMsg('workflow'),,true,false,false,false); }
# Render list function execute($param) { global $wgParser,$wgOut; $wgParser->disableCache(); $this->setHeaders(); $wgOut->addWikiText('Not done yet...',true); } }
- Define main workflow class containing all the functionality
class Workflow {
var $magic; var $state; var $name; var $pagename; var $update; var $wf;
# Constructor function Workflow() { global $wgHooks,$wgExtensionFunctions,$wgWorkflowMagic,$wgExtraNamespaces; $wgExtensionFunctions[] = array($this,'init'); $wgHooks['LanguageGetMagic'][] = array($this,'languageGetMagic'); $this->bypassAjaxDispatcher(); $this->magic = $wgWorkflowMagic; $this->pagename = $_REQUEST['title']; $this->wf = $wgExtraNamespaces[NS_WORKFLOW]; return true; }
# Called from $wgExtensionFunctions array when initialising extensions function init() { global $wgParser,$wgLanguageCode,$wgMessageCache,$wgSiteNotice;
# Require NS_WORKFLOW to be defined before installing parser-function or javascript etc if (!defined('NS_WORKFLOW')) {
$wgSiteNotice .= '
';
return false; }
# Add the messages used (todo: move into i18n) if ($wgLanguageCode == 'en') { $wgMessageCache->addMessages(array( 'workflow' => $this->wf, 'workflowMissingNamespace' => 'The NS_WORKFLOW namespace must be defined. The workflow extension is disabled.' )); }
# Add the specialpage to the environment SpecialPage::addPage(new SpecialWorkflow());
# Add the parser-function hook $wgParser->setFunctionHook($this->magic,array($this,'expandMagic'));
# Update a workflow state if flag set by Ajax processing if ($this->update) $this->updateState();
# Add the client-side scripts for changing states $this->addJS(); }
# Expand the #tags to reveal the current state and hide the others and add javascript # - note the hidden states mustn't be rendered because they contain categorisation links which shouldn't be processed function expandMagic(&$parser) { global $wgUser,$namespaceNames; $tmpl = $namespaceNames[NS_TEMPLATE]; $cat = $namespaceNames[NS_CATEGORY];
# Extend catlinks information to include workflows $this->extendCatlinks();
# Populate $argv with both named and numeric parameters $argv = array(); $items = array(); $name = 'Untitled'; foreach (func_get_args() as $arg) if (!is_object($arg)) { if (preg_match('/^(.+?)\\s*=\\s*(.+)$/',$arg,$match)) $argv[$match[1]] = $match[2]; else $name = $arg; }
# Make a clone of the parser for parsing without affecting LinkHolder or catlinks arrays $psr = new parser; $opt = ParserOptions::newFromUser($wgUser);
# Get the list of workflow states from the Workflow article $states = array(); $text = "[[{$this->wf}:$name]]"; $title = Title::newFromtext($name,NS_WORKFLOW); if (is_object($title) && $title->exists()) { $article = new Article($title); $links = $psr->parse($article->getContent(),$title,$opt,true,true)->getLinks(); if (is_array($links[NS_MAIN])) $states = $links[NS_MAIN]; else $text = "[[{$this->wf}:$name|{$this->wf}:$name (".wfMsg('workflowMissingNamespace').")]]"; }
# Transclude each (use a parser clone for the ones which aren't current to avoid categorisation)
if (count($states)) {
$text = ;
foreach ($states as $dbk => $id) {
$title = Title::newFromText($dbk,NS_TEMPLATE);
$text .= $title->exists() ? "
($name)" : "
$template:$dbk";
#$text .= "$category:$name"; # if state is current
}
}
# Surround the content in a clickable container which updates the displayed item and persistent state # - container must call workflowSwitchState when clicked # - container must update workflowStates[] and workflowLengths[]
return $text; }
# Update the current state of a workflow item in the requested article function updateState() { $title = Title::newFromtext($this->pagename); if (is_object($title) && $title->exists()) { $article = new Article($title); $text = $article->getContent(); preg_replace_callback("/\\{\\{#{$this->magic}:.*?id\\s*=\\s*['\"]?$id\\W.*?\\}\\}/s",array($this,'updateStateCallback'),$text,1,$count); if ($count) $article->updateArticle($text,"summary",false,false); } }
# Replacement callback for updating tag state function updateStateCallback($m) { $m[0] = preg_replace("/(state\\s*=\\s*['\"]?)\\w+/","$1{$this->state}",$m[0],1,$count); if ($count < 1) $m[0] = preg_replace("/(\\{\\{#{$this->magic}:/","$0state=${$this->state}|",$m[0]); return $m[0]; }
# Extend catlinks information to include workflows function extendCatlinks() { static $done = 0; if ($done++) return; global $wgUser; $skin = $wgUser->getSkin();
# Create a new Skin class (WorkflowSkin) by extending the existing one with overridden getCategoryLinks method $class = get_class($skin); eval("class WorkflowSkin extends {$class} ".'{ function getCategoryLinks() { global $wgWorkflow; return $wgWorkflow->renderWorkflowInfo(parent::getCategoryLinks()); } }');
# Replace user's skin with a WorkflowSkin replica $wgUser->mSkin = new WorkflowSkin(); foreach (array_keys(get_class_vars($class)) as $k) $wgUser->mSkin->$k = $skin->$k; }
# Render the workflow info which appears in the catlinks area
function renderWorkflowInfo(&$catlinks) {
$title = Title::makeTitle(NS_SPECIAL,wfMsg('workflow'));
$catlinks .= "
<a href='{$title->getLocalURL()}'>".wfMsg('workflow')."</a>:\n";
return $catlinks;
}
# Return just the catlinks to the client after updating a tag state function returnCatlinks() { global $wgUser,$wgOut; $skin = $wgUser->getSkin(); $catlinks = is_object($skin) ? $skin->getCategoryLinks() : 'Error: no skin!'; $wgOut->disable(); while(@ob_end_clean()); if (in_array('Content-Encoding: gzip',headers_list())) $catlinks = gzencode($catlinks); echo($catlinks); return false; }
# Make necessary Javascript functions available to the page function addJS() { global $wgOut,$wgJsMimeType; $wgOut->addScript("<script type='$wgJsMimeType'> var workflowStates = []; var workflowLengths = []; function workflowUpdateState(name) { // make an ajax request, return into id=catlinks // disable timer var state = workflowStates[name]; sajax_do_call('{$this->magic}',[{$this->pagename},name,state],document.getElementById('catlinks')); } function workflowSwitchState(name) { // one of the states has display= the others 'none' // enable and reset timer (if state different than last, otherwise ensure diabled) // timer must call workflowUpdateState when expired var state = workflowStates[name] = workflowStates[name] < workflowLengths[name] ? workflowStates[name]+1 : 0; } </script>"); }
# If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render) function bypassAjaxDispatcher() { global $wgUseAjax,$wgHooks; if ($wgUseAjax && $_REQUEST['action'] == 'ajax' && $_REQUEST['rs'] == $this->magic && is_array($_REQUEST['rsargs'])) { list($_REQUEST['title'],$this->name,$this->state) = $_REQUEST['rsargs']; $wgHooks['OutputPageBeforeHTML'][] = array($this,'returnCatlinks'); $this->update = true; $_REQUEST['action'] = 'render'; } else $this->update = false; }
# Needed in some versions to prevent Special:Version from breaking function __toString() { return 'Workflow'; }
# Set up the magic words function languageGetMagic(&$magicWords,$langCode = 0) { $magicWords[$this->magic] = array(0,$this->magic); return true; }
}
$wgWorkflow = new Workflow(); ?>