Malvineous has submitted this change and it was merged.

Change subject: Added support for execution to be done on client side. This to 
prevent max execution time error in php.
......................................................................


Added support for execution to be done on client side.
This to prevent max execution time error in php.

Added a checkbox, for selecting client side execution, is by default not 
checked.

Added api for editing pages (used in js)

refactored old code, added a class for handling the edit functionality so it can
be reused by other components (the api)

Fix formatting and restore some behaviour lost

Rebase onto master

Fix jslint warnings

Change-Id: I0946f14eda03d7435151a0860dcb2d0fb380732b
---
A MassEditRegex.api.php
M MassEditRegex.class.php
M MassEditRegex.i18n.php
A MassEditRegex.js
M MassEditRegex.php
A MassEditRegex.special.php
M README
M i18n/en.json
M i18n/qqq.json
9 files changed, 1,141 insertions(+), 569 deletions(-)

Approvals:
  Malvineous: Verified; Looks good to me, approved



diff --git a/MassEditRegex.api.php b/MassEditRegex.api.php
new file mode 100644
index 0000000..a57507a
--- /dev/null
+++ b/MassEditRegex.api.php
@@ -0,0 +1,33 @@
+<?php
+class MassEditRegexAPI {
+       public static function edit( $pageid, $search, $replace, $summary ) {
+               $massEditRegex = new MassEditRegex( $search, $replace, $summary 
);
+
+               $user = User::newFromSession();
+
+               // Check permissions
+               if ( !$user->isAllowed( 'masseditregex' ) ) {
+                       return json_encode( array(
+                               'error' => 
mfMessage('masseditregex-noaccess')->text()
+                       ) );
+               }
+
+               // Show a message if the database is in read-only mode
+               if ( wfReadOnly() ) {
+                       return json_encode( array(
+                               'error' => 
mfMessage('masseditregex-readonlydb')->text()
+                       ) );
+               }
+
+               // If user is blocked, s/he doesn't need to access this page
+               if ( $user->isBlocked() ) {
+                       return json_encode( array(
+                               'error' => 
mfMessage('masseditregex-blocked')->text()
+                       ) );
+               }
+
+               return json_encode(array(
+                       'changes' => 
$massEditRegex->editPage(Title::newFromID($pageid))
+               ) );
+       }
+}
\ No newline at end of file
diff --git a/MassEditRegex.class.php b/MassEditRegex.class.php
index 11296bb..2e2d4a4 100644
--- a/MassEditRegex.class.php
+++ b/MassEditRegex.class.php
@@ -1,560 +1,235 @@
 <?php
-if ( ! defined( 'MEDIAWIKI' ) )
-       die();
-/**
- * Allow users in the Bot group to edit many articles in one go by applying
- * regular expressions to a list of pages.
- *
- * @file
- * @ingroup SpecialPage
- *
- * @link http://www.mediawiki.org/wiki/Extension:MassEditRegex Documentation
- *
- * @author Adam Nielsen <[email protected]>
- * @copyright Copyright © 2009,2013 Adam Nielsen
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
- */
 
-/// Maximum number of pages/diffs to display when previewing the changes
-define('MER_MAX_PREVIEW_DIFFS', 20);
+class MassEditRegex {
+       /**
+        * @var array
+        */
+       private $search;
 
-/// Maximum number of pages to edit.
-define('MER_MAX_EXECUTE_PAGES', 1000);
+       /**
+        * @var array
+        */
+       private $replace;
 
-/** Main class that define a new special page*/
-class MassEditRegex extends SpecialPage {
-       private $aPageList;       ///< Array of string - user-supplied page 
titles
-       private $strPageListType; ///< Type of titles (categories, backlinks, 
etc.)
-       private $strMatch;        ///< Match regex from form
-       private $strReplace;      ///< Substitution regex from form
-       private $aMatch;          ///< $strMatch exploded into array
-       private $aReplace;        ///< $strReplace exploded into array
-       private $strSummary;      ///< Edit summary
-       private $diff;            ///< Access to diff engine
+       /**
+        * @var string
+        */
+       private $summary;
 
-       function __construct() {
-               parent::__construct( 'MassEditRegex', 'masseditregex' );
+       /**
+        * @var \DifferenceEngine
+        */
+       private $diffEngine;
+
+       /**
+        * @var \User
+        */
+       private $user;
+
+       /**
+        * @param $search string newline delimited string of search regexes
+        * @param $replace string newline delimited string of replacements
+        * @param $summary string
+        * @param User $user
+        */
+       function __construct( $search, $replace, $summary, \User $user = null ) 
{
+               $this->setReplace( $replace );
+               $this->setSearch( $search );
+               $this->setSummary( $summary );
+               $this->setUser( $user );
+
+               $this->diffEngine = new DifferenceEngine();
        }
 
-       /// Run the regexes.
-       function execute( $par ) {
-               global $wgUser;
+       /**
+        * Apply the list of regex expressions to a single page
+        *
+        * @param Title $title
+        *   Page to alter
+        *
+        * @return number of changes performed on given title
+        */
+       public function editPage( \Title $title ) {
+               $article = new Article( $title );
+               $rev = $this->getRevision( $title );
+               $content = $this->getContent( $rev );
+               $curText = $content->getNativeData();
+               list( $newText, $changes ) = $this->replaceText( $curText );
 
-               $wgOut = $this->getOutput();
-
-               $this->setHeaders();
-
-               // Check permissions
-               if ( !$wgUser->isAllowed( 'masseditregex' ) ) {
-                       $this->displayRestrictionError();
-                       return;
+               if ( strcmp( $curText, $newText ) != 0 ) {
+                       $newContent = new WikitextContent( $newText );
+                       $article->doEditContent( $newContent, $this->summary,
+                               EDIT_UPDATE | EDIT_FORCE_BOT | 
EDIT_DEFER_UPDATES,
+                               $rev->getId() );
                }
 
-               // Show a message if the database is in read-only mode
-               if ( wfReadOnly() ) {
-                       $wgOut->readOnlyPage();
-                       return;
+               return $changes;
+       }
+
+       /**
+        * Apply the list of regex expressions to a single page for preview
+        *
+        * @param Title $title
+        *   Page to preview
+        *
+        * @return string html diff
+        */
+       public function previewPage( \Title $title ) {
+               $rev = $this->getRevision( $title );
+               $content = $this->getContent( $rev );
+               $curText = $content->getNativeData();
+               list( $newText ) = $this->replaceText( $curText );
+
+               $this->diffEngine->setText( $curText, $newText );
+
+               return $this->diffEngine->getDiff( '<b>'
+                       . htmlspecialchars( $title->getPrefixedText() ) . ' - '
+                       . wfMessage( 'masseditregex-before' )->text() . '</b>',
+                       '<b>' . wfMessage( 'masseditregex-after' )->text() . 
'</b>' );
+       }
+
+       /**
+        * Main regex transform function.
+        *
+        * @param $input
+        * @return mixed
+        * @throws UsageException
+        */
+       private function replaceText( $input ) {
+               $changes = $iCount = 0;
+               foreach ( $this->search as $i => $strMatch ) {
+                       $strNextReplace = $this->replace[ $i ];
+                       $result = @preg_replace_callback($strMatch,
+                               function ( $aMatches ) use ( $strNextReplace ) {
+                                       foreach ( $aMatches as $i => $strMatch 
) {
+                                               $aFind[ ] = '$' . $i;
+                                               $aReplace[ ] = $strMatch;
+                                       }
+                                       return str_replace( $aFind, $aReplace, 
$strNextReplace );
+                               }, $input, -1, $iCount );
+                       $changes += $iCount;
+                       if ( $result !== null ) {
+                               $input = $result;
+                       } else {
+                               throw new UsageException( wfMessage( 
'masseditregex-badregex' )->text()
+                                       . ' <b>' . htmlspecialchars( $strMatch 
) . '</b>',
+                                       'masseditregex-badregex' );
+                       }
                }
+               return array( $input, $changes );
+       }
 
-               // If user is blocked, s/he doesn't need to access this page
-               if ( $wgUser->isBlocked() ) {
-                       $wgOut->blockedPage();
-                       return;
+       /**
+        * @param Title $title
+        * @return Revision
+        * @throws BadTitleError
+        */
+       private function getRevision( \Title $title ) {
+               $rev = Revision::newFromTitle( $title, 0, Revision::READ_LATEST 
);
+               if ( !$rev ) {
+                       throw new \BadTitleError( 
wfMessage('masseditregex-norevisions') );
                }
+               return $rev;
+       }
 
-               $this->outputHeader();
+       /**
+        * @param $rev
+        * @return mixed
+        * @throws PermissionsError
+        */
 
-               $wgRequest = $this->getRequest();
-               $strPageList = $wgRequest->getText( 'wpPageList', 'Sandbox' );
-               $this->aPageList = explode("\n", trim($strPageList));
-               $this->strPageListType = $wgRequest->getText( 'wpPageListType', 
'pagenames' );
+       private function getContent( $rev ) {
+               $content = $rev->getContent( Revision::FOR_THIS_USER, 
$this->user );
+               if ( !$content ) {
+                       throw new \PermissionsError( 
wfMessage('masseditregex-noaccess' )->text());
+               }
+               return $content;
+       }
 
-               $this->iNamespace = $wgRequest->getInt( 'namespace', NS_MAIN );
+       /**
+        * @return array
+        */
+       public function getReplace() {
+               return $this->replace;
+       }
 
-               $this->strMatch = $wgRequest->getText( 'wpMatch', '/hello 
(.*)\n/' );
-               $this->aMatch = explode("\n", trim($this->strMatch));
+       /**
+        * @return array
+        */
+       public function getSearch() {
+               return $this->search;
+       }
 
-               $this->strReplace = $wgRequest->getText( 'wpReplace', 'goodbye 
$1' );
-               $this->aReplace = explode("\n", $this->strReplace);
+       /**
+        * @return string
+        */
+       public function getSummary() {
+               return $this->summary;
+       }
 
-               $this->strSummary = $wgRequest->getText( 'wpSummary', '' );
+       /**
+        * @return \User
+        */
+       public function getUser() {
+               return $this->user;
+       }
 
-               // Replace \n in the match with an actual newline (since a 
newline can't
-               // be typed in, it'll act as the splitter for the next regex)
-               foreach ( $this->aReplace as &$str ) {
+       /**
+        * @param \DifferenceEngine $diffEngine
+        */
+       public function setDiffEngine( $diffEngine ) {
+               $this->diffEngine = $diffEngine;
+       }
+
+       /**
+        * @return \DifferenceEngine
+        */
+       public function getDiffEngine() {
+               return $this->diffEngine;
+       }
+
+       /**
+        * @param \User $user
+        */
+       public function setUser( $user = null ) {
+               if ( is_null( $user ) ){
+                       $user = User::newFromSession();
+               }
+               $this->user = $user;
+       }
+
+       /**
+        * @param string $replace
+        */
+       public function setReplace( $replace ) {
+               $replace = explode( "\n", $replace );
+
+               foreach ( $replace as &$str ) {
                        // Convert \n into a newline, \\n into \n, \\\n into 
\<newline>, etc.
                        $str = preg_replace(
                                array(
                                        '/(^|[^\\\\])((\\\\)*)(\2)\\\\n/',
                                        '/(^|[^\\\\])((\\\\)*)(\2)n/'
                                ), array(
-                                       "\\1\\2\n",
-                                       "\\1\\2n"
-                               ), $str);
+                               "\\1\\2\n",
+                               "\\1\\2n"
+                       ), $str);
                }
 
-               if ( $wgRequest->wasPosted() ) {
-                       $this->perform( !$wgRequest->getCheck('wpSave') );
-               } else {
-                       $this->showForm();
-                       $this->showHints();
-               }
-
+               $this->replace = $replace;
        }
 
-       /// Display the form requesting the regexes from the user.
-       function showForm() {
-               $wgOut = $this->getOutput();
-
-               $wgOut->addWikiMsg( 'masseditregextext' );
-               $titleObj = SpecialPage::getTitleFor( 'MassEditRegex' );
-
-               $wgOut->addHTML(
-                       Xml::openElement('form', array(
-                               'id' => 'masseditregex',
-                               'method' => 'post',
-                               'action' => 
$titleObj->getLocalURL('action=submit')
-                       )) .
-                       Xml::element('p',
-                               null, $this->msg( 'masseditregex-pagelisttxt' 
)->text()
-                       ) .
-                       Xml::textarea(
-                               'wpPageList',
-                               join( "\n", $this->aPageList )
-                       ) .
-                       Xml::element('span',
-                               null, $this->msg( 
'masseditregex-listtype-intro' )->text()
-                       ) .
-                       Xml::openElement('ul', array(
-                               'style' => 'list-style: none' // don't want any 
bullets for radio btns
-                       ))
-               );
-
-               // Generate HTML for the radio buttons (one for each list type)
-               foreach (array('pagenames', 'pagename-prefixes', 'categories', 
'backlinks')
-                       as $strValue)
-               {
-                       // Have to use openElement because putting an Xml::xxx 
return value
-                       // inside an Xml::element causes the HTML code to be 
escaped and appear
-                       // on the page.
-                       $wgOut->addHTML(
-                               Xml::openElement('li') .
-                               // Give grep a chance to find the usages:
-                               // masseditregex-listtype-pagenames, 
masseditregex-listtype-pagename-prefixes,
-                               // masseditregex-listtype-categories, 
masseditregex-listtype-backlinks
-                               Xml::radioLabel(
-                                       $this->msg( 'masseditregex-listtype-' . 
$strValue )->text(),
-                                       'wpPageListType',
-                                       $strValue,
-                                       'masseditregex-radio-' . $strValue,
-                                       $strValue == $this->strPageListType
-                               ) .
-                               Xml::closeElement('li')
-                       );
-               }
-               $wgOut->addHTML(
-                       Xml::closeElement('ul') .
-
-                       // Display the textareas for the regex and replacement 
to go into
-
-                       // Can't use Xml::buildTable because we need to put 
code into the table
-                       Xml::openElement('table', array(
-                               'style' => 'width: 100%'
-                       )) .
-                               Xml::openElement('tr') .
-                                       Xml::openElement('td') .
-                                               Xml::element( 'p', null, 
$this->msg( 'masseditregex-matchtxt' )->text() ) .
-                                               Xml::textarea(
-                                                       'wpMatch',
-                                                       $this->strMatch  // use 
original value
-                                               ) .
-                                       Xml::closeElement('td') .
-                                       Xml::openElement('td') .
-                                               Xml::element( 'p', null, 
$this->msg( 'masseditregex-replacetxt' )->text() ) .
-                                               Xml::textarea(
-                                                       'wpReplace',
-                                                       $this->strReplace  // 
use original value
-                                               ) .
-                                       Xml::closeElement('td') .
-                                       Xml::closeElement('tr') .
-                       Xml::closeElement('table') .
-
-                       Xml::openElement( 'div', array( 'class' => 
'editOptions' ) ) .
-
-                       // Display the edit summary and preview
-
-                       Xml::tags( 'span',
-                               array(
-                                       'class' => 'mw-summary',
-                                       'id' => 'wpSummaryLabel'
-                               ),
-                               Xml::tags( 'label', array(
-                                       'for' => 'wpSummary'
-                               ), $this->msg( 'summary' )->escaped() )
-                       ) . ' ' .
-
-                       Xml::input( 'wpSummary',
-                               60,
-                               $this->strSummary,
-                               array(
-                                       'id' => 'wpSummary',
-                                       'maxlength' => '200',
-                                       'tabindex' => '1'
-                               )
-                       ) .
-
-                       Xml::tags( 'div',
-                               array( 'class' => 'mw-summary-preview' ),
-                               $this->msg( 'summary-preview' )->parse() .
-                                       Linker::commentBlock( $this->strSummary 
)
-                       ) .
-                       Xml::closeElement( 'div' ) . // class=editOptions
-
-                       // Display the preview + execute buttons
-
-                       Xml::element('input', array(
-                               'id'        => 'wpSave',
-                               'name'      => 'wpSave',
-                               'type'      => 'submit',
-                               'value'     => $this->msg( 
'masseditregex-executebtn' )->text(),
-                               'accesskey' => $this->msg( 'accesskey-save' 
)->text(),
-                               'title'     => $this->msg( 
'masseditregex-tooltip-execute' )->text() .
-                                       ' [' . $this->msg( 'accesskey-save' 
)->text() . ']'
-                       )) .
-
-                       Xml::element('input', array(
-                               'id'        => 'wpPreview',
-                               'name'      => 'wpPreview',
-                               'type'      => 'submit',
-                               'value'     => $this->msg( 'showpreview' 
)->text(),
-                               'accesskey' => $this->msg( 'accesskey-preview' 
)->text(),
-                               // @todo i18n-ize the brackets
-                               'title'     => $this->msg( 'tooltip-preview' 
)->text() . ' ['
-                                       . $this->msg( 'accesskey-preview' 
)->text() . ']'
-                       ))
-
-               );
-
-               $wgOut->addHTML( Xml::closeElement('form') );
-       }
-
-       /// Show a short table of regex examples.
-       function showHints() {
-               global $wgOut;
-
-               $wgOut->addHTML(
-                       Xml::element( 'p', null, $this->msg( 
'masseditregex-hint-intro' )->text() )
-               );
-               $wgOut->addHTML(Xml::buildTable(
-
-                       // Table rows (the hints)
-                       array(
-                               array(
-                                       '/$/',
-                                       'abc',
-                                       $this->msg( 
'masseditregex-hint-toappend' )->text()
-                               ),
-                               array(
-                                       '/$/',
-                                       '\\n[[Category:New]]',
-                                       // Since we can't pass "rowspan=2" to 
the hint text above, we'll
-                                       // have to display it again
-                                       $this->msg( 
'masseditregex-hint-toappend' )->text()
-                               ),
-                               array(
-                                       '/{{OldTemplate}}/',
-                                       '',
-                                       $this->msg( 'masseditregex-hint-remove' 
)->text()
-                               ),
-                               array(
-                                       '/\\[\\[Category:[^]]+\]\]/',
-                                       '',
-                                       $this->msg( 
'masseditregex-hint-removecat' )->text()
-                               ),
-                               array(
-                                       '/(\\[\\[[^]]*\\|[^]]*)AAA(.*\\]\\])/',
-                                       '$1BBB$2',
-                                       $this->msg( 
'masseditregex-hint-renamelink' )->text()
-                               ),
-                       ),
-
-                       // Table attributes
-                       array(
-                               'class' => 'wikitable'
-                       ),
-
-                       // Table headings
-                       array(
-                               $this->msg( 'masseditregex-hint-headmatch' 
)->text(), // really needs width 12em
-                               $this->msg( 'masseditregex-hint-headreplace' 
)->text(), // really needs width 12em
-                               $this->msg( 'masseditregex-hint-headeffect' 
)->text()
-                       )
-
-               )); // Xml::buildTable
-
-       }
-
-       /// Apply all the regexes to a single page.
        /**
-        * @param Title $title
-        *   Page to alter (or preview.)
-        *
-        * @param bool $isPreview
-        *   true to generate a diff, false to alter the page content.
-        *
-        * @param string $htmlDiff
-        *   On return, contains HTML for the diff, if $isPreview was true.
-        *
-        * @return true on success, false if the page could not be found.
-        *
-        * @throw UsageException if the regex was invalid.
+        * @param string $search
         */
-       function editPage( $title, $isPreview, &$htmlDiff ) {
-               global $wgOut, $wgLang, $wgUser;
-
-               $article = new Article($title);
-               $rev = Revision::newFromTitle($title, 0, Revision::READ_LATEST);
-               if (!$rev) return false;
-               $content = $rev->getContent(Revision::FOR_THIS_USER, $wgUser);
-               if (!$content) return false;
-               $curText = $content->getNativeData();
-
-               $iCount = 0;
-               $newText = $curText;
-               foreach ( $this->aMatch as $i => $strMatch ) {
-                       $strNextReplace = $this->aReplace[$i];
-                       $result = @preg_replace_callback( $strMatch,
-                               function ( $aMatches ) use($strNextReplace){
-                                       $strFind = array();
-                                       $strReplace = array();
-                                       foreach ($aMatches as $i => $strMatch) {
-                                               $aFind[] = '$' . $i;
-                                               $aReplace[] = $strMatch;
-                                       }
-                                       return str_replace($aFind, $aReplace, 
$strNextReplace);
-                               }, $newText, -1, $iCount );
-                       if ($result !== null) {
-                               $newText = $result;
-                       } else {
-                               throw new UsageException( $this->msg( 
'masseditregex-badregex' )->escaped()
-                                       . ' <b>' . htmlspecialchars( $strMatch 
) . '</b>', 'masseditregex-badregex' );
-                       }
-               }
-
-               if ( $isPreview ) {
-                       // In preview mode, display the first few diffs
-                       $this->diff->setText( $curText, $newText );
-                       $htmlDiff .= $this->diff->getDiff( '<b>'
-                               . htmlspecialchars( $title->getPrefixedText() ) 
. ' - '
-                               . $this->msg( 'masseditregex-before' 
)->escaped() . '</b>',
-                               '<b>' . $this->msg( 'masseditregex-after' 
)->escaped() . '</b>' );
-               } else {
-                       // Not in preview mode, make the edits
-                       $wgOut->addHTML( '<li>' . $this->msg( 
'masseditregex-num-changes',
-                               $title->getPrefixedText(), $iCount )->escaped() 
. '</li>' );
-
-                       if ( strcmp( $curText, $newText ) != 0 ) {
-                               $newContent = new WikitextContent( $newText );
-                               $article->doEditContent( $newContent, 
$this->strSummary,
-                                       EDIT_UPDATE | EDIT_FORCE_BOT | 
EDIT_DEFER_UPDATES,
-                                       $rev->getId() );
-                       }
-               }
-               return true;
+       public function setSearch( $search ) {
+               $this->search = explode( "\n", trim( $search ) );
        }
 
-       /// Perform the regex process.
        /**
-        * @param bool $isPreview
-        *   true to generate diffs, false to perform page edits.
+        * @param string $summary
         */
-       function perform( $isPreview ) {
-               global $wgRequest, $wgOut, $wgUser, $wgLang;
-
-               $pageCountLimit = $isPreview ? MER_MAX_PREVIEW_DIFFS : 
MER_MAX_EXECUTE_PAGES;
-               $errors = array();
-
-               if ( $isPreview ) {
-                       $this->diff = new DifferenceEngine();
-                       $this->diff->showDiffStyle(); // send CSS link to the 
browser for diff colours
-                       $htmlDiff = '';
-               } else {
-                       $wgOut->addHTML( '<ul>' );
-               }
-
-               $iArticleCount = 0;
-               try {
-                       foreach ( $this->aPageList as $pageTitle ) {
-                               $titleArray = array();
-                               switch ($this->strPageListType) {
-                                       case 'pagenames': // Can do this in one 
hit
-                                               $t = Title::newFromText( 
$pageTitle );
-                                               if ( !$t || !$this->editPage( 
$t, $isPreview, $htmlDiff ) ) {
-                                                       $errors[] = $this->msg( 
'masseditregex-page-not-exists',
-                                                               $pageTitle 
)->escaped();
-                                               }
-                                               $iArticleCount++;
-                                               break;
-
-                                       case 'pagename-prefixes':
-                                               $titles = 
PrefixSearch::titleSearch( $pageTitle,
-                                                       $pageCountLimit - 
$iArticleCount );
-                                               if ( empty( $titles ) ) {
-                                                       $errors[] = $this->msg( 
'masseditregex-exprnomatch',
-                                                               $pageTitle 
)->escaped();
-                                                       $iArticleCount++;
-                                                       continue;
-                                               }
-
-                                               foreach ( $titles as $title ) {
-                                                       $t = 
Title::newFromText( $title );
-                                                       if ( !$t ) {
-                                                               $errors[] = 
$this->msg( 'masseditregex-page-not-exists',
-                                                                       $title 
)->escaped();
-                                                       } else {
-                                                               $titleArray[] = 
$t;
-                                                       }
-                                               }
-                                               break;
-
-                                       case 'categories':
-                                               $cat = 
Category::newFromName($pageTitle);
-                                               if ( $cat === false ) {
-                                                       $errors[] = $this->msg( 
'masseditregex-page-not-exists',
-                                                               $pageTitle 
)->escaped();
-                                                       break;
-                                               }
-                                               $titleArray = 
$cat->getMembers($pageCountLimit - $iArticleCount);
-                                               break;
-
-                                       case 'backlinks':
-                                               $t = 
Title::newFromText($pageTitle);
-                                               if ( !$t ) {
-                                                       if ( $isPreview ) {
-                                                               $errors[] = 
$this->msg( 'masseditregex-page-not-exists',
-                                                                       
$pageTitle )->escaped();
-                                                       }
-                                                       continue;
-                                               }
-                                               $blc = $t->getBacklinkCache();
-                                               if ( $t->getNamespace() == 
NS_TEMPLATE ) {
-                                                       // Backlinks for 
Template pages are in a different table
-                                                       $table = 
'templatelinks';
-                                               } else {
-                                                       $table = 'pagelinks';
-                                               }
-                                               $titleArray = 
$blc->getLinks($table, false, false,
-                                                       $pageCountLimit - 
$iArticleCount);
-                                               break;
-                               }
-
-                               // If the above switch produced an array of 
pages, run through them now
-                               foreach ( $titleArray as $target ) {
-                                       if ( !$this->editPage( $target, 
$isPreview, $htmlDiff ) ) {
-                                               $errors[] = $this->msg( 
'masseditregex-page-not-exists',
-                                                       
$target->getPrefixedText() )->escaped();
-                                       }
-                                       $iArticleCount++;
-                                       if ( $iArticleCount >= $pageCountLimit 
) {
-                                               $htmlDiff .= Xml::element('p', 
null,
-                                                       $this->msg( 
'masseditregex-max-preview-diffs' )->numParams(
-                                                               $pageCountLimit 
)->text()
-                                               );
-                                               break;
-                                       }
-                               }
-
-                       }
-               } catch (UsageException $e) {
-                       $errors[] = $e;
-
-                       // Force a preview if there was a bad regex
-                       if ( !$isPreview ) {
-                               $wgOut->addHTML( '</ul>' );
-                       }
-                       $isPreview = true;
-               }
-
-               if ( !$isPreview ) {
-                       $wgOut->addHTML( '</ul>' );
-               }
-
-               if ( ( $iArticleCount == 0 ) && empty( $errors ) ) {
-                       $errors[] = $this->msg( 'masseditregex-err-nopages' 
)->escaped();
-                       // Force a preview if there was nothing to do
-                       $isPreview = true;
-               }
-
-               if ( !empty($errors ) ) {
-                       $wgOut->addHTML( '<div class="errorbox">' );
-                       $wgOut->addHTML( $this->msg( 'masseditregex-editfailed' 
)->escaped() );
-
-                       $wgOut->addHTML( '<ul><li>' );
-                       $wgOut->addHTML( join( '</li><li> ', $errors) );
-                       $wgOut->addHTML( '</li></ul></div>' );
-               }
-
-               if ( $isPreview ) {
-                       // Show the form again ready for further editing if 
we're just previewing
-                       $this->showForm();
-
-                       // Show the diffs now (after any errors)
-                       $wgOut->addHTML( $htmlDiff );
-               } else {
-                       $wgOut->addWikiMsg( 
'masseditregex-num-articles-changed', $iArticleCount );
-                       $wgOut->addHTML(
-                               Linker::linkKnown(
-                                       SpecialPage::getSafeTitleFor( 
'Contributions', $wgUser->getName() ),
-                                       $this->msg( 
'masseditregex-view-full-summary' )->parse()
-                               )
-                       );
-               }
+       public function setSummary( $summary ) {
+               $this->summary = $summary;
        }
-
-       public static function efSkinTemplateNavigationUniversal( &$sktemplate, 
&$links )
-       {
-               $title = $sktemplate->getTitle();
-               $ns = $title->getNamespace();
-               if ( $ns == NS_CATEGORY ) {
-                       $url = SpecialPage::getTitleFor( 'MassEditRegex' 
)->getLocalURL(
-                               array(
-                                       'wpPageList' => $title->getText(),
-                                       'wpPageListType' => 'categories',
-                               )
-                       );
-               } elseif (
-                       ( $ns == NS_SPECIAL )
-                       && ( $title->isSpecial( 'Whatlinkshere' ) )
-               ) {
-                       $titleParts = 
SpecialPageFactory::resolveAlias($title->getText());
-
-                       $url = SpecialPage::getTitleFor( 'MassEditRegex' 
)->getLocalURL(
-                               array(
-                                       'wpPageList' => $titleParts[1],
-                                       'wpPageListType' => 'backlinks',
-                               )
-                       );
-               } else {
-                       // No tab
-                       return true;
-               }
-
-               $links['views']['masseditregex'] = array(
-                       'class' => false,
-                       'text' => wfMessage('masseditregex-editall')->text(),
-                       'href' => $url,
-                       'context' => 'main',
-               );
-               return true;
-       }
-
-       public static function efBaseTemplateToolbox( &$tpl, &$toolbox ) {
-               if ( !$tpl->getTitle()->isSpecial( 'MassEditRegex' ) ) return 
true;
-
-               // Hide the 'printable version' link as the shortcut key 
conflicts with
-               // the preview button.
-               unset($toolbox['print']);
-               return true;
-       }
-
 }
diff --git a/MassEditRegex.i18n.php b/MassEditRegex.i18n.php
index 8fe5798..409f567 100644
--- a/MassEditRegex.i18n.php
+++ b/MassEditRegex.i18n.php
@@ -11,6 +11,7 @@
  * This shim maintains compatibility back to MediaWiki 1.17.
  */
 $messages = array();
+
 if ( !function_exists( 'wfJsonI18nShime651553115efb814' ) ) {
        function wfJsonI18nShime651553115efb814( $cache, $code, &$cachedData ) {
                $codeSequence = array_merge( array( $code ), 
$cachedData['fallbackSequence'] );
diff --git a/MassEditRegex.js b/MassEditRegex.js
new file mode 100644
index 0000000..b1f3244
--- /dev/null
+++ b/MassEditRegex.js
@@ -0,0 +1,280 @@
+function executeMassEdit() {
+
+       function getDataFromPath( data, path  ) {
+               path = path.split( '.' );
+               for ( var x = 0; x < path.length; x++ ) {
+                       var dataKeys = [];
+                       for ( var k in data ) {
+                               dataKeys.push( k );
+                       }
+                       if ( $.inArray( path[x], dataKeys ) == -1 ) {
+                               return false;
+                       }
+                       data = data[path[x]];
+               }
+               return data;
+       }
+
+       function makeAPICall( data, callback, c ) {
+               var waitForCallback = false;
+
+               if ( c !== undefined ) {
+                       data.params[data.continueKey] = c;
+               }
+
+               $.ajax( {
+                       url: mw.util.wikiScript( 'api' ),
+                       data: data.params,
+                       dataType: 'json',
+                       type: 'POST',
+                       success: function( response ) {
+                               var result = getDataFromPath( response, 
data.resultPath );
+                               if ( result ) {
+                                       if ( data.continuePath ) {
+                                               var continueData = 
getDataFromPath( response, data.continuePath );
+                                               if ( continueData ) {
+                                                       waitForCallback = true;
+                                                       makeAPICall( data, 
function( r ) {
+                                                               waitForCallback 
= false;
+                                                               result = 
result.concat( r );
+                                                       }, continueData );
+                                               }
+                                               var timerId = setInterval( 
function() {
+                                                       if ( !waitForCallback ) 
{
+                                                               callback( 
result );
+                                                               clearInterval( 
timerId );
+                                                       }
+                                               }, 10 );
+                                       } else {
+                                               callback( result );
+                                       }
+                               } else if ( response && response.error ) {
+                                       alert( mw.message( 
'masseditregex-js-mwapi-api-error',
+                                               response.error.code, 
response.error.info ).text() );
+                               } else {
+                                       alert( mw.message( 
'masseditregex-js-mwapi-general-error' ).text() );
+                               }
+                       },
+                       error: function( xhr ) {
+                               alert( mw.message( 
'masseditregex-js-mwapi-unknown-error' ).text() );
+                       }
+               } );
+       }
+
+       function getCategoryPages( page, callback ) {
+               var data = {
+                       resultPath: 'query.categorymembers',
+                       continuePath: 
'query-continue.categorymembers.cmcontinue',
+                       continueKey: 'cmcontinue',
+                       params:{
+                               format: 'json',
+                               action: 'query',
+                               list: 'categorymembers',
+                               cmtitle: 'Category:' + page,
+                               cmlimit: 100
+                       }
+               };
+               makeAPICall( data, callback );
+       }
+
+       function getBackLinkPages( page, callback ) {
+               var data = {
+                       resultPath: 'query.backlinks',
+                       continuePath: 'query-continue.backlinks.blcontinue',
+                       continueKey: 'cmcontinue',
+                       params:{
+                               format: 'json',
+                               action: 'query',
+                               list: 'backlinks',
+                               bltitle: page,
+                               bllimit: 100
+                       }
+               };
+               makeAPICall( data, callback );
+       }
+
+       function getAllPrefixPages( page, callback ) {
+               var data = {
+                       resultPath: 'query.namespaces',
+                       params:{
+                               format: 'json',
+                               action: 'query',
+                               meta: 'siteinfo',
+                               siprop: 'namespaces'
+                       }
+               };
+               makeAPICall( data, function( namespaces ) {
+                       var data;
+                       var nsId;
+                       var result = {
+                               pages: []
+                       };
+                       var size = 0;
+                       var count = 0;
+                       var key;
+
+                       // Count number of elements
+                       for ( key in namespaces ) {
+                               if ( namespaces[key].id > 0 ) {
+                                       size++;
+                               }
+                       }
+
+                       // Iterate through the namespaces
+                       for ( key in namespaces ) {
+                               nsId = namespaces[key].id;
+                               if ( nsId >= 0 ) {
+                                       data = {
+                                               resultPath: 'query.allpages',
+                                               continuePath: 
'query-continue.allpages.apcontinue',
+                                               continueKey: 'apcontinue',
+                                               params:{
+                                                       format: 'json',
+                                                       action: 'query',
+                                                       list: 'allpages',
+                                                       apnamespace: nsId,
+                                                       apprefix: page,
+                                                       aplimit: 100
+                                               }
+                                       };
+
+                                       ( function ( data, result, callback ) {
+                                               makeAPICall(data, function 
(pages) {
+                                                       result.pages = 
result.pages.concat( pages );
+                                                       if ( callback !== null 
) {
+                                                               callback( 
result.pages );
+                                                       }
+                                               });
+                                       }( data, result, count++ === size ? 
callback : null ) );
+
+                               }
+                       }
+               } );
+       }
+
+       function getPages( pages, callback ) {
+               var data = {
+                       resultPath: 'query.pages',
+                       params: {
+                               format: 'json',
+                               action: 'query',
+                               titles: pages.join( '|' )
+                       }
+               };
+               makeAPICall( data, function ( data ) {
+                       // Convert from object to array
+                       pages = [];
+                       for ( var key in data ) {
+                               pages.push( data[key] );
+                       }
+                       callback( pages );
+               } );
+       }
+
+       function editPages( pages, search, replace, summary, cb ) {
+               var rObj = { remaining: pages.length };
+               if ( pages.length === 0 ) {
+                       cb( { error: 'No pages found!' } );
+                       return;
+               }
+
+               for ( var x = 0; x < pages.length; x++ ) {
+                       ( function ( page, search, replace, cb, rObj ) {
+                               var pageId = page.pageid;
+
+                               if ( pageId === undefined ) {
+                                       cb( { error: mw.message( 
'masseditregex-js-pagenotexist', page.title ).text() } );
+                               } else {
+                                       $.ajax({
+                                               url: mw.util.wikiScript(),
+                                               data: {
+                                                       action: 'ajax',
+                                                       rs: 
'MassEditRegexAPI::edit',
+                                                       rsargs: [pageId, 
search, replace, summary]
+                                               },
+                                               dataType: 'json',
+                                               type: 'POST'
+                                       }).done( function ( response ) {
+                                                       rObj.remaining--;
+                                                       cb( page, response, 
rObj.remaining );
+                                       });
+                               }
+                       }( pages[x], search, replace, cb, rObj ) );
+               }
+       }
+
+       function doEdit( pages ) {
+               var search = $( '#wpMatch' ).val();
+               var replace = $( '#wpReplace' ).val();
+               var summary = $( '#wpSummary' ).val();
+               var content = $( '<div></div>' );
+               var heading = $( '<h1></h1>' );
+               content.append( heading );
+               heading.text( mw.message( 'masseditregex-js-working' ).text() );
+               var list = $( '<ul></ul>' );
+               content.append( list );
+
+               content.dialog( {
+                       height: $(window).height() * 0.8,
+                       width: $(window).width() * 0.8,
+                       modal: true
+               } );
+
+               editPages( pages, search, replace, summary,
+                       function( page, response, remaining ) {
+                               var li = $('<li></li>');
+
+                               if ( page.error || response.error ) {
+                                       li.text(page.title + ': ' + page.error 
? page.error : response.error);
+                               } else {
+                                       li.text( mw.message( 
'masseditregex-js-editpage', response.changes,
+                                               page.title, remaining ).text() 
);
+                               }
+
+                               list.prepend(li);
+
+                               if ( remaining === 0 ) {
+                                       li = $('<li></li>');
+                                       li.text( mw.message( 
'masseditregex-js-jobdone' ).text() );
+                                       list.prepend(li);
+                                       heading.text( mw.message( 
'masseditregex-js-jobdone' ).text() );
+                               }
+                       }
+               );
+       }
+
+       var pages = $( '#wpPageList' ).val().split( '\n' );
+       var type = $( 'input[name="wpPageListType"]:checked' ).val();
+
+       var x;
+       switch ( type ) {
+               case 'pagenames':
+                       getPages( pages, doEdit );
+                       break;
+               case 'pagename-prefixes':
+                       for ( x = 0; x < pages.length; x++ ) {
+                               getAllPrefixPages( pages[x], doEdit );
+                       }
+                       break;
+               case 'backlinks':
+                       for ( x = 0; x < pages.length; x++ ) {
+                               getBackLinkPages( pages[x], doEdit );
+                       }
+                       break;
+               case 'categories':
+                       for ( x = 0; x < pages.length; x++ ) {
+                               getCategoryPages( pages[x], doEdit );
+                       }
+                       break;
+       }
+}
+
+$( document ).ready( function () {
+       $( '#wpSave' ).click( function () {
+               if ( $( '#wpClientSide' ).is( ':checked' ) ) {
+                       executeMassEdit();
+                       return false;
+               }
+               return true;
+       } );
+} );
diff --git a/MassEditRegex.php b/MassEditRegex.php
index bfafff7..42449b6 100644
--- a/MassEditRegex.php
+++ b/MassEditRegex.php
@@ -11,14 +11,15 @@
  * @link http://www.mediawiki.org/wiki/Extension:MassEditRegex Documentation
  *
  * @author Adam Nielsen <[email protected]>
- * @copyright Copyright © 2009,2013 Adam Nielsen
+ * @author Kim Eik <[email protected]>
+ * @copyright Copyright © 2009-2015 Adam Nielsen
  * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
  */
 
 $wgExtensionCredits['specialpage'][] = array(
        'path' => __FILE__,
        'name' => 'Mass Edit via Regular Expressions',
-       'version' => '7.0.0',
+       'version' => '8.0.0',
        'author' => 'Adam Nielsen',
        'url' => 'https://www.mediawiki.org/wiki/Extension:MassEditRegex',
        'descriptionmsg' => 'masseditregex-desc'
@@ -29,11 +30,38 @@
 $wgExtensionMessagesFiles['MassEditRegex'] = $dir . 'MassEditRegex.i18n.php';
 $wgExtensionMessagesFiles['MassEditRegexAlias'] = $dir . 
'MassEditRegex.alias.php';
 $wgAutoloadClasses['MassEditRegex'] = $dir . 'MassEditRegex.class.php';
-$wgSpecialPages['MassEditRegex'] = 'MassEditRegex';
+$wgAutoloadClasses['MassEditRegexSpecialPage'] = $dir . 
'MassEditRegex.special.php';
+$wgAutoloadClasses['MassEditRegexAPI'] = $dir . 'MassEditRegex.api.php';
+$wgSpecialPages['MassEditRegex'] = 'MassEditRegexSpecialPage';
 $wgSpecialPageGroups['MassEditRegex'] = 'pagetools';
 
 // Required permission to use Special:MassEditRegex
 $wgAvailableRights[] = 'masseditregex';
 
-$wgHooks['SkinTemplateNavigation::Universal'][] = 
'MassEditRegex::efSkinTemplateNavigationUniversal';
-$wgHooks['BaseTemplateToolbox'][] = 'MassEditRegex::efBaseTemplateToolbox';
+$wgHooks['SkinTemplateNavigation::Universal'][] = 
'MassEditRegexSpecialPage::efSkinTemplateNavigationUniversal';
+$wgHooks['BaseTemplateToolbox'][] = 
'MassEditRegexSpecialPage::efBaseTemplateToolbox';
+
+$wgResourceModules['MassEditRegex'] = array(
+       'scripts' => array(
+               'MassEditRegex.js'
+       ),
+       'dependencies' => array(
+               'jquery.ui.dialog'
+       ),
+       'group' => 'MassEditRegex',
+       'localBasePath' => dirname(__FILE__),
+       'remoteExtPath' => 'MassEditRegex',
+       'messages' => array (
+               'masseditregex-js-execution',
+               'masseditregex-js-jobdone',
+               'masseditregex-js-editpage',
+               'masseditregex-js-working',
+               'masseditregex-js-pagenotexist',
+               'masseditregex-js-mwapi-api-error',
+               'masseditregex-js-mwapi-general-error',
+               'masseditregex-js-mwapi-unknown-error',
+       )
+);
+
+// AJAX
+$wgAjaxExportList[] = 'MassEditRegexAPI::edit';
diff --git a/MassEditRegex.special.php b/MassEditRegex.special.php
new file mode 100644
index 0000000..92d4729
--- /dev/null
+++ b/MassEditRegex.special.php
@@ -0,0 +1,528 @@
+<?php
+if ( ! defined( 'MEDIAWIKI' ) )
+       die();
+/**
+ * Allow users in the Bot group to edit many articles in one go by applying
+ * regular expressions to a list of pages.
+ *
+ * @file
+ * @ingroup SpecialPage
+ *
+ * @link http://www.mediawiki.org/wiki/Extension:MassEditRegex Documentation
+ *
+ * @author Adam Nielsen <[email protected]>
+ * @copyright Copyright © 2009,2013 Adam Nielsen
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
+ */
+
+/// Maximum number of pages/diffs to display when previewing the changes
+define('MER_MAX_PREVIEW_DIFFS', 20);
+
+/// Maximum number of pages to edit.
+define('MER_MAX_EXECUTE_PAGES', 1000);
+
+/** Main class that define a new special page*/
+class MassEditRegexSpecialPage extends SpecialPage {
+       private $aPageList;       ///< Array of string - user-supplied page 
titles
+       private $strPageListType; ///< Type of titles (categories, backlinks, 
etc.)
+       private $strMatch;        ///< Match regex from form
+       private $strReplace;      ///< Substitution regex from form
+       private $isClientSide;    ///< Is the client-side checkbox ticked?
+       private $sk;              ///< Skin instance
+
+       /**
+        * @var \MassEditRegex
+        */
+       private $massEditRegex;
+
+       function __construct() {
+               parent::__construct( 'MassEditRegex', 'masseditregex' );
+       }
+
+       /// Perform the regex process.
+       /**
+        * @param bool $isPreview
+        *   true to generate diffs, false to perform page edits.
+        */
+       public function perform( $isPreview ) {
+               global $wgOut, $wgUser, $wgLang;
+
+               $pageCountLimit = $isPreview ? MER_MAX_PREVIEW_DIFFS : 
MER_MAX_EXECUTE_PAGES;
+               $errors = array();
+
+               if ( $isPreview ) {
+                       $this->massEditRegex->getDiffEngine()->showDiffStyle();
+                       $htmlDiff = '';
+               } else {
+                       $wgOut->addHTML( '<ul>' );
+               }
+
+               $iArticleCount = 0;
+               try {
+                       foreach ( $this->aPageList as $pageTitle ) {
+                               $titleArray = array();
+                               switch ($this->strPageListType) {
+                                       case 'pagenames': // Can do this in one 
hit
+                                               $t = Title::newFromText( 
$pageTitle );
+                                               if ( !$t || !$this->editPage( 
$t, $isPreview, $htmlDiff ) ) {
+                                                       $errors[] = wfMsg( 
'masseditregex-page-not-exists',
+                                                               
htmlspecialchars( $pageTitle ) );
+                                               }
+                                               $iArticleCount++;
+                                               break;
+
+                                       case 'pagename-prefixes':
+                                               $titles = 
PrefixSearch::titleSearch( $pageTitle,
+                                                       $pageCountLimit - 
$iArticleCount );
+                                               if ( empty( $titles ) ) {
+                                                       $errors[] = wfMsg( 
'masseditregex-exprnomatch',
+                                                               
htmlspecialchars( $pageTitle ) );
+                                                       $iArticleCount++;
+                                                       continue;
+                                               }
+
+                                               foreach ( $titles as $title ) {
+                                                       $t = 
Title::newFromText( $title );
+                                                       if ( !$t ) {
+                                                               $errors[] = 
wfMessage(  'masseditregex-page-not-exists', $title  )->text();
+                                                       } else {
+                                                               $titleArray[] = 
$t;
+                                                       }
+                                               }
+                                               break;
+
+                                       case 'categories':
+                                               $cat = 
Category::newFromName($pageTitle);
+                                               if ( $cat === false ) {
+                                                       $errors[] = wfMsg( 
'masseditregex-page-not-exists',
+                                                               
htmlspecialchars( $pageTitle ) );
+                                                       break;
+                                               }
+                                               $titleArray = 
$cat->getMembers($pageCountLimit - $iArticleCount);
+                                               break;
+
+                                       case 'backlinks':
+                                               $t = 
Title::newFromText($pageTitle);
+                                               if ( !$t ) {
+                                                       if ( $isPreview ) {
+                                                               $errors[] = 
wfMsg( 'masseditregex-page-not-exists',
+                                                                       
htmlspecialchars( $pageTitle ) );
+                                                       }
+                                                       continue;
+                                               }
+                                               $blc = $t->getBacklinkCache();
+                                               if ( $t->getNamespace() == 
NS_TEMPLATE ) {
+                                                       // Backlinks for 
Template pages are in a different table
+                                                       $table = 
'templatelinks';
+                                               } else {
+                                                       $table = 'pagelinks';
+                                               }
+                                               $titleArray = 
$blc->getLinks($table, false, false,
+                                                       $pageCountLimit - 
$iArticleCount);
+                                               break;
+                               }
+
+                               // If the above switch produced an array of 
pages, run through them now
+                               foreach ( $titleArray as $target ) {
+                                       if ( !$this->editPage( $target, 
$isPreview, $htmlDiff ) ) {
+                                               $errors[] = wfMsg( 
'masseditregex-page-not-exists',
+                                                       htmlspecialchars( 
$target->getPrefixedText() ) );
+                                       }
+                                       $iArticleCount++;
+                                       if ( $iArticleCount >= $pageCountLimit 
) {
+                                               $htmlDiff .= Xml::element('p', 
null,
+                                                       wfMsg( 
'masseditregex-max-preview-diffs',
+                                                               
$wgLang->formatNum( $pageCountLimit )
+                                                       )
+                                               );
+                                               break;
+                                       }
+                               }
+
+                       }
+               } catch (UsageException $e) {
+                       $errors[] = $e;
+
+                       // Force a preview if there was a bad regex
+                       if ( !$isPreview ) {
+                               $wgOut->addHTML( '</ul>' );
+                       }
+                       $isPreview = true;
+               }
+
+               if ( !$isPreview ) {
+                       $wgOut->addHTML( '</ul>' );
+               }
+
+               if ( ( $iArticleCount == 0 ) && empty( $errors ) ) {
+                       $errors[] = wfMessage(  'masseditregex-err-nopages'  
)->text();
+                       // Force a preview if there was nothing to do
+                       $isPreview = true;
+               }
+
+               if ( !empty($errors ) ) {
+                       $wgOut->addHTML( '<div class="errorbox">' );
+                       $wgOut->addHTML( wfMessage(  'masseditregex-editfailed' 
 )->text() );
+
+                       $wgOut->addHTML( '<ul><li>' );
+                       $wgOut->addHTML( join( '</li><li> ', $errors) );
+                       $wgOut->addHTML( '</li></ul></div>' );
+               }
+
+               if ( $isPreview ) {
+                       // Show the form again ready for further editing if 
we're just previewing
+                       $this->showForm();
+
+                       // Show the diffs now (after any errors)
+                       $wgOut->addHTML( '<hr style="margin: 1em;"/>' );
+                       $wgOut->addHTML( $htmlDiff );
+               } else {
+                       $wgOut->addWikiMsg( 
'masseditregex-num-articles-changed', $iArticleCount );
+                       $wgOut->addHTML(
+                               $this->sk->makeKnownLinkObj(
+                                       SpecialPage::getSafeTitleFor( 
'Contributions', $wgUser->getName() ),
+                                       wfMsgHtml( 
'masseditregex-view-full-summary' )
+                               )
+                       );
+               }
+       }
+
+       /// Display the special page, and run the regexes if a form is being 
submitted
+       public function execute( $par ) {
+               global $wgUser;
+
+               $wgOut = $this->getOutput();
+               $wgOut->addModules('MassEditRegex');
+
+               $this->setHeaders();
+
+               // Check permissions
+               if ( !$wgUser->isAllowed( 'masseditregex' ) ) {
+                       $this->displayRestrictionError();
+                       return;
+               }
+
+               // Show a message if the database is in read-only mode
+               if ( wfReadOnly() ) {
+                       $wgOut->readOnlyPage();
+                       return;
+               }
+
+               // If user is blocked, s/he doesn't need to access this page
+               if ( $wgUser->isBlocked() ) {
+                       $wgOut->blockedPage();
+                       return;
+               }
+
+               $this->outputHeader();
+
+               $wgRequest = $this->getRequest();
+               $strPageList = $wgRequest->getText( 'wpPageList', 'Sandbox' );
+               $this->aPageList = explode( "\n", trim( $strPageList ) );
+               $this->strPageListType = $wgRequest->getText( 'wpPageListType', 
'pagenames' );
+
+               $this->sk = $wgUser->getSkin();
+
+               $this->strMatch = $wgRequest->getText( 'wpMatch', '/hello 
(.*)\n/' );
+
+               $this->strReplace = $wgRequest->getText( 'wpReplace', 'goodbye 
$1' );
+
+               $summary = $wgRequest->getText( 'wpSummary', '' );
+               $this->isClientSide = $wgRequest->getVal( 'wpClientSide', false 
) == 1;
+
+               $this->massEditRegex = new MassEditRegex(
+                       $this->strMatch, $this->strReplace, $summary, $wgUser
+               );
+
+               if ( $wgRequest->wasPosted() ) {
+                       $this->perform( !$wgRequest->getCheck('wpSave') );
+               } else {
+                       $this->showForm();
+                       $this->showHints();
+               }
+
+       }
+
+       /// Display the form requesting the regexes from the user.
+       function showForm() {
+               $wgOut = $this->getOutput();
+
+               $wgOut->addWikiMsg( 'masseditregextext' );
+               $titleObj = SpecialPage::getTitleFor( 'MassEditRegex' );
+
+               $wgOut->addHTML(
+                       Xml::openElement('form', array(
+                               'id' => 'masseditregex',
+                               'method' => 'post',
+                               'action' => 
$titleObj->getLocalURL('action=submit')
+                       )) .
+                       Xml::element('p',
+                               null, wfMessage(  'masseditregex-pagelisttxt'  
)->text()
+                       ) .
+                       Xml::textarea(
+                               'wpPageList',
+                               join( "\n", $this->aPageList )
+                       ) .
+                       Xml::element('span',
+                               null, wfMessage(  
'masseditregex-listtype-intro'  )->text()
+                       ) .
+                       Xml::openElement('ul', array(
+                               'style' => 'list-style: none' // don't want any 
bullets for radio btns
+                       ))
+               );
+
+
+               // Generate HTML for the radio buttons (one for each list type)
+               foreach (array('pagenames', 'pagename-prefixes', 'categories', 
'backlinks')
+                       as $strValue){
+
+                       // Have to use openElement because putting an Xml::xxx 
return value
+                       // inside an Xml::element causes the HTML code to be 
escaped and appear
+                       // on the page.
+                       $wgOut->addHTML(
+                               Xml::openElement('li') .
+                               // Give grep a chance to find the usages:
+                               // masseditregex-listtype-pagenames, 
masseditregex-listtype-pagename-prefixes,
+                               // masseditregex-listtype-categories, 
masseditregex-listtype-backlinks
+                               Xml::radioLabel(
+                                       wfMessage(  'masseditregex-listtype-' . 
$strValue  )->text(),
+                                       'wpPageListType',
+                                       $strValue,
+                                       'masseditregex-radio-' . $strValue,
+                                       $strValue == $this->strPageListType
+                               ) .
+                               Xml::closeElement('li')
+                       );
+               }
+               $wgOut->addHTML(
+                       Xml::closeElement('ul') .
+
+                       // Display the textareas for the regex and replacement 
to go into
+
+                       // Can't use Xml::buildTable because we need to put 
code into the table
+                       Xml::openElement('table', array(
+                               'style' => 'width: 100%'
+                       )) .
+                               Xml::openElement('tr') .
+                                       Xml::openElement('td') .
+                                               Xml::element('p', null, 
wfMessage(  'masseditregex-matchtxt'  )->text()) .
+                                               Xml::textarea(
+                                                       'wpMatch',
+                                                       $this->strMatch  // use 
original value
+                                               ) .
+                                               Xml::closeElement('textarea') .
+                                       Xml::closeElement('td') .
+                                       Xml::openElement('td') .
+                                               Xml::element('p', null, 
wfMessage(  'masseditregex-replacetxt'  )->text()) .
+                                               Xml::textarea(
+                                                       'wpReplace',
+                                                       $this->strReplace  // 
use original value
+                                               ) .
+                                               Xml::closeElement('textarea') .
+                                       Xml::closeElement('td') .
+                                       Xml::closeElement('tr') .
+                       Xml::closeElement('table') .
+
+                       Xml::openElement( 'div', array(
+                               'class' => 'editOptions',
+                               'style' => 'margin: 1ex;'
+                       ) ) .
+
+                       // Display the edit summary and preview
+
+                       Xml::tags( 'span',
+                               array(
+                                       'class' => 'mw-summary',
+                                       'id' => 'wpSummaryLabel'
+                               ),
+                               Xml::tags( 'label', array(
+                                       'for' => 'wpSummary'
+                               ), wfMessage(  'summary'  )->text() )
+                       ) . ' ' .
+
+                       Xml::input( 'wpSummary',
+                               60,
+                               $this->massEditRegex->getSummary(),
+                               array(
+                                       'id' => 'wpSummary',
+                                       'maxlength' => '200',
+                                       'tabindex' => '1'
+                               )
+                       ) .
+
+                       Xml::tags( 'div',
+                               array( 'class' => 'mw-summary-preview' ),
+                               wfMsgExt( 'summary-preview', 'parseinline' ) .
+                                       $this->sk->commentBlock( 
$this->massEditRegex->getSummary() )
+                       ) .
+                       Xml::closeElement( 'div' ) . // class=editOptions
+
+                       // Display the preview + execute buttons
+                       Xml::element('input', array(
+                               'id'        => 'wpSave',
+                               'name'      => 'wpSave',
+                               'type'      => 'submit',
+                               'value'     => wfMessage(  
'masseditregex-executebtn'  )->text(),
+                               'accesskey' => wfMessage(  'accesskey-save'  
)->text(),
+                               'title'     => wfMessage(  
'masseditregex-tooltip-execute'  )->text().' ['.wfMessage(  'accesskey-save'  
)->text().']',
+                       )) .
+
+                       Xml::element('input', array(
+                               'id'        => 'wpPreview',
+                               'name'      => 'wpPreview',
+                               'type'      => 'submit',
+                               'value'     => wfMessage( 'showpreview' 
)->text(),
+                               'accesskey' => wfMessage( 'accesskey-preview' 
)->text(),
+                               'title'     => wfMessage(  'tooltip-preview'  
)->text().' ['.wfMessage(  'accesskey-preview'  )->text().']',
+                       )) .
+
+                       Xml::tags( 'span',
+                               array(
+                                       'style' => 'margin-left: 1em;'
+                               ),
+                               Xml::checkLabel(
+                                       
wfMessage('masseditregex-js-clientside'),
+                                       'wpClientSide',
+                                       'wpClientSide',
+                                       $this->isClientSide,
+                                       array(
+                                               'title' => 
wfMessage('masseditregex-js-execution'),
+                                       )
+                               )
+                       )
+               );
+
+               $wgOut->addHTML( Xml::closeElement('form') );
+               $wgOut->addModules('MassEditRegex');
+       }
+
+       /// Show a short table of regex examples.
+       function showHints() {
+               global $wgOut;
+
+               $wgOut->addHTML(
+                       Xml::element( 'p', null, wfMessage(  
'masseditregex-hint-intro'  )->text() )
+               );
+               $wgOut->addHTML(Xml::buildTable(
+
+                       // Table rows (the hints)
+                       array(
+                               array(
+                                       '/$/',
+                                       'abc',
+                                       wfMessage(  
'masseditregex-hint-toappend'  )->text()
+                               ),
+                               array(
+                                       '/$/',
+                                       '\\n[[Category:New]]',
+                                       // Since we can't pass "rowspan=2" to 
the hint text above, we'll
+                                       // have to display it again
+                                       wfMessage(  
'masseditregex-hint-toappend'  )->text()
+                               ),
+                               array(
+                                       '/{{OldTemplate}}/',
+                                       '',
+                                       wfMessage(  'masseditregex-hint-remove' 
 )->text()
+                               ),
+                               array(
+                                       '/\\[\\[Category:[^]]+\]\]/',
+                                       '',
+                                       wfMessage(  
'masseditregex-hint-removecat'  )->text()
+                               ),
+                               array(
+                                       '/(\\[\\[[^]]*\\|[^]]*)AAA(.*\\]\\])/',
+                                       '$1BBB$2',
+                                       wfMessage(  
'masseditregex-hint-renamelink'  )->text()
+                               ),
+                       ),
+
+                       // Table attributes
+                       array(
+                               'class' => 'wikitable'
+                       ),
+
+                       // Table headings
+                       array(
+                               wfMessage(  'masseditregex-hint-headmatch'  
)->text(), // really needs width 12em
+                               wfMessage(  'masseditregex-hint-headreplace'  
)->text(), // really needs width 12em
+                               wfMessage(  'masseditregex-hint-headeffect'  
)->text()
+                       )
+
+               )); // Xml::buildTable
+
+       }
+
+       public static function efSkinTemplateNavigationUniversal( &$sktemplate, 
&$links )
+       {
+               $title = $sktemplate->getTitle();
+               $ns = $title->getNamespace();
+
+               if ( $ns == NS_CATEGORY ) {
+                       $url = SpecialPage::getTitleFor( 'MassEditRegex' 
)->getLocalURL(
+                               array(
+                                       'wpPageList' => $title->getText(),
+                                       'wpPageListType' => 'categories',
+                               )
+                       );
+               } elseif (
+                       ( $ns == NS_SPECIAL )
+                       && ( $title->isSpecial( 'Whatlinkshere' ) )
+               ) {
+                       $titleParts = 
SpecialPageFactory::resolveAlias($title->getText());
+
+                       $url = SpecialPage::getTitleFor( 'MassEditRegex' 
)->getLocalURL(
+                               array(
+                                       'wpPageList' => $titleParts[1],
+                                       'wpPageListType' => 'backlinks',
+                               )
+                       );
+               } else {
+                       // No tab
+                       return true;
+               }
+
+               $links['views']['masseditregex'] = array(
+                       'class' => false,
+                       'text' => wfMessage('masseditregex-editall')->text(),
+                       'href' => $url,
+                       'context' => 'main',
+               );
+               return true;
+       }
+
+       public static function efBaseTemplateToolbox( &$tpl, &$toolbox ) {
+               global $wgTitle;
+               if ( !$wgTitle->isSpecial( 'MassEditRegex' ) ) return true;
+
+               // Hide the 'printable version' link as the shortcut key 
conflicts with
+               // the preview button.
+               unset($toolbox['print']);
+               return true;
+       }
+
+       /**
+        * Call MassEditRegex::editPage() or MassEditRegex::previewPage()
+        * @param $title
+        * @param $isPreview
+        * @param $htmlDiff
+        * @deprecated this is just a wrapper function for legacy code, do not 
use.
+        *   Instead use editPage or previewPage in MassEditRegex
+        * @return bool
+        */
+       private function editPage( $title, $isPreview, &$htmlDiff ) {
+               global $wgOut;
+               try {
+                       if ( $isPreview ) {
+                               $htmlDiff .= $this->massEditRegex->previewPage( 
$title );
+                       } else {
+                               $changeCount = $this->massEditRegex->editPage( 
$title );
+                               $wgOut->addHTML( '<li>' . $this->msg( 
'masseditregex-num-changes',
+                                       $title->getPrefixedText(), $changeCount 
)->escaped() . '</li>' );
+                       }
+                       return true;
+               } catch ( Exception $e ) {
+                       wfDebug( $e->getMessage() );
+                       return false;
+               }
+       }
+}
diff --git a/README b/README
index b946521..9faf9a0 100644
--- a/README
+++ b/README
@@ -16,4 +16,4 @@
     // Allow administrators to use Special:MassEditRegex
     $wgGroupPermissions['sysop']['masseditregex'] = true;
 
-  2. Go to [[Special:MassEditRegex]]
+  2. Go to [[Special:MassEditRegex]]
\ No newline at end of file
diff --git a/i18n/en.json b/i18n/en.json
index 9653a01..bb4bd38 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,42 +1,55 @@
 {
-    "@metadata": {
-        "authors": [
-            "Adam Nielsen"
-        ]
-    },
-    "action-masseditregex": "perform a bulk edit",
-    "masseditregex": "Mass edit using regular expressions",
-    "masseditregex-desc": "Use regular expressions to 
[[Special:MassEditRegex|edit many pages in one operation]]",
-    "masseditregextext": "Enter one or more regular expressions (one per line) 
for matching, and one or more expressions to replace each match with.\nThe 
first match-expression, if successful, will be replaced with the first 
replace-expression, and so on.\nSee 
[http://php.net/manual/en/function.preg-replace.php the PHP function 
preg_replace()] for details.",
-    "masseditregex-pagelisttxt": "Pages to edit:",
-    "masseditregex-matchtxt": "Search for:",
-    "masseditregex-replacetxt": "Replace with:",
-    "masseditregex-executebtn": "Execute",
-    "masseditregex-err-nopages": "You must specify at least one page to 
change.",
-    "masseditregex-before": "Before",
-    "masseditregex-after": "After",
-    "masseditregex-max-preview-diffs": "Preview has been limited to the first 
$1 {{PLURAL:$1|match|matches}}.",
-    "masseditregex-num-changes": "$1: $2 {{PLURAL:$2|change|changes}}",
-    "masseditregex-page-not-exists": "$1 does not exist",
-    "masseditregex-num-articles-changed": "$1 {{PLURAL:$1|page|pages}} edited",
-    "masseditregex-view-full-summary": "View full edit summary",
-    "masseditregex-hint-intro": "Here are some hints and examples for 
accomplishing common tasks:",
-    "masseditregex-hint-headmatch": "Match",
-    "masseditregex-hint-headreplace": "Replace",
-    "masseditregex-hint-headeffect": "Effect",
-    "masseditregex-hint-toappend": "Append some text to the end of the page - 
great for adding pages to categories",
-    "masseditregex-hint-remove": "Remove some text from all the pages in the 
list",
-    "masseditregex-hint-removecat": "Remove all categories from a page (note 
the escaping of the square brackets in the wikicode.)\nThe replacement values 
should not be escaped.",
-    "masseditregex-hint-renamelink": "Replace AAA with BBB in link text but 
not the link destination: [[AAA|AAA]] becomes [[AAA|BBB]]",
-    "masseditregex-listtype-intro": "This is a list of:",
-    "masseditregex-listtype-pagenames": "Page names (edit these pages)",
-    "masseditregex-listtype-pagename-prefixes": "Page name prefixes (edit 
pages having names beginning with this text)",
-    "masseditregex-listtype-categories": "Category names (edit each page 
within these categories)",
-    "masseditregex-listtype-backlinks": "Backlinks (edit pages that link to 
these ones)",
-    "masseditregex-exprnomatch": "The expression \"$1\" matched no pages.",
-    "masseditregex-badregex": "Invalid regex:",
-    "masseditregex-editfailed": "Edit failed:",
-    "masseditregex-tooltip-execute": "Apply these changes to each page",
-    "masseditregex-editall": "Edit all",
-    "right-masseditregex": "Replace page contents using regular expressions"
-}
\ No newline at end of file
+       "@metadata": {
+               "authors": [
+                       "Adam Nielsen"
+               ]
+       },
+       "action-masseditregex": "perform a bulk edit",
+       "masseditregex": "Mass edit using regular expressions",
+       "masseditregex-desc": "Use regular expressions to 
[[Special:MassEditRegex|edit many pages in one operation]]",
+       "masseditregextext": "Enter one or more regular expressions (one per 
line) for matching, and one or more expressions to replace each match 
with.\nThe first match-expression, if successful, will be replaced with the 
first replace-expression, and so on.\nSee 
[http://php.net/manual/en/function.preg-replace.php the PHP function 
preg_replace()] for details.",
+       "masseditregex-pagelisttxt": "Pages to edit:",
+       "masseditregex-matchtxt": "Search for:",
+       "masseditregex-replacetxt": "Replace with:",
+       "masseditregex-executebtn": "Execute",
+       "masseditregex-err-nopages": "You must specify at least one page to 
change.",
+       "masseditregex-before": "Before",
+       "masseditregex-after": "After",
+       "masseditregex-max-preview-diffs": "Preview has been limited to the 
first $1 {{PLURAL:$1|match|matches}}.",
+       "masseditregex-num-changes": "$1: $2 {{PLURAL:$2|change|changes}}",
+       "masseditregex-page-not-exists": "$1 does not exist",
+       "masseditregex-num-articles-changed": "$1 {{PLURAL:$1|page|pages}} 
edited",
+       "masseditregex-view-full-summary": "View full edit summary",
+       "masseditregex-hint-intro": "Here are some hints and examples for 
accomplishing common tasks:",
+       "masseditregex-hint-headmatch": "Match",
+       "masseditregex-hint-headreplace": "Replace",
+       "masseditregex-hint-headeffect": "Effect",
+       "masseditregex-hint-toappend": "Append some text to the end of the page 
- great for adding pages to categories",
+       "masseditregex-hint-remove": "Remove some text from all the pages in 
the list",
+       "masseditregex-hint-removecat": "Remove all categories from a page 
(note the escaping of the square brackets in the wikicode.)\nThe replacement 
values should not be escaped.",
+       "masseditregex-hint-renamelink": "Replace AAA with BBB in link text but 
not the link destination: [[AAA|AAA]] becomes [[AAA|BBB]]",
+       "masseditregex-listtype-intro": "This is a list of:",
+       "masseditregex-listtype-pagenames": "Page names (edit these pages)",
+       "masseditregex-listtype-pagename-prefixes": "Page name prefixes (edit 
pages having names beginning with this text)",
+       "masseditregex-listtype-categories": "Category names (edit each page 
within these categories)",
+       "masseditregex-listtype-backlinks": "Backlinks (edit pages that link to 
these ones)",
+       "masseditregex-exprnomatch": "The expression \"$1\" matched no pages.",
+       "masseditregex-badregex": "Invalid regex:",
+       "masseditregex-editfailed": "Edit failed:",
+       "masseditregex-tooltip-execute": "Apply these changes to each page",
+       "masseditregex-editall": "Edit all",
+       "right-masseditregex": "Replace page contents using regular 
expressions",
+       "masseditregex-noaccess": "User does not have access to edit this page",
+       "masseditregex-norevisions": "No revisions found",
+       "masseditregex-blocked": "User is blocked",
+       "masseditregex-readonlydb": "Database is read-only",
+       "masseditregex-js-clientside": "Execute in browser",
+       "masseditregex-js-execution": "Check this to do execution on client 
side, used when many pages causes php max execution time.",
+       "masseditregex-js-jobdone": "Job finished",
+       "masseditregex-js-editpage": "Replaced $1 occurence(s) on $2, $3 left.",
+       "masseditregex-js-working": "Working.. please wait..",
+       "masseditregex-js-pagenotexist": "$1 does not exist.",
+       "masseditregex-js-mwapi-api-error": "Error: MediaWiki API returned 
error code $1: $2",
+       "masseditregex-js-mwapi-general-error": "Error: Unknown result from 
MediaWiki API.",
+       "masseditregex-js-mwapi-unknown-error": "Error: Unknown error during 
request to MediaWiki API."
+}
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 44bd6dc..3fe67d2 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -45,4 +45,18 @@
        "masseditregex-tooltip-execute": "Used as tooltip for the Submit 
button.\n\nSee also:\n* {{msg-mw|Masseditregex-executebtn}} - label for 
button\n* {{msg-mw|Accesskey-save}} - access key",
        "masseditregex-editall": "Tab displayed on category pages - keep it 
short",
        "right-masseditregex": "{{doc-right|masseditregex}}"
+
+       "masseditregex-noaccess": "Error message displayed when user does not 
have access to a given page",
+       "masseditregex-norevisions": "Error message displayed when no revisions 
could be found for a given title",
+       "masseditregex-blocked": "Error message displayed when a user account 
is blocked",
+       "masseditregex-readonlydb": "Error message displayed when database is 
set to read only",
+       "masseditregex-js-clientside": "Tickbox that when ticked will perform 
the page iteration in the browser, sending a request to the server for each 
page.",
+       "masseditregex-js-execution": "Tooltip (HTML title) for 
masseditregex-js-clientside tickbox.  Provides more space to explain what 
'client side/in browser' means.",
+       "masseditregex-js-jobdone": "Client-side heading text when job is 
complete\nSee also:\n* {{msg-mw|Masseditregex-js-working}}",
+       "masseditregex-js-editpage": "text displayed for each page where 
replacement has been done:\nParameters:\n* $1 number of occurences\n* $2 page 
title\n* $3 how many pages remaining.",
+       "masseditregex-js-working": "text displayed in heading when job is in 
progress\nSee also:\n* {{msg-mw|Masseditregex-js-jobdone}}",
+       "masseditregex-js-pagenotexist": "text displayed when page title does 
not exist\nParameters:\n* $1 page title",
+       "masseditregex-js-mwapi-api-error": "error message displayed when 
mediawiki api call fails\nParameters:\n* $1 response error code\n* $2 response 
error info",
+       "masseditregex-js-mwapi-general-error": "error message displayed when 
general error occurred on mediawiki api",
+       "masseditregex-js-mwapi-unknown-error": "error message displayed when 
an unknown error occurred during a mediawiki api request",
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/98094
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0946f14eda03d7435151a0860dcb2d0fb380732b
Gerrit-PatchSet: 11
Gerrit-Project: mediawiki/extensions/MassEditRegex
Gerrit-Branch: master
Gerrit-Owner: Netbrain <[email protected]>
Gerrit-Reviewer: Malvineous <[email protected]>
Gerrit-Reviewer: Netbrain <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to