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