r56585 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r56584‎ | r56585 | r56586 >
Date:07:26, 18 September 2009
Author:malvineous
Status:deferred
Tags:
Comment:
[Extension:MassEditRegex] Import new extension into SVN
Modified paths:
  • /trunk/extensions/MassEditRegex (added) (history)
  • /trunk/extensions/MassEditRegex/MassEditRegex.alias.php (added) (history)
  • /trunk/extensions/MassEditRegex/MassEditRegex.class.php (added) (history)
  • /trunk/extensions/MassEditRegex/MassEditRegex.i18n.php (added) (history)
  • /trunk/extensions/MassEditRegex/MassEditRegex.php (added) (history)
  • /trunk/extensions/MassEditRegex/README (added) (history)

Diff [purge]

Index: trunk/extensions/MassEditRegex/MassEditRegex.alias.php
@@ -0,0 +1,13 @@
 2+<?php
 3+/**
 4+ * Aliases for special pages
 5+ *
 6+ * @addtogroup Extensions
 7+ */
 8+
 9+$aliases = array();
 10+
 11+/** English */
 12+$aliases['en'] = array(
 13+ 'MassEditRegex' => array('MassEditRegex'),
 14+);
Index: trunk/extensions/MassEditRegex/MassEditRegex.i18n.php
@@ -0,0 +1,38 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for MassEditRegex extension
 5+ *
 6+ * @addtogroup Extensions
 7+ */
 8+
 9+$messages = array();
 10+
 11+/** English
 12+ * @author Adam Nielsen
 13+ */
 14+$messages['en'] = array(
 15+ 'masseditregex' => 'Mass Edit using Regular Expressions',
 16+ 'masseditregex-desc' => 'Use regular expressions to [[Special:MassEditRegex|edit many pages in one operation]]',
 17+ 'masseditregextext' => 'Enter one or more regular expressions (one per line) for matching, and one or more expressions to replace each match with. The first match-expression, if successful, will be replaced with the first replace-expression, and so on. See the PHP function preg_replace() for details.',
 18+ 'pagelisttxt' => 'Pages to edit:',
 19+ 'matchtxt' => 'Search for:',
 20+ 'replacetxt' => 'Replace with:',
 21+ 'executebtn' => 'Execute',
 22+ 'err-nopages' => 'You must specify at least one page to change.',
 23+
 24+ 'before' => 'Before',
 25+ 'after' => 'After',
 26+ 'max-preview-diffs' => 'Preview has been limited to the first $1 matches.',
 27+
 28+ 'num-changes' => 'changes', // e.g. "5 changes", can't use $1 or it'll be too slow
 29+ 'num-articles-changed' => '$1 articles edited',
 30+ 'view-full-summary' => 'View full edit summary',
 31+
 32+ 'hint-intro' => 'Here are some hints and examples for accomplishing common tasks:',
 33+ 'hint-headmatch' => 'Match',
 34+ 'hint-headreplace' => 'Replace',
 35+ 'hint-headeffect' => 'Effect',
 36+ 'hint-toappend' => 'Append some text to the end of the article - great for adding pages to categories',
 37+ 'hint-remove' => 'Remove some text from all the pages in the list',
 38+ 'hint-removecat' => 'Remove all categories from an article (note the escaping of the square brackets in the wikicode.) The replacement values should not be escaped.'
 39+);
Index: trunk/extensions/MassEditRegex/MassEditRegex.php
@@ -0,0 +1,32 @@
 2+<?php
 3+if ( ! defined( 'MEDIAWIKI' ) )
 4+ die();
 5+/**
 6+ * Allow users in the Bot group to edit many articles in one go by applying
 7+ * regular expressions to a list of pages.
 8+ *
 9+ * @addtogroup Extensions
 10+ *
 11+ * @link http://www.mediawiki.org/wiki/Extension:MassEditRegex Documentation
 12+ *
 13+ * @author Adam Nielsen <malvineous@shikadi.net>
 14+ * @copyright Copyright © 2009 Adam Nielsen
 15+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 16+ */
 17+
 18+$wgExtensionCredits['specialpage'][] = array(
 19+ 'path' => __FILE__,
 20+ 'name' => 'Mass Edit via Regular Expressions',
 21+ 'version' => 'r2',
 22+ 'author' => 'Adam Nielsen',
 23+ 'url' => 'http://www.mediawiki.org/wiki/Extension:MassEditRegex',
 24+ 'description' => 'Use regular expressions to [[Special:MassEditRegex|edit many pages in one operation]]',
 25+ 'descriptionmsg' => 'masseditregex-desc'
 26+);
 27+
 28+$dir = dirname(__FILE__) . '/';
 29+$wgExtensionMessagesFiles['MassEditRegex'] = $dir . 'MassEditRegex.i18n.php';
 30+$wgExtensionAliasesFiles['MassEditRegex'] = $dir . 'MassEditRegex.alias.php';
 31+$wgAutoloadClasses['MassEditRegex'] = $dir . 'MassEditRegex.class.php';
 32+$wgSpecialPages['MassEditRegex'] = 'MassEditRegex';
 33+$wgSpecialPageGroups['MassEditRegex'] = 'pagetools';
Index: trunk/extensions/MassEditRegex/README
@@ -0,0 +1,19 @@
 2+== MassEditRegex ==
 3+Copyright © 2009 Adam Nielsen <malvineous@shikadi.net>
 4+GNU General Public License 2.0 or later
 5+http://www.gnu.org/copyleft/gpl.html
 6+
 7+Edit a list of pages in a single operation by applying a regular expression
 8+to each page's content.
 9+
 10+See http://www.mediawiki.org/wiki/Extension:MassEditRegex for full instructions.
 11+
 12+Briefly:
 13+
 14+ 1. Add to LocalSettings.php:
 15+
 16+ include_once("$IP/extensions/MassEditRegex/MassEditRegex.php");
 17+
 18+ 2. Go to [[Special:MassEditRegex]]
 19+
 20+ 3. If you don't have access, go to [[Special:User rights management]] and add yourself to the Bot group.
Index: trunk/extensions/MassEditRegex/MassEditRegex.class.php
@@ -0,0 +1,327 @@
 2+<?php
 3+if ( ! defined( 'MEDIAWIKI' ) )
 4+ die();
 5+/**
 6+ * Allow users in the Bot group to edit many articles in one go by applying
 7+ * regular expressions to a list of pages.
 8+ *
 9+ * @addtogroup SpecialPage
 10+ *
 11+ * @link http://www.mediawiki.org/wiki/Extension:MassEditRegex Documentation
 12+ *
 13+ * @author Adam Nielsen <malvineous@shikadi.net>
 14+ * @copyright Copyright © 2009 Adam Nielsen
 15+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 16+ */
 17+
 18+// Maximum number of pages/diffs to display when previewing the changes
 19+define('MER_MAX_PREVIEW_DIFFS', 10);
 20+
 21+/** Main class that define a new special page*/
 22+class MassEditRegex extends SpecialPage {
 23+
 24+ function MassEditRegex() {
 25+ SpecialPage::SpecialPage('MassEditRegex', 'bot');
 26+ }
 27+
 28+ function execute( $par ) {
 29+ global $wgAllowSysopQueries, $wgUser, $wgRequest, $wgOut;
 30+ wfLoadExtensionMessages('MassEditRegex');
 31+
 32+ if (!$wgUser->isBot()) {
 33+ $wgOut->permissionRequired('bot');
 34+ return;
 35+ }
 36+
 37+ if ($wgRequest->wasPosted()) {
 38+ $f = new MassEditRegexForm(
 39+ $wgRequest->getText('wpPageList'),
 40+ $wgRequest->getText('wpMatch'),
 41+ $wgRequest->getText('wpReplace'),
 42+ $wgRequest->getText('wpSummary')
 43+ );
 44+ if ($wgRequest->getVal('wpPreviewBtn') !== NULL) {
 45+ $f->showPreview();
 46+ } else if ($wgRequest->getVal('wpExecuteBtn') !== NULL) {
 47+ $f->execute();
 48+ }
 49+ } else {
 50+ $f = new MassEditRegexForm();
 51+ $f->showForm();
 52+ $f->showHints();
 53+ }
 54+
 55+ }
 56+}
 57+
 58+/**
 59+ * @access private
 60+ * @addtogroup SpecialPage
 61+ */
 62+
 63+class MassEditRegexForm {
 64+ private $aPageList;
 65+ private $aMatch;
 66+ private $aReplace;
 67+ private $strReplace; // keep to avoid having to re-escape again
 68+ private $strSummary;
 69+ private $sk;
 70+
 71+ function MassEditRegexForm(
 72+ $strPageList = 'Sandbox',
 73+ $strMatch = '/hello (.*)\n/', // defaults
 74+ $strReplace = 'goodbye \1',
 75+ $strSummary = ''
 76+ ) {
 77+ global $wgOut, $wgUser;
 78+ $this->aPageList = split("\n", trim($strPageList));
 79+ //print_r($this->aPages);
 80+ //if (count($this->aPages) == 0) $this->aPages[0] = $this->aPages;
 81+ $this->aMatch = split("\n", trim($strMatch));
 82+ $this->strReplace = $strReplace;
 83+ $this->aReplace = split("\n", $strReplace);
 84+ $this->strSummary = $strSummary;
 85+
 86+ $wgOut->setPagetitle(wfMsg('masseditregex'));
 87+
 88+ $this->sk = $wgUser->getSkin();
 89+
 90+ // Replace \n in the match with an actual newline (since a newline can't
 91+ // be typed in, it'll act as the splitter for the next regex)
 92+ foreach ($this->aReplace as &$str) {
 93+ // Convert \n into a newline, \\n into \n, \\\n into \<newline>, etc.
 94+ $str = preg_replace(array(
 95+ '/(^|[^\\\\])((\\\\)*)(\2)\\\\n/',
 96+ '/(^|[^\\\\])((\\\\)*)(\2)n/'
 97+ ), array(
 98+ "\\1\\2\n",
 99+ "\\1\\2n"
 100+ ), $str);
 101+ }
 102+ }
 103+
 104+ function showForm($err = '') {
 105+ global $wgOut, $wgUser, $wgLang;
 106+ global $wgLogQueries;
 107+
 108+ if ($err) {
 109+ $wgOut->addHTML('<div class="wikierror">' . htmlspecialchars($err) . '</div>');
 110+ }
 111+
 112+ $wgOut->addWikiText(wfMsg('masseditregextext'));
 113+
 114+ $txtPageList = wfMsg('pagelisttxt');
 115+ $txtMatch = wfMsg('matchtxt');
 116+ $txtReplace = wfMsg('replacetxt');
 117+ $txtPreviewBtn = wfMsg('showpreview');
 118+ $txtExecuteBtn = wfMsg('executebtn');
 119+
 120+ $txtEditSummary = wfMsg('summary');
 121+ $txtSummaryPreview = wfMsg('summary-preview');
 122+
 123+ $titleObj = Title::makeTitle(NS_SPECIAL, 'MassEditRegex');
 124+ $action = $titleObj->escapeLocalURL('action=submit');
 125+
 126+ $htmlPageList = htmlspecialchars(join("\n", $this->aPageList));
 127+ $htmlMatch = htmlspecialchars(join("\n", $this->aMatch));
 128+ $htmlReplace = htmlspecialchars($this->strReplace); // use original value
 129+ $htmlSummary = htmlspecialchars($this->strSummary);
 130+ $htmlSummaryPreview = $this->sk->commentBlock($this->strSummary, $titleObj);
 131+
 132+ $mainForm = <<<ENDFORM
 133+<form id="masseditregex" method="post" action="{$action}">
 134+<p>{$txtPageList}</p>
 135+<!-- Newlines are important here - one after <textarea> but none
 136+ before </textarea>, otherwise leading blank lines get cut
 137+ off, or trailing newlines get added! Tested FF3 -->
 138+<textarea name="wpPageList" cols="80" rows="4" tabindex="1" style="width:100%;">
 139+{$htmlPageList}</textarea>
 140+
 141+<table border="0" cellspacing="0" cellpadding="0" style="width: 100%;">
 142+<tr><td>
 143+<p>{$txtMatch}</p>
 144+<textarea name="wpMatch" cols="80" rows="4" tabindex="1" style="width:95%;">
 145+{$htmlMatch}</textarea>
 146+</td><td>
 147+<p>{$txtReplace}</p>
 148+<textarea name="wpReplace" cols="80" rows="4" tabindex="1" style="width:100%;">
 149+{$htmlReplace}</textarea>
 150+</td></tr>
 151+</table>
 152+<p></p>
 153+<div class="editOptions">
 154+<span id="wpSummaryLabel"><label for="wpSummary">{$txtEditSummary}</label></span>
 155+<input type="text" value="$htmlSummary" name="wpSummary" id="wpSummary"
 156+maxlength="200" size="60" /><br />
 157+
 158+<div class="mw-summary-preview">
 159+$txtSummaryPreview
 160+$htmlSummaryPreview
 161+</div>
 162+</div>
 163+
 164+<p>
 165+ <input type="submit" name="wpPreviewBtn" value="{$txtPreviewBtn}">
 166+ <input type="submit" name="wpExecuteBtn" value="{$txtExecuteBtn}">
 167+</p>
 168+</form>
 169+ENDFORM;
 170+ $wgOut->addHTML($mainForm);
 171+ return;
 172+ }
 173+
 174+ function showHints()
 175+ {
 176+ global $wgOut;
 177+ $hintIntro = wfMsg('hint-intro');
 178+ $hintMatch = wfMsg('hint-headmatch');
 179+ $hintReplace = wfMsg('hint-headreplace');
 180+ $hintEffect = wfMsg('hint-headeffect');
 181+ $hintToAppend = wfMsg('hint-toappend');
 182+ $hintRemove = wfMsg('hint-remove');
 183+ $hintRemoveCat = wfMsg('hint-removecat');
 184+
 185+ $htmlHints = <<<ENDHINTS
 186+<p>{$hintIntro}</p>
 187+<table border="1" cellspacing="0" cellpadding="2" class="wikitable">
 188+<thead><tr>
 189+ <th style="width: 12em;">{$hintMatch}</th>
 190+ <th style="width: 12em;">{$hintReplace}</th>
 191+ <th>{$hintEffect}</th>
 192+</tr></thead>
 193+<tbody>
 194+ <tr>
 195+ <td>/$/<br/>/$/</td><td>abc<br/>\\n[[Category:New]]</td><td>{$hintToAppend}</td>
 196+ </tr><tr>
 197+ <td>{{OldTemplate}}</td><td></td><td>{$hintRemove}</td>
 198+ </tr><tr>
 199+ <td>\\[\\[Category:[^]]+\]\]</td><td></td><td>{$hintRemoveCat}</td>
 200+ </tr>
 201+</tbody>
 202+</table>
 203+ENDHINTS;
 204+ $wgOut->addHTML($htmlHints);
 205+
 206+ return;
 207+ }
 208+
 209+ function showPreview()
 210+ {
 211+ $this->execute(false);
 212+ return;
 213+ }
 214+
 215+ function getPages()
 216+ {
 217+ if (sizeof($this->aPageList) == 0) return NULL;
 218+ $req = new FauxRequest(array(
 219+ 'action' => 'query',
 220+ 'titles' => join('|', $this->aPageList),
 221+ 'prop' => 'info|revisions',
 222+ 'intoken' => 'edit',
 223+ 'rvprop' => 'content',
 224+ //'rvlimit' => 1 // most recent revision only
 225+ ), false);
 226+ $processor = new ApiMain($req, true);
 227+ $processor->execute();
 228+ $aPages = $processor->getResultData();
 229+ if (empty($aPages)) return NULL; // no pages match the titles given
 230+ return $aPages['query']['pages'];
 231+ }
 232+
 233+ function execute($bPerformEdits = true)
 234+ {
 235+ global $wgOut, $wgUser;
 236+ global $wgRequest, $wgTitle;
 237+
 238+ $aPages = $this->getPages();
 239+ if ($aPages === NULL) {
 240+ $this->showForm(wfMsg('err-nopages'));
 241+ return;
 242+ }
 243+
 244+ // Show the form again ready for further editing if we're just previewing
 245+ if (!$bPerformEdits) $this->showForm();
 246+
 247+ $diff = new DifferenceEngine();
 248+ $diff->showDiffStyle(); // send CSS link to the browser for diff colours
 249+
 250+ $strChanges = wfMsg('num-changes');
 251+
 252+ if ($bPerformEdits) $wgOut->addHTML('<ul>');
 253+
 254+ // Save the state until the MW Edit API does it for us
 255+ if ($bPerformEdits) {
 256+ $o_wgOut = clone $wgOut; // need to do a deep copy here
 257+ $wgOut->disable(); // not strictly necessary, but might speed things up
 258+ $o_wgTitle = $wgTitle;
 259+ }
 260+
 261+ $iArticleCount = 0;
 262+ foreach ($aPages as $p) {
 263+ $iArticleCount++;
 264+ if (!isset($p['revisions'])) {
 265+ if ($bPerformEdits) {
 266+ $o_wgOut->addHTML('<li> ' . $p['title'] . ' does not exist</li>');
 267+ } else {
 268+ $wgOut->addHTML('<p>' . $p['title'] . ' does not exist</p>');
 269+ }
 270+ continue; // empty page
 271+ }
 272+ $curContent = $p['revisions'][0]['*'];
 273+ $iCount = 0;
 274+ $newContent = @preg_replace($this->aMatch, $this->aReplace, $curContent, -1, $iCount);
 275+
 276+ if ($bPerformEdits) {
 277+ // Not in preview mode, make the edits
 278+ //print_r($p);
 279+ $o_wgOut->addHTML('<li> ' . $p['title'] . ': ' . $iCount . ' ' . $strChanges . '</li>');
 280+ $req = new FauxRequest(array(
 281+ 'action' => 'edit',
 282+ 'bot' => true,
 283+ 'token' => $p['edittoken'],
 284+ 'title' => $p['title'],
 285+ 'summary' => $this->strSummary,
 286+ 'text' => $newContent,
 287+ 'basetimestamp' => $p['starttimestamp']
 288+ ), true);
 289+ $processor = new ApiMain($req, true);
 290+ try {
 291+ $processor->execute();
 292+ } catch (UsageException $e) {
 293+ $o_wgOut->addHTML('<ul><li>Edit failed: ' . $e . '</li></ul>');
 294+ }
 295+ } else {
 296+ // In preview mode, display the first few diffs
 297+ $diff->setText($curContent, $newContent);
 298+ $dtxt = $diff->getDiff('<b>' . $p['title'] . ' - ' . wfMsg('before') . '</b>',
 299+ '<b>' . wfMsg('after') . '</b>');
 300+ $wgOut->addHTML($dtxt);
 301+
 302+ if ($iArticleCount >= MER_MAX_PREVIEW_DIFFS) {
 303+ $wgOut->addHTML('<p>' . wfMsg('max-preview-diffs', MER_MAX_PREVIEW_DIFFS) . '</p>');
 304+ break;
 305+ }
 306+ }
 307+
 308+ }
 309+ // Restore the state after the Edit API has messed with it
 310+ if ($bPerformEdits) {
 311+ $wgTitle = $o_wgTitle;
 312+ $wgOut = $o_wgOut;
 313+ }
 314+
 315+ if ($bPerformEdits) {
 316+ $wgOut->addHTML('</ul><p>' . wfMsg('num-articles-changed', $iArticleCount)
 317+ . '</p>' . $this->sk->makeKnownLinkObj(
 318+ SpecialPage::getSafeTitleFor('Contributions', $wgUser->getName()),
 319+ wfMsg('view-full-summary')
 320+ )
 321+ );
 322+ }
 323+
 324+ return;
 325+ }
 326+
 327+}
 328+

Status & tagging log