Difference between revisions of "Extension:Workflow.php"

From Organic Design wiki
(Comments added for proposed changes to workflow categorization)
(new dev)
Line 1: Line 1:
[[Category:Extensions created with Template:Extension]][[Category:Extensions|Workflow]]{{voodoo}}[[Category:Extensions in progress|Workflow]]
 
<php>
 
 
<?php
 
<?php
# Extension:Workflow
+
/**
# - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
+
* Extension:Workflow
# - Author: [http://www.organicdesign.co.nz/nad User:Nad]
+
*{{Category:Extensions|Workflow}}{{php}}{{Category:Extensions created with Template:Extension}}
# - Started: 2007-10-06
+
* @package MediaWiki
 +
* @subpackage Extensions
 +
* @author Aran Dunkley [http://www.organicdesign.co.nz/nad User:Nad]
 +
* @licence GNU General Public Licence 2.0 or later
 +
* Started: 2007-10-06
 +
*/
 +
if (!defined('MEDIAWIKI')) die("Not an entry point.");
  
if (!defined('MEDIAWIKI')) die('Not an entry point.');
+
define('WORKFLOW_VERSION', "0.1.0, 2008-11-07");
 
 
define('WORKFLOW_VERSION','0.0.13, 2008-08-28');
 
  
 
$wgWorkflowUpdateDelay = 1000; # Delay in milliseconds after clicking state before Ajax update is made
 
$wgWorkflowUpdateDelay = 1000; # Delay in milliseconds after clicking state before Ajax update is made
  
 
$wgExtensionCredits['parserhook'][] = $wgExtensionCredits['specialpage'][] = 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]",
'description' => 'Adds the ability for articles to be part of workflow sequences and easily moved
+
'description' => "Adds the ability for articles to be part of workflow sequences and easily moved
dynamically between phases in the sequence using AJAX.',
+
dynamically between phases in the sequence using AJAX.",
'url'        => 'http://www.mediawiki.org/wiki/Extension:Workflow',
+
'url'        => "http://www.mediawiki.org/wiki/Extension:Workflow",
 
'version'    => WORKFLOW_VERSION
 
'version'    => WORKFLOW_VERSION
 
);
 
);
  
# Define a new specialpage for displaying a list of workflows
+
/**
 +
* Define a new specialpage for displaying a list of workflows
 +
*/
 
require_once "$IP/includes/SpecialPage.php";
 
require_once "$IP/includes/SpecialPage.php";
 
class SpecialWorkflow extends SpecialPage {
 
class SpecialWorkflow extends SpecialPage {
Line 35: Line 39:
 
$wgParser->disableCache();
 
$wgParser->disableCache();
 
$this->setHeaders();
 
$this->setHeaders();
$wgOut->addWikiText('Not done yet... discussion at [[Extension talk:Workflow.php]]', true);
+
$wgOut->addWikiText("Not done yet... discussion at [[Extension talk:Workflow.php]]", true);
 
}
 
}
 
}
 
}
 
 
# Define main workflow class containing all the functionality
+
/**
 +
* Define main workflow class containing all the functionality
 +
*/
 
class Workflow {
 
class Workflow {
  
Line 46: Line 52:
 
var $name;    # name of the workflow to update
 
var $name;    # name of the workflow to update
 
var $pagename; # the name of the current page (in which the workflows reside)
 
var $pagename; # the name of the current page (in which the workflows reside)
 +
var $cat;      # lcoal lang name for "Category"
 
var $title;
 
var $title;
 
var $workflowData = array();
 
var $workflowData = array();
  
# Constructor
+
/**
 +
* Constructor
 +
*/
 
function Workflow() {
 
function Workflow() {
global $wgHooks, $wgSiteNotice, $wgExtensionFunctions, $wgWorkflowMagic, $wgExtraNamespaces;
+
global $wgHooks, $wgSiteNotice, $wgExtensionFunctions, $wgWorkflowMagic, $wgExtraNamespaces, $wgAjaxExportList;
  
 
# 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">The NS_WORKFLOW namespace must be defined. Extension is disabled.</div>';
+
$wgSiteNotice .= "<div class='usermessage'>The NS_WORKFLOW namespace must be defined. Extension is disabled.</div>";
 
return;
 
return;
 
}
 
}
Line 62: Line 71:
 
#$this->magic = $wgContLang->getNsText(NS_WORKFLOW);
 
#$this->magic = $wgContLang->getNsText(NS_WORKFLOW);
 
$this->magic = $wgExtraNamespaces[NS_WORKFLOW]; # $wgContLang doesn't exist yet
 
$this->magic = $wgExtraNamespaces[NS_WORKFLOW]; # $wgContLang doesn't exist yet
 +
 +
$wgAjaxExportList[] = $this->magic;
 
 
 
# Reserve the magic word for use as a parser-function
 
# Reserve the magic word for use as a parser-function
Line 74: Line 85:
 
}
 
}
  
# Setup
+
/**
 +
* Extension setup
 +
*/
 
function setup() {
 
function setup() {
 
global $wgParser, $wgLanguageCode, $wgMessageCache, $wgContLang;
 
global $wgParser, $wgLanguageCode, $wgMessageCache, $wgContLang;
$cat = $wgContLang->getNsText(NS_CATEGORY);
 
 
 
 +
$this->cat = $wgContLang->getNsText(NS_CATEGORY);
 +
 
# Add the messages used (todo: move into i18n)
 
# Add the messages used (todo: move into i18n)
 
if ($wgLanguageCode == 'en') {
 
if ($wgLanguageCode == 'en') {
 
$wgMessageCache->addMessages(array(
 
$wgMessageCache->addMessages(array(
 
'workflow' => $this->magic, # NOTE: the parser-function, special-page and link-rendering all use this word
 
'workflow' => $this->magic, # NOTE: the parser-function, special-page and link-rendering all use this word
'workflowStateUpdated' => "[[{$this->magic}:$1|$1 {$this->magic}]] state set to [[$cat:$2|$2]]."
+
'workflowStateUpdated' => "[[{$this->magic}:$1|$1 {$this->magic}]] state set to [[{$this->cat}:$2|$2]]."
 
));
 
));
 
}
 
}
Line 97: Line 111:
 
}
 
}
  
# Expand the #workflow 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
+
* Expand the #workflow 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) {
 
function expandMagic(&$parser) {
 
global $wgUser, $wgJsMimeType, $wgContLang;
 
global $wgUser, $wgJsMimeType, $wgContLang;
 
$parser->disableCache();
 
$parser->disableCache();
 
$tmpl = $wgContLang->getNsText(NS_TEMPLATE);
 
$tmpl = $wgContLang->getNsText(NS_TEMPLATE);
$cat = $wgContLang->getNsText(NS_CATEGORY);
 
 
$wf = $this->magic;
 
$wf = $this->magic;
  
Line 116: Line 131:
 
foreach (func_get_args() as $arg) if (!is_object($arg)) {
 
foreach (func_get_args() as $arg) if (!is_object($arg)) {
 
$args .= "|$arg";
 
$args .= "|$arg";
if (preg_match('/^(.+?)\\s*=\\s*(.+)$/',$arg,$match)) $argv[$match[1]] = $match[2]; else $argv[] = $arg;
+
if (preg_match("%^(.+?)\\s*=\\s*(.+)$%", $arg, $match)) $argv[$match[1]] = $match[2]; else $argv[] = $arg;
 
}
 
}
 
$name = $argv[0];
 
$name = $argv[0];
Line 128: Line 143:
 
# Get the list of workflow states from the Workflow article
 
# Get the list of workflow states from the Workflow article
 
# NOTE: this method of using the parser to get the links corrupts the order, so a preg_match_all may be preferable
 
# NOTE: this method of using the parser to get the links corrupts the order, so a preg_match_all may be preferable
$states = array();
+
$states = $this->loadStates($name);
$title  = Title::newFromtext($name,NS_WORKFLOW);
+
$title  = Title::newFromtext($name, NS_WORKFLOW);
 
$edit  = $title->userCan('edit');
 
$edit  = $title->userCan('edit');
 
$url    = $title->getLocalUrl();
 
$url    = $title->getLocalUrl();
Line 135: Line 150:
 
$anchor = "$wf:$name";
 
$anchor = "$wf:$name";
 
if (is_object($title) && $title->exists()) {
 
if (is_object($title) && $title->exists()) {
$article = new Article($title);
+
if (count($states)<1) $anchor .= " (".wfMsg('workflowMissingContent').")";
if (preg_match_all('/^\\*\\s*\\[{2}\\s*(.+?:)?\\s*(.+?)\\s*\\]{2}/m', $article->getContent(), $match)) $states = $match[2];
+
} else $href .= " class='new'";
else $anchor .= " (".wfMsg('workflowMissingContent').")";
 
}
 
else $href .= " class='new'";
 
 
$html = "<a $href title='$anchor'>$anchor</a>";
 
$html = "<a $href title='$anchor'>$anchor</a>";
  
Line 150: Line 162:
 
else $left = $right = "<td></td>"; # no menu buttons if not allowed to edit
 
else $left = $right = "<td></td>"; # no menu buttons if not allowed to edit
 
$html  = "<div class='workflow' id='workflow-$name'>";
 
$html  = "<div class='workflow' id='workflow-$name'>";
$data  = '';
+
$data  = "";
 
$ci    = 0;
 
$ci    = 0;
foreach ($states as $i => $dbk) {
+
foreach ($states as $i => $state) {
 
$i++;
 
$i++;
$stitle = Title::newFromText($dbk, NS_TEMPLATE);
+
$stitle = Title::newFromText($state, NS_TEMPLATE);
 
$surl  = $stitle->getLocalUrl();
 
$surl  = $stitle->getLocalUrl();
$state  = $stitle->getText();
 
$anchor = preg_replace('|^.+/|','',$state);
 
 
$data  .= ",'$state'";
 
$data  .= ",'$state'";
$style  = 'display:none';
+
$style  = "display:none";
 
$p      =& $psr; # use local parser by default
 
$p      =& $psr; # use local parser by default
 +
$p      =& $parser; # use local parser by default
 
$this->workflowData[$name][] = $state;
 
$this->workflowData[$name][] = $state;
 
if ($current === false) $current = $state; # make current default to first item name
 
if ($current === false) $current = $state; # make current default to first item name
 
$wikitext = $stitle->exists() ? '{'.'{'."$state|state=$state$args}".'}' : "[[$tmpl:$state]]"; # transclude or render a red-link
 
$wikitext = $stitle->exists() ? '{'.'{'."$state|state=$state$args}".'}' : "[[$tmpl:$state]]"; # transclude or render a red-link
# State should be loaded current category currently assigned from article rather than from the state arguement in the parser function
+
if (0&&$state == $current) {
# Regex needs to check for all categorizations and remove redunant ones for that particular workflow. Updated article needs the new
+
$wikitext .= "[[{$this->cat}:$state]]"; # if state is current, add a category link
# state appended to the end of the article
+
$style = "";
 
 
if ($state == $current) {
 
$wikitext .= "[[$cat:$state]]"; # if state is current, add a category link
 
$style = '';
 
 
$p =& $parser; # use the global parser so catlinks are updated
 
$p =& $parser; # use the global parser so catlinks are updated
 
$ci = $i;
 
$ci = $i;
 
}
 
}
$content = $p->parse($wikitext,$title,$opt,false,$state != $current)->getText();
+
$content = $p->parse($wikitext, $title, $opt, false, $state != $current)->getText();
  
 
# Append the menu to the state
 
# Append the menu to the state
Line 189: Line 196:
 
}
 
}
  
# Update the current state of a workflow item in the requested article
+
/**
# - the actual article edit is done in returnCatlinks after all parsin finished
+
* Load states into workflowData property for passed workflow name
# - this disables the parser-cache if the content has changed
+
*/
 +
function loadStates($name) {
 +
$states = array();
 +
$title  = Title::newFromText($name, NS_WORKFLOW);
 +
if (is_object($title) && $title->exists()) {
 +
$article = new Article($title);
 +
if (preg_match_all("%^\\*\\s*\\[{2}\\s*(.+?:)?\\s*(.+?)\\s*\\]{2}%m", $article->getContent(), $match)) {
 +
$states = $this->workflowData[$name] = $match[2];
 +
}
 +
}
 +
return $states;
 +
}
 +
 
 +
/**
 +
* Update the current state of a workflow item in the requested article
 +
* - the actual article edit is done in returnCatlinks after all parsing finished
 +
* - this disables the parser-cache
 +
*/
 
function updateState(&$parser, &$text) {
 
function updateState(&$parser, &$text) {
$result = preg_replace_callback(
+
$this->loadStates($this->name);
"/\\{\\{\\s*#\\s*{$this->magic}\\s*:\\s*{$this->name}\\s*(.*?)\\s*\\}\\}/is",
+
static $i = 0;
array($this, 'updateStateCallback'),
+
if ($i++ < 1 && isset($this->workflowData[$this->name]) && count($this->workflowData[$this->name]) > 1) {
$text,
+
$cats = $this->workflowData[$this->name];
1,
+
$cats = join('|', $cats);
$count
+
#print "xxxx$text"; #preg_replace("%\[\[{$this->cat}:($cats)\]\]%", "", $text);
);
+
$parser->disableCache();
if ($count) $this->text = $text = $result;
+
$this->text .= $text."\n\n";
$parser->disableCache();
+
}
 
return true;
 
return true;
 
}
 
}
  
# Replacement callback for updating tag state
+
/**
function updateStateCallback($match) {
+
* Extend catlinks information to include workflows
$result = preg_replace("/(state\\s*=\\s*['\"]?)[^'\"\]\|]+/", "$1{$this->state}", $match[1], 1, $count);
+
*/
if ($count < 1) $result = "|state={$this->state}".$match[1];
 
return '{'.'{'."#{$this->magic}:{$this->name}$result".'}'.'}';
 
}
 
 
 
# Extend catlinks information to include workflows
 
 
function extendCatlinks() {
 
function extendCatlinks() {
 
static $done = 0;
 
static $done = 0;
Line 233: Line 252:
 
}
 
}
 
 
# Render the workflow info which appears in the catlinks area
+
/**
 +
* Render the workflow info which appears in the catlinks area
 +
*/
 
function renderWorkflowInfo(&$catlinks) {
 
function renderWorkflowInfo(&$catlinks) {
 
if (count($this->workflowData)) {
 
if (count($this->workflowData)) {
Line 243: Line 264:
 
$catlinks .= "$table<a href='{$title->getLocalURL()}'>$name</a>:&nbsp;</td><td>";
 
$catlinks .= "$table<a href='{$title->getLocalURL()}'>$name</a>:&nbsp;</td><td>";
 
$current = 0;
 
$current = 0;
$sep = '&nbsp;';
+
$sep = "&nbsp;";
 
foreach ($states as $i => $state) {
 
foreach ($states as $i => $state) {
 
if ($current) {
 
if ($current) {
Line 250: Line 271:
 
$class .= $title->exists() ? '' : ' new';
 
$class .= $title->exists() ? '' : ' new';
 
$catlinks .= "$sep<a class='$class' href='{$title->getLocalURL()}'>$state</a>";
 
$catlinks .= "$sep<a class='$class' href='{$title->getLocalURL()}'>$state</a>";
$sep = ' &rarr; ';
+
$sep = " &rarr; ";
 
}
 
}
 
else $current = $state;
 
else $current = $state;
Line 261: Line 282:
 
}
 
}
  
# Return just the catlinks to the client after updating a tag state
+
/**
# - the article is updated here if replacement text has been set
+
* Return just the catlinks to the client after updating a tag state
function returnCatlinks() {
+
* - the article is updated here if replacement text has been set
 +
*/
 +
function returnCatlinks(&$out, &$text) {
 
global $wgUser, $wgOut, $wgTitle;
 
global $wgUser, $wgOut, $wgTitle;
 
if ($this->text) {
 
if ($this->text) {
 
$article = new Article($wgTitle);
 
$article = new Article($wgTitle);
$article->doEdit($this->text, wfMsg('workflowStateUpdated', $this->name, $this->state), EDIT_UPDATE);
+
#$article->doEdit($this->text, wfMsg('workflowStateUpdated', $this->name, $this->state), EDIT_UPDATE);
 
}
 
}
 
$skin = $wgUser->getSkin();
 
$skin = $wgUser->getSkin();
$catlinks = is_object($skin) ? $skin->getCategories() : 'Error: no skin!';
+
$catlinks = is_object($skin) ? $skin->getCategories() : "Error: no skin!";
 
$wgOut->disable();
 
$wgOut->disable();
 
wfResetOutputBuffers();
 
wfResetOutputBuffers();
 
header("Cache-Control: no-cache, must-revalidate");
 
header("Cache-Control: no-cache, must-revalidate");
 
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
echo($catlinks);
+
print "<pre>text\n{$this->text}</pre>";
 +
print $catlinks;
 
return false;
 
return false;
 
}
 
}
  
# Make necessary Javascript functions available to the page
+
/**
 +
* Make necessary Javascript functions available to the page
 +
*/
 
function addJS() {
 
function addJS() {
 
global $wgOut, $wgJsMimeType, $wgWorkflowUpdateDelay;
 
global $wgOut, $wgJsMimeType, $wgWorkflowUpdateDelay;
Line 306: Line 332:
 
}
 
}
  
# If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render)
+
/**
# - this sets the state, name, pagename and update properties
+
* If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render)
 +
* - this sets the state, name, pagename and update properties
 +
*/
 
function bypassAjaxDispatcher() {
 
function bypassAjaxDispatcher() {
global $wgUseAjax,$wgHooks;
+
global $wgUseAjax, $wgHooks;
if ($wgUseAjax && $_REQUEST['action'] == 'ajax' && $_REQUEST['rs'] == $this->magic && is_array($_REQUEST['rsargs'])) {
+
if ($wgUseAjax
list($_REQUEST['title'], $this->name,$this->state) = $_REQUEST['rsargs'];
+
&& isset($_GET['action'])
$wgHooks['ParserBeforeStrip'][] = array($this, 'updateState');
+
&& $_GET['action'] == 'ajax'
 +
&& isset($_GET['rs'])
 +
&& $_GET['rs'] == $this->magic
 +
&& isset($_GET['rsargs'])
 +
&& is_array($_GET['rsargs'])
 +
) {
 +
list($title, $this->name, $this->state) = $_GET['rsargs'];
 +
$wgHooks['ParserBeforeStrip'][]   = array($this, 'updateState');
 
$wgHooks['OutputPageBeforeHTML'][] = array($this, 'returnCatlinks');
 
$wgHooks['OutputPageBeforeHTML'][] = array($this, 'returnCatlinks');
$_REQUEST['action'] = 'render';
+
$_GET = $_REQUEST = array('title' => $title, 'action' => 'view');
 
}
 
}
$this->pagename = $_REQUEST['title'];
+
$this->pagename = isset($_REQUEST['title']) ? $_REQUEST['title'] : '';
 
$this->text = '';
 
$this->text = '';
 
}
 
}
  
# Needed in some versions to prevent Special:Version from breaking
+
/**
 +
* Needed in some versions to prevent Special:Version from breaking
 +
*/
 
function __toString() { return 'Workflow'; }
 
function __toString() { return 'Workflow'; }
  
# Set up the magic words
+
/**
 +
* Set up the magic words
 +
*/
 
function languageGetMagic(&$magicWords, $langCode = 0) {
 
function languageGetMagic(&$magicWords, $langCode = 0) {
 
$magicWords[$this->magic] = array($langCode, $this->magic);
 
$magicWords[$this->magic] = array($langCode, $this->magic);
 
return true;
 
return true;
 
}
 
}
 
 
}
 
}
  
 
$wgWorkflow = new Workflow();
 
$wgWorkflow = new Workflow();
 
</php>
 

Revision as of 09:06, 7 November 2008

<?php /**

* Extension:Workflow
*
Info.svg These are the MediaWiki extensions we're using and/or developing. Please refer to the information on the mediawiki.org wiki for installation and usage details. Extensions here which have no corresponding mediawiki article are either not ready for use or have been superseded. You can also browse our extension code in our local Subversion repository or our GitHub mirror.

Template:PhpCategory:Extensions created with Template:Extension

* @package MediaWiki
* @subpackage Extensions
* @author Aran Dunkley User:Nad
* @licence GNU General Public Licence 2.0 or later
* Started: 2007-10-06
*/

if (!defined('MEDIAWIKI')) die("Not an entry point.");

define('WORKFLOW_VERSION', "0.1.0, 2008-11-07");

$wgWorkflowUpdateDelay = 1000; # Delay in milliseconds after clicking state before Ajax update is made

$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... discussion at Extension talk:Workflow.php", true); } }

/**

* Define main workflow class containing all the functionality
*/

class Workflow {

var $magic; # magic word used for the workflow parser-function (set from messages) var $state; # state to update a workflow to in an ajax update request var $name; # name of the workflow to update var $pagename; # the name of the current page (in which the workflows reside) var $cat; # lcoal lang name for "Category" var $title; var $workflowData = array();

/** * Constructor */ function Workflow() { global $wgHooks, $wgSiteNotice, $wgExtensionFunctions, $wgWorkflowMagic, $wgExtraNamespaces, $wgAjaxExportList;

# Require NS_WORKFLOW to be defined before installing parser-function or javascript etc if (!defined('NS_WORKFLOW')) {

$wgSiteNotice .= "

The NS_WORKFLOW namespace must be defined. Extension is disabled.

";

return; }

# The parser-function name, special-page name and links all use the word defined in $wgExtraNamespaces[NS_WORKFLOW] #$this->magic = $wgContLang->getNsText(NS_WORKFLOW); $this->magic = $wgExtraNamespaces[NS_WORKFLOW]; # $wgContLang doesn't exist yet

$wgAjaxExportList[] = $this->magic;

# Reserve the magic word for use as a parser-function $wgHooks['LanguageGetMagic'][] = array($this, 'languageGetMagic');

# If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render) # - this sets the state, name, pagename and update properties $this->bypassAjaxDispatcher();

# Add the extension's setup function to the list to be called after the environment is up and running $wgExtensionFunctions[] = array($this, 'setup'); }

/** * Extension setup */ function setup() { global $wgParser, $wgLanguageCode, $wgMessageCache, $wgContLang;

$this->cat = $wgContLang->getNsText(NS_CATEGORY);

# Add the messages used (todo: move into i18n) if ($wgLanguageCode == 'en') { $wgMessageCache->addMessages(array( 'workflow' => $this->magic, # NOTE: the parser-function, special-page and link-rendering all use this word 'workflowStateUpdated' => "[[{$this->magic}:$1|$1 {$this->magic}]] state set to [[{$this->cat}:$2|$2]]." )); }

# Add the specialpage to the environment SpecialPage::addPage(new SpecialWorkflow());

# Add the parser-function hook $wgParser->setFunctionHook($this->magic, array($this, 'expandMagic'));

# Add the client-side scripts for changing states $this->addJS(); }

/** * Expand the #workflow 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, $wgJsMimeType, $wgContLang; $parser->disableCache(); $tmpl = $wgContLang->getNsText(NS_TEMPLATE); $wf = $this->magic;

# Extend catlinks information to include workflows $this->extendCatlinks();

# Populate $argv with both named and numeric parameters $args =; $argv = array(); $items = array(); $name = 'Untitled'; foreach (func_get_args() as $arg) if (!is_object($arg)) { $args .= "|$arg"; if (preg_match("%^(.+?)\\s*=\\s*(.+)$%", $arg, $match)) $argv[$match[1]] = $match[2]; else $argv[] = $arg; } $name = $argv[0]; $this->workflowData[$name] = array(0); $current = isset($argv['state']) ? $argv['state'] : false;

# 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 # NOTE: this method of using the parser to get the links corrupts the order, so a preg_match_all may be preferable $states = $this->loadStates($name); $title = Title::newFromtext($name, NS_WORKFLOW); $edit = $title->userCan('edit'); $url = $title->getLocalUrl(); $href = "href='$url'"; $anchor = "$wf:$name"; if (is_object($title) && $title->exists()) { if (count($states)<1) $anchor .= " (".wfMsg('workflowMissingContent').")"; } else $href .= " class='new'"; $html = "<a $href title='$anchor'>$anchor</a>";

# Transclude each (use a parser clone for the ones which aren't current to avoid categorisation) if (count($states)) { if ($edit) {

$left = "<a href='javascript:;'><</a>"; $right = "<a href='javascript:;'>></a>";

}

else $left = $right = ""; # no menu buttons if not allowed to edit $html = "

";

$data = ""; $ci = 0; foreach ($states as $i => $state) { $i++; $stitle = Title::newFromText($state, NS_TEMPLATE); $surl = $stitle->getLocalUrl(); $data .= ",'$state'"; $style = "display:none"; $p =& $psr; # use local parser by default $p =& $parser; # use local parser by default $this->workflowData[$name][] = $state; if ($current === false) $current = $state; # make current default to first item name $wikitext = $stitle->exists() ? '{'.'{'."$state|state=$state$args}".'}' : "$tmpl:$state"; # transclude or render a red-link if (0&&$state == $current) { $wikitext .= "[[{$this->cat}:$state]]"; # if state is current, add a category link $style = ""; $p =& $parser; # use the global parser so catlinks are updated $ci = $i; } $content = $p->parse($wikitext, $title, $opt, false, $state != $current)->getText();

# Append the menu to the state

$html .= "
$left $right
$content
\n";

}

$html .= "

<script type='$wgJsMimeType'>workflowData['$name']=[$ci$data];</script>\n";

$this->workflowData[$name][0] = $ci; }

return array($html, 'isHTML' => true, 'noparse' => true); }

/** * Load states into workflowData property for passed workflow name */ function loadStates($name) { $states = array(); $title = Title::newFromText($name, NS_WORKFLOW); if (is_object($title) && $title->exists()) { $article = new Article($title); if (preg_match_all("%^\\*\\s*\\[{2}\\s*(.+?:)?\\s*(.+?)\\s*\\]{2}%m", $article->getContent(), $match)) { $states = $this->workflowData[$name] = $match[2]; } } return $states; }

/** * Update the current state of a workflow item in the requested article * - the actual article edit is done in returnCatlinks after all parsing finished * - this disables the parser-cache */ function updateState(&$parser, &$text) { $this->loadStates($this->name); static $i = 0; if ($i++ < 1 && isset($this->workflowData[$this->name]) && count($this->workflowData[$this->name]) > 1) { $cats = $this->workflowData[$this->name]; $cats = join('|', $cats); #print "xxxx$text"; #preg_replace("%\[\[{$this->cat}:($cats)\]\]%", "", $text); $parser->disableCache(); $this->text .= $text."\n\n"; } return true; }

/** * 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 getCategories() { global $wgWorkflow; return $wgWorkflow->renderWorkflowInfo(parent::getCategories()); } }');

# 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) { if (count($this->workflowData)) { //$title = Title::makeTitle(NS_SPECIAL,wfMsg('workflow'));

//$table = "

<a href='{$title->getLocalURL()}'>".wfMsg('workflow')."</a>:"; $table = "
";

foreach ($this->workflowData as $name => $states) { $title = Title::makeTitle(NS_WORKFLOW, $name);

$catlinks .= "$table<a href='{$title->getLocalURL()}'>$name</a>: 
";

$current = 0; $sep = " "; foreach ($states as $i => $state) { if ($current) { $title = Title::makeTitle(NS_CATEGORY,$state); $class = $current == $i ? 'current' : ; $class .= $title->exists() ?  : ' new'; $catlinks .= "$sep<a class='$class' href='{$title->getLocalURL()}'>$state</a>"; $sep = " → "; } else $current = $state; }

$table = "
";

}

$catlinks .= "
\n";

} return $catlinks; }

/** * Return just the catlinks to the client after updating a tag state * - the article is updated here if replacement text has been set */ function returnCatlinks(&$out, &$text) { global $wgUser, $wgOut, $wgTitle; if ($this->text) { $article = new Article($wgTitle); #$article->doEdit($this->text, wfMsg('workflowStateUpdated', $this->name, $this->state), EDIT_UPDATE); } $skin = $wgUser->getSkin(); $catlinks = is_object($skin) ? $skin->getCategories() : "Error: no skin!"; $wgOut->disable(); wfResetOutputBuffers(); header("Cache-Control: no-cache, must-revalidate"); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

print "
text\n{$this->text}
";

print $catlinks; return false; }

/** * Make necessary Javascript functions available to the page */ function addJS() { global $wgOut, $wgJsMimeType, $wgWorkflowUpdateDelay; $wgOut->addScript("<script type='$wgJsMimeType'> var workflowData = []; var workflowUpdate = 0; var workflowLastState = 0; function workflowUpdateState(name) { clearTimeout(workflowUpdate); workflowLastState = workflowData[name][0]; var state = workflowData[name][workflowLastState]; sajax_do_call('{$this->magic}',[wgPageName,name,state],document.getElementById('catlinks')); } function workflowSwitchState(name,dir) { clearTimeout(workflowUpdate); document.getElementById('workflow-'+name+'-'+workflowData[name][0]).setAttribute('style','display:none'); if (workflowLastState == 0) workflowLastState = workflowData[name][0]; workflowData[name][0] += dir; if (workflowData[name][0] < 1) workflowData[name][0] = workflowData[name].length-1; if (workflowData[name][0] > workflowData[name].length-1) workflowData[name][0] = 1; var state = workflowData[name][0]; document.getElementById('workflow-'+name+'-'+state).setAttribute('style',); if (workflowLastState != state) workflowUpdate = setTimeout('workflowUpdateState(\"'+name+'\")',$wgWorkflowUpdateDelay); } </script>"); }

/** * If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render) * - this sets the state, name, pagename and update properties */ function bypassAjaxDispatcher() { global $wgUseAjax, $wgHooks; if ($wgUseAjax && isset($_GET['action']) && $_GET['action'] == 'ajax' && isset($_GET['rs']) && $_GET['rs'] == $this->magic && isset($_GET['rsargs']) && is_array($_GET['rsargs']) ) { list($title, $this->name, $this->state) = $_GET['rsargs']; $wgHooks['ParserBeforeStrip'][] = array($this, 'updateState'); $wgHooks['OutputPageBeforeHTML'][] = array($this, 'returnCatlinks'); $_GET = $_REQUEST = array('title' => $title, 'action' => 'view'); } $this->pagename = isset($_REQUEST['title']) ? $_REQUEST['title'] : ; $this->text = ; }

/** * 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($langCode, $this->magic); return true; } }

$wgWorkflow = new Workflow();