Difference between revisions of "Extension:Workflow.php"
(almost there but not quite working yet) |
(1.0.0 - new way which can optionally work with templates) |
||
Line 11: | Line 11: | ||
if (!defined('MEDIAWIKI')) die("Not an entry point."); | if (!defined('MEDIAWIKI')) die("Not an entry point."); | ||
− | define('WORKFLOW_VERSION', "0. | + | define('WORKFLOW_VERSION', "1.0.0, 2008-12-01"); |
+ | $wgWorkflowMagic = 'Workflow'; | ||
$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 | ||
Line 22: | Line 23: | ||
'url' => "http://www.mediawiki.org/wiki/Extension:Workflow", | 'url' => "http://www.mediawiki.org/wiki/Extension:Workflow", | ||
'version' => WORKFLOW_VERSION | 'version' => WORKFLOW_VERSION | ||
− | + | ); | |
/** | /** | ||
Line 48: | Line 49: | ||
class Workflow { | class Workflow { | ||
− | var $magic; | + | var $magic; # magic word used for the workflow parser-function (set from messages) |
− | + | var $cat; # local lang name for "Category" | |
− | + | var $workflowData = array(); # Stores lists of state-content, current state and args for each workflow | |
− | + | var $state = false; # state to update a workflow to in an ajax update request | |
− | var $cat; | + | var $name; # name of the workflow to update |
− | |||
− | var $workflowData | ||
− | var $ | ||
/** | /** | ||
Line 61: | Line 59: | ||
*/ | */ | ||
function Workflow() { | function Workflow() { | ||
− | global $wgHooks | + | global $wgHooks, $wgExtensionFunctions, $wgWorkflowMagic; |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
# The parser-function name, special-page name and links all use the word defined in $wgExtraNamespaces[NS_WORKFLOW] | # The parser-function name, special-page name and links all use the word defined in $wgExtraNamespaces[NS_WORKFLOW] | ||
− | + | $this->magic = $wgWorkflowMagic; | |
− | |||
− | |||
− | |||
# Reserve the magic word for use as a parser-function | # Reserve the magic word for use as a parser-function | ||
Line 79: | Line 68: | ||
# If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render) | # 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 | + | # - this sets the $this->state and $this->name so that OutputPageBeforeHTML calls updateState() |
$this->bypassAjaxDispatcher(); | $this->bypassAjaxDispatcher(); | ||
Line 97: | Line 86: | ||
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' => " | + | 'workflowMissingStates' => "No workflow states defined", |
+ | 'workflowStateUpdated' => "$1 {$this->magic} state set to [[{$this->cat}:$2|$2]]." | ||
)); | )); | ||
} | } | ||
Line 114: | Line 104: | ||
/** | /** | ||
* Expand the #workflow to reveal the current state and hide the others and add javascript | * Expand the #workflow to reveal the current state and hide the others and add javascript | ||
− | |||
*/ | */ | ||
function expandMagic(&$parser) { | function expandMagic(&$parser) { | ||
− | global $ | + | global $wgTitle, $wgJsMimeType; |
$parser->disableCache(); | $parser->disableCache(); | ||
− | |||
− | |||
# Extend catlinks information to include workflows | # Extend catlinks information to include workflows | ||
$this->extendCatlinks(); | $this->extendCatlinks(); | ||
− | # | + | # Extract workflow info from args |
− | $ | + | $name = 'Untitled'; |
− | $ | + | $states = array(); |
− | $ | + | $tmpl = false; |
− | $ | + | $par = 'state'; |
foreach (func_get_args() as $arg) if (!is_object($arg)) { | foreach (func_get_args() as $arg) if (!is_object($arg)) { | ||
− | + | if (preg_match("%^(.+?)\\s*=\\s*(.+)$%", $arg, $match)) { | |
− | if (preg_match("%^(.+?)\\s*=\\s*(.+)$%", $arg, $match)) $ | + | list($arg, $k, $v) = $match; |
+ | switch ($k) { | ||
+ | case 'template' : $tmpl = $v; break; | ||
+ | case 'parameter' : $par = $v; break; | ||
+ | default: $states[$k] = $v; | ||
+ | } | ||
+ | } else $name = $arg; | ||
} | } | ||
− | |||
− | # | + | # Store the data for this workflow |
− | + | $this->workflowData[$name] = array('parameter' => $par, 'template' => $tmpl, 'states' => $states); | |
− | + | $current = $this->getCurrentState($name); | |
− | |||
− | |||
− | $ | ||
− | |||
# Build links for workflow table | # Build links for workflow table | ||
− | + | $edit = $wgTitle->userCan('edit'); | |
− | $edit = $ | + | $url = $wgTitle->getLocalUrl(); |
− | $url = $ | + | $anchor = $name; |
− | + | if (count($states) < 1) $anchor .= " (".wfMsg('workflowMissingStates').")"; | |
− | $anchor = | + | $html = "<a href='$url' title='$anchor'>$anchor</a>"; |
− | |||
− | |||
− | |||
− | $html = "<a $ | ||
− | # | + | # Render the states in their div elements with only current one visible |
if (count($states)) { | if (count($states)) { | ||
if ($edit) { | if ($edit) { | ||
Line 162: | Line 146: | ||
} | } | ||
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 | + | $html = "<div class='workflow' id='workflow-$name'>"; |
− | $data | + | $data = array_search($current, array_keys($states))+1; |
− | + | foreach ($states as $state => $wikitext) { | |
− | foreach ($states as $ | + | $data .= ",'$state'"; |
− | + | $style = $state == $current ? "" : "display:none"; | |
− | + | $content = $parser->parse($wikitext, $wgTitle, $parser->mOptions, false, $state != $current)->getText(); | |
− | $data | + | $stitle = Title::newFromText($state, NS_CATEGORY); |
− | $style | + | $surl = $stitle->getLocalUrl(); |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | $content = $parser->parse($wikitext, $ | ||
# Append the menu to the state | # Append the menu to the state | ||
− | $html .= "<div class='workflow-state' id='workflow-$name-$ | + | $html .= "<div class='workflow-state' id='workflow-$name-$state' style='$style'> |
− | <table cellpadding='0' cellspacing='0'><tr><td id='content' colspan='3'>$content</td></tr><tr>$left | + | <table cellpadding='0' cellspacing='0'><tr><td id='workflow-content' colspan='3'>$content</td></tr><tr>$left |
− | <td class='menu' id='title'><a href='$surl' title='$ | + | <td class='menu' id='title'><a href='$surl' title='{$this->cat}:$state'>$state</a></td>$right |
</tr></table></div>\n"; | </tr></table></div>\n"; | ||
} | } | ||
− | $html .= "</div><script type='$wgJsMimeType'>workflowData['$name']=[ | + | $html .= "</div><script type='$wgJsMimeType'>workflowData['$name']=[$data];</script>\n"; |
} | } | ||
return array($html, 'isHTML' => true, 'noparse' => true); | return array($html, 'isHTML' => true, 'noparse' => true); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
Line 250: | Line 181: | ||
function getCategories() { | function getCategories() { | ||
global $wgWorkflow; | global $wgWorkflow; | ||
+ | if ($wgWorkflow->state) $wgWorkflow->updateCatLinks(); | ||
return $wgWorkflow->renderWorkflowInfo(parent::getCategories()); | return $wgWorkflow->renderWorkflowInfo(parent::getCategories()); | ||
} | } | ||
Line 257: | Line 189: | ||
$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; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Update $wgOut's category links if state updated by ajax | ||
+ | */ | ||
+ | function updateCatLinks() { | ||
+ | global $wgOut, $wgUser; | ||
+ | $data =& $this->workflowData[$this->name]; | ||
+ | $cats = join('|', array_keys($data['states'])); | ||
+ | $tmp = array(); | ||
+ | foreach ($wgOut->mCategoryLinks['normal'] as $i => $link) if (!preg_match("%>($cats)</a>%i", $link)) $tmp[] = $link; | ||
+ | $title = Title::newFromText($this->state, NS_CATEGORY); | ||
+ | $tmp[] = $wgUser->getSkin()->makeLinkObj($title, $title->getText()); | ||
+ | $wgOut->mCategoryLinks['normal'] = $tmp; | ||
} | } | ||
Line 265: | Line 211: | ||
if (count($this->workflowData)) { | if (count($this->workflowData)) { | ||
$table = "<table cellpadding='0' cellspacing='0'><tr><td></td><td align='right'>"; | $table = "<table cellpadding='0' cellspacing='0'><tr><td></td><td align='right'>"; | ||
− | foreach ($this->workflowData as $name => $ | + | foreach ($this->workflowData as $name => $data) { |
− | $ | + | $current = $this->getCurrentState($name); |
− | + | $catlinks .= "$table<b>$name</b>: </td><td>"; | |
− | $catlinks .= "$table< | ||
− | |||
$sep = " "; | $sep = " "; | ||
− | foreach ($states as $state) { | + | foreach (array_keys($data['states']) as $state) { |
$title = Title::newFromText($state, NS_CATEGORY); | $title = Title::newFromText($state, NS_CATEGORY); | ||
− | $class = $current == $state ? 'current' : '' | + | $class = ($current == $state ? 'current' : '') . ($title->exists() ? '' : ' new'); |
− | + | $catlinks .= "$sep<a class='$class' href='{$title->getLocalURL()}'>{$title->getText()}</a>"; | |
− | $catlinks .= "$sep<a class='$class' href='{$title->getLocalURL()}'>$ | ||
$sep = " → "; | $sep = " → "; | ||
} | } | ||
Line 286: | Line 229: | ||
/** | /** | ||
− | * | + | * Extract the current state of the named workflow from the current article content |
− | |||
*/ | */ | ||
− | function | + | function getCurrentState($name) { |
− | + | $data =& $this->workflowData[$name]; | |
− | if ($ | + | if (isset($data['current'])) $state = $data['current']; |
+ | else { | ||
+ | |||
+ | # Get the content of the current article | ||
+ | global $wgTitle; | ||
$article = new Article($wgTitle); | $article = new Article($wgTitle); | ||
− | $article-> | + | $text = $article->getContent(); |
+ | $cats = join('|', array_keys($data['states'])); | ||
+ | |||
+ | # Template specified, extract state from template parameter | ||
+ | if ($data['template']) { | ||
+ | $tmpl = $data['template']; | ||
+ | $par = $data['parameter']; | ||
+ | if (preg_match("%\s*\{\{$tmpl.+?\|\s*$par\s*=\s*(\w+)%s", $text, $m)) $state = $m[1]; | ||
+ | } | ||
+ | |||
+ | # No template specified, extract state from category links | ||
+ | elseif (preg_match("%\s*\[\[{$this->cat}:($cats)\]\]\s*%", $text, $m)) $state = $m[1]; | ||
+ | |||
+ | # Otherwise default to first defined state | ||
+ | else { | ||
+ | $state = array_keys($data['states']); | ||
+ | $state = count($state) ? $state[0] : 'Error: No states defined!'; | ||
+ | } | ||
} | } | ||
+ | return $state; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Update the current state of a workflow item in the requested article | ||
+ | * - called from OutputPageBeforeHTML if $this->state was set by bypassAjaxDispatcher() | ||
+ | */ | ||
+ | function updateState() { | ||
+ | $cat = $this->cat; | ||
+ | $name = $this->name; | ||
+ | $state = $this->state; | ||
+ | $data = &$this->workflowData[$name]; | ||
+ | $cats = join('|', array_keys($data['states'])); | ||
+ | $tmpl = $data['template']; | ||
+ | $par = $data['parameter']; | ||
+ | $data['current'] = $state; | ||
+ | |||
+ | # Get current article content | ||
+ | global $wgTitle; | ||
+ | $article = new Article($wgTitle); | ||
+ | $text = $article->getContent(); | ||
+ | |||
+ | # Update the state in the article text (as template parameter or direct cat links) | ||
+ | $text = $tmpl | ||
+ | ? preg_replace("%(\s*\{\{$tmpl.+?\|\s*$par\s*=\s*)\w+%s", "$1$state", $text) | ||
+ | : preg_replace("%\s*\[\[{$cat}:($cats)\]\]\s*%i", "", $text) . "[[$cat:$state]]"; | ||
+ | |||
+ | # Update the article with the new text | ||
+ | $article->doEdit($text, wfMsg('workflowStateUpdated', $this->name, $this->state), EDIT_UPDATE); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Return just the catlinks to the client after updating a tag state | ||
+ | * - this also calls updateState() if $this->state set by bypassAjaxDispatcher() | ||
+ | */ | ||
+ | function onOutputPageBeforeHTML(&$out) { | ||
+ | global $wgUser; | ||
+ | if ($this->state) $this->updateState(); | ||
$skin = $wgUser->getSkin(); | $skin = $wgUser->getSkin(); | ||
$catlinks = is_object($skin) ? $skin->getCategories() : "Error: no skin!"; | $catlinks = is_object($skin) ? $skin->getCategories() : "Error: no skin!"; | ||
− | $ | + | $out->disable(); |
wfResetOutputBuffers(); | wfResetOutputBuffers(); | ||
header("Cache-Control: no-cache, must-revalidate"); | header("Cache-Control: no-cache, must-revalidate"); | ||
Line 318: | Line 319: | ||
workflowLastState = workflowData[name][0]; | workflowLastState = workflowData[name][0]; | ||
var state = workflowData[name][workflowLastState]; | var state = workflowData[name][workflowLastState]; | ||
− | sajax_do_call(' | + | sajax_do_call('workflow_ajax_callback',[wgPageName,name,state],document.getElementById('catlinks')); |
} | } | ||
function workflowSwitchState(name,dir) { | function workflowSwitchState(name,dir) { | ||
clearTimeout(workflowUpdate); | clearTimeout(workflowUpdate); | ||
− | + | var id = 'workflow-'+name+'-'+workflowData[name][workflowData[name][0]]; | |
+ | document.getElementById(id).setAttribute('style','display:none'); | ||
if (workflowLastState == 0) workflowLastState = workflowData[name][0]; | if (workflowLastState == 0) workflowLastState = workflowData[name][0]; | ||
workflowData[name][0] += dir; | workflowData[name][0] += dir; | ||
Line 328: | Line 330: | ||
if (workflowData[name][0] > workflowData[name].length-1) workflowData[name][0] = 1; | if (workflowData[name][0] > workflowData[name].length-1) workflowData[name][0] = 1; | ||
var state = workflowData[name][0]; | var state = workflowData[name][0]; | ||
− | + | id = 'workflow-'+name+'-'+workflowData[name][state]; | |
+ | document.getElementById(id).setAttribute('style',''); | ||
if (workflowLastState != state) workflowUpdate = setTimeout('workflowUpdateState(\"'+name+'\")',$wgWorkflowUpdateDelay); | if (workflowLastState != state) workflowUpdate = setTimeout('workflowUpdateState(\"'+name+'\")',$wgWorkflowUpdateDelay); | ||
} | } | ||
Line 336: | Line 339: | ||
/** | /** | ||
* If it's a workflow-related ajax call, don't use dispatcher (because we need the catlinks generated by normal page render) | * 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 | + | * - this sets the $this->state and $this->name so that OutputPageBeforeHTML calls updateState() |
+ | * - the ajax dispatcher id bypassed by changing the action to 'view' | ||
*/ | */ | ||
function bypassAjaxDispatcher() { | function bypassAjaxDispatcher() { | ||
− | global $wgUseAjax, $wgHooks; | + | global $wgUseAjax, $wgHooks, $wgAjaxExportList; |
if ($wgUseAjax | if ($wgUseAjax | ||
− | && isset($_GET['action']) | + | && isset($_GET['action']) && $_GET['action'] == 'ajax' |
− | + | && isset($_GET['rs']) && $_GET['rs'] == 'workflow_ajax_callback' | |
− | && isset($_GET['rs']) | + | && isset($_GET['rsargs']) && is_array($_GET['rsargs']) |
− | |||
− | && isset($_GET['rsargs']) | ||
− | |||
) { | ) { | ||
list($title, $this->name, $this->state) = $_GET['rsargs']; | list($title, $this->name, $this->state) = $_GET['rsargs']; | ||
− | + | $wgHooks['OutputPageBeforeHTML'][] = $this; | |
− | $wgHooks['OutputPageBeforeHTML'][] = | ||
$_GET = $_REQUEST = array('title' => $title, 'action' => 'view'); | $_GET = $_REQUEST = array('title' => $title, 'action' => 'view'); | ||
+ | $wgAjaxExportList[] = 'workflow_ajax_callback'; | ||
} | } | ||
− | |||
− | |||
} | } | ||
Line 360: | Line 359: | ||
* Needed in some versions to prevent Special:Version from breaking | * Needed in some versions to prevent Special:Version from breaking | ||
*/ | */ | ||
− | function __toString() { return | + | function __toString() { return __CLASS__; } |
/** | /** |
Revision as of 04:08, 1 December 2008
<?php /**
* Extension:Workflow *
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', "1.0.0, 2008-12-01");
$wgWorkflowMagic = 'Workflow'; $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 $cat; # local lang name for "Category" var $workflowData = array(); # Stores lists of state-content, current state and args for each workflow var $state = false; # state to update a workflow to in an ajax update request var $name; # name of the workflow to update
/** * Constructor */ function Workflow() { global $wgHooks, $wgExtensionFunctions, $wgWorkflowMagic;
# The parser-function name, special-page name and links all use the word defined in $wgExtraNamespaces[NS_WORKFLOW] $this->magic = $wgWorkflowMagic;
# 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 $this->state and $this->name so that OutputPageBeforeHTML calls updateState() $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 'workflowMissingStates' => "No workflow states defined", 'workflowStateUpdated' => "$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 */ function expandMagic(&$parser) { global $wgTitle, $wgJsMimeType; $parser->disableCache();
# Extend catlinks information to include workflows $this->extendCatlinks();
# Extract workflow info from args $name = 'Untitled'; $states = array(); $tmpl = false; $par = 'state'; foreach (func_get_args() as $arg) if (!is_object($arg)) { if (preg_match("%^(.+?)\\s*=\\s*(.+)$%", $arg, $match)) { list($arg, $k, $v) = $match; switch ($k) { case 'template' : $tmpl = $v; break; case 'parameter' : $par = $v; break; default: $states[$k] = $v; } } else $name = $arg; }
# Store the data for this workflow $this->workflowData[$name] = array('parameter' => $par, 'template' => $tmpl, 'states' => $states); $current = $this->getCurrentState($name);
# Build links for workflow table $edit = $wgTitle->userCan('edit'); $url = $wgTitle->getLocalUrl(); $anchor = $name; if (count($states) < 1) $anchor .= " (".wfMsg('workflowMissingStates').")"; $html = "<a href='$url' title='$anchor'>$anchor</a>";
# Render the states in their div elements with only current one visible 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 = array_search($current, array_keys($states))+1; foreach ($states as $state => $wikitext) { $data .= ",'$state'"; $style = $state == $current ? "" : "display:none"; $content = $parser->parse($wikitext, $wgTitle, $parser->mOptions, false, $state != $current)->getText(); $stitle = Title::newFromText($state, NS_CATEGORY); $surl = $stitle->getLocalUrl();
# Append the menu to the state
$html .= "$content | ||
<a href='$surl' title='{$this->cat}:$state'>$state</a> |
}
$html .= "<script type='$wgJsMimeType'>workflowData['$name']=[$data];</script>\n";
}
return array($html, 'isHTML' => true, 'noparse' => 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; if ($wgWorkflow->state) $wgWorkflow->updateCatLinks(); 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; }
/** * Update $wgOut's category links if state updated by ajax */ function updateCatLinks() { global $wgOut, $wgUser; $data =& $this->workflowData[$this->name]; $cats = join('|', array_keys($data['states'])); $tmp = array(); foreach ($wgOut->mCategoryLinks['normal'] as $i => $link) if (!preg_match("%>($cats)</a>%i", $link)) $tmp[] = $link; $title = Title::newFromText($this->state, NS_CATEGORY); $tmp[] = $wgUser->getSkin()->makeLinkObj($title, $title->getText()); $wgOut->mCategoryLinks['normal'] = $tmp; }
/** * Render the workflow info which appears in the catlinks area */ function renderWorkflowInfo(&$catlinks) { if (count($this->workflowData)) {
$table = "
";
foreach ($this->workflowData as $name => $data) { $current = $this->getCurrentState($name); $catlinks .= "$table$name: | ";
$sep = " "; foreach (array_keys($data['states']) as $state) { $title = Title::newFromText($state, NS_CATEGORY); $class = ($current == $state ? 'current' : ) . ($title->exists() ? : ' new'); $catlinks .= "$sep<a class='$class' href='{$title->getLocalURL()}'>{$title->getText()}</a>"; $sep = " → "; } $table = " | |
";
} $catlinks .= " |
\n";
} return $catlinks; }
/** * Extract the current state of the named workflow from the current article content */ function getCurrentState($name) { $data =& $this->workflowData[$name]; if (isset($data['current'])) $state = $data['current']; else {
# Get the content of the current article global $wgTitle; $article = new Article($wgTitle); $text = $article->getContent(); $cats = join('|', array_keys($data['states']));
# Template specified, extract state from template parameter if ($data['template']) { $tmpl = $data['template']; $par = $data['parameter']; if (preg_match("%\s*\{\{$tmpl.+?\|\s*$par\s*=\s*(\w+)%s", $text, $m)) $state = $m[1]; }
# No template specified, extract state from category links elseif (preg_match("%\s*\[\[{$this->cat}:($cats)\]\]\s*%", $text, $m)) $state = $m[1];
# Otherwise default to first defined state else { $state = array_keys($data['states']); $state = count($state) ? $state[0] : 'Error: No states defined!'; } } return $state; }
/** * Update the current state of a workflow item in the requested article * - called from OutputPageBeforeHTML if $this->state was set by bypassAjaxDispatcher() */ function updateState() { $cat = $this->cat; $name = $this->name; $state = $this->state; $data = &$this->workflowData[$name]; $cats = join('|', array_keys($data['states'])); $tmpl = $data['template']; $par = $data['parameter']; $data['current'] = $state;
# Get current article content global $wgTitle; $article = new Article($wgTitle); $text = $article->getContent();
# Update the state in the article text (as template parameter or direct cat links) $text = $tmpl ? preg_replace("%(\s*\{\{$tmpl.+?\|\s*$par\s*=\s*)\w+%s", "$1$state", $text) : preg_replace("%\s*\[\[{$cat}:($cats)\]\]\s*%i", "", $text) . "$cat:$state";
# Update the article with the new text $article->doEdit($text, wfMsg('workflowStateUpdated', $this->name, $this->state), EDIT_UPDATE); }
/** * Return just the catlinks to the client after updating a tag state * - this also calls updateState() if $this->state set by bypassAjaxDispatcher() */ function onOutputPageBeforeHTML(&$out) { global $wgUser; if ($this->state) $this->updateState(); $skin = $wgUser->getSkin(); $catlinks = is_object($skin) ? $skin->getCategories() : "Error: no skin!"; $out->disable(); wfResetOutputBuffers(); header("Cache-Control: no-cache, must-revalidate"); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 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('workflow_ajax_callback',[wgPageName,name,state],document.getElementById('catlinks')); } function workflowSwitchState(name,dir) { clearTimeout(workflowUpdate); var id = 'workflow-'+name+'-'+workflowData[name][workflowData[name][0]]; document.getElementById(id).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]; id = 'workflow-'+name+'-'+workflowData[name][state]; document.getElementById(id).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 $this->state and $this->name so that OutputPageBeforeHTML calls updateState() * - the ajax dispatcher id bypassed by changing the action to 'view' */ function bypassAjaxDispatcher() { global $wgUseAjax, $wgHooks, $wgAjaxExportList; if ($wgUseAjax && isset($_GET['action']) && $_GET['action'] == 'ajax' && isset($_GET['rs']) && $_GET['rs'] == 'workflow_ajax_callback' && isset($_GET['rsargs']) && is_array($_GET['rsargs']) ) { list($title, $this->name, $this->state) = $_GET['rsargs']; $wgHooks['OutputPageBeforeHTML'][] = $this; $_GET = $_REQUEST = array('title' => $title, 'action' => 'view'); $wgAjaxExportList[] = 'workflow_ajax_callback'; } }
/** * Needed in some versions to prevent Special:Version from breaking */ function __toString() { return __CLASS__; }
/** * Set up the magic words */ function languageGetMagic(&$magicWords, $langCode = 0) { $magicWords[$this->magic] = array($langCode, $this->magic); return true; } }
$wgWorkflow = new Workflow();