diff --git a/extension.json b/extension.json
index eb054258..7a323606 100644
--- a/extension.json
+++ b/extension.json
@@ -1,6 +1,6 @@
{
"name": "Replace Text",
- "version": "1.6",
+ "version": "1.7",
"author": [
"Yaron Koren",
"Niklas Laxström",
diff --git a/src/Job.php b/src/Job.php
index 04826585..66e4381c 100644
--- a/src/Job.php
+++ b/src/Job.php
@@ -24,8 +24,8 @@ namespace MediaWiki\Extension\ReplaceText;
use CommentStoreComment;
use Job as JobParent;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Revision\SlotRecord;
use RequestContext;
+use TextContent;
use Title;
use User;
use WatchAction;
@@ -111,43 +111,81 @@ class Job extends JobParent {
}
} else {
- if ( $this->title->getContentModel() !== CONTENT_MODEL_WIKITEXT ) {
- $this->error = 'replaceText: Wiki page "' .
- $this->title->getPrefixedDBkey() . '" does not hold regular wikitext.';
- return false;
- }
$wikiPage = new WikiPage( $this->title );
- $wikiPageContent = $wikiPage->getContent();
- if ( $wikiPageContent === null ) {
+ $latestRevision = $wikiPage->getRevisionRecord();
+
+ if ( $latestRevision === null ) {
$this->error =
- 'replaceText: No contents found for wiki page at "' . $this->title->getPrefixedDBkey() . '."';
+ 'replaceText: No revision found for wiki page at "' . $this->title->getPrefixedDBkey() . '".';
return false;
}
- $article_text = $wikiPageContent->getNativeData();
- $target_str = $this->params['target_str'];
- $replacement_str = $this->params['replacement_str'];
- $num_matches = 0;
-
- if ( $this->params['use_regex'] ) {
- $new_text =
- preg_replace( '/' . $target_str . '/Uu', $replacement_str, $article_text, -1, $num_matches );
+ if ( isset( $this->params['roles'] ) ) {
+ $slotRoles = $this->params['roles'];
} else {
- $new_text = str_replace( $target_str, $replacement_str, $article_text, $num_matches );
+ $slotRoles = $latestRevision->getSlotRoles();
}
- // If there's at least one replacement, modify the page,
+ $revisionSlots = $latestRevision->getSlots();
+ $updater = $wikiPage->newPageUpdater( $current_user );
+ $hasMatches = false;
+
+ foreach ( $slotRoles as $role ) {
+ if ( !$revisionSlots->hasSlot( $role ) ) {
+ $this->error =
+ 'replaceText: Slot "' . $role .
+ '" does not exist for wiki page "' . $this->title->getPrefixedDBkey() . '".';
+ return false;
+ }
+
+ $slotContent = $revisionSlots->getContent( $role );
+
+ if ( $slotContent->getModel() !== CONTENT_MODEL_WIKITEXT ) {
+ // The slot does not contain wikitext, give an error.
+ $this->error =
+ 'replaceText: Slot "' . $role .
+ '" does not hold regular wikitext for wiki page "' . $this->title->getPrefixedDBkey() . '".';
+ return false;
+ }
+
+ if ( !( $slotContent instanceof TextContent ) ) {
+ // Sanity check: Does the slot actually contain TextContent?
+ $this->error =
+ 'replaceText: Slot "' . $role .
+ '" does not hold regular wikitext for wiki page "' . $this->title->getPrefixedDBkey() . '".';
+ return false;
+ }
+
+ $slot_text = $slotContent->getText();
+
+ $target_str = $this->params['target_str'];
+ $replacement_str = $this->params['replacement_str'];
+ $num_matches = 0;
+
+ if ( $this->params['use_regex'] ) {
+ $new_text =
+ preg_replace( '/' . $target_str . '/Uu', $replacement_str, $slot_text, -1, $num_matches );
+ } else {
+ $new_text = str_replace( $target_str, $replacement_str, $slot_text, $num_matches );
+ }
+
+ // If there's at least one replacement, modify the slot.
+ if ( $num_matches > 0 ) {
+ $hasMatches = true;
+ $updater->setContent( $role, new WikitextContent( $new_text ) );
+ }
+ }
+
+ // If at least one slot is edited, modify the page,
// using the passed-in edit summary.
- if ( $num_matches > 0 ) {
- $updater = $wikiPage->newPageUpdater( $current_user );
- $updater->setContent( SlotRecord::MAIN, new WikitextContent( $new_text ) );
+ if ( $hasMatches ) {
$edit_summary = CommentStoreComment::newUnsavedComment( $this->params['edit_summary'] );
$flags = EDIT_MINOR;
if ( $permissionManager->userHasRight( $current_user, 'bot' ) ) {
$flags |= EDIT_FORCE_BOT;
}
if ( isset( $this->params['doAnnounce'] ) &&
- !$this->params['doAnnounce'] ) {
+ !$this->params['doAnnounce'] ) {
$flags |= EDIT_SUPPRESS_RC;
# fixme log this action
}
diff --git a/src/Search.php b/src/Search.php
index b1d74092..09ba9b0a 100644
--- a/src/Search.php
+++ b/src/Search.php
@@ -40,7 +40,7 @@ class Search {
$dbr = wfGetDB( DB_REPLICA );
$tables = [ 'page', 'revision', 'text', 'slots', 'content' ];
- $vars = [ 'page_id', 'page_namespace', 'page_title', 'old_text' ];
+ $vars = [ 'page_id', 'page_namespace', 'page_title', 'old_text', 'slot_role_id' ];
if ( $use_regex ) {
$comparisonCond = self::regexCond( $dbr, 'old_text', $search );
} else {
diff --git a/src/SpecialReplaceText.php b/src/SpecialReplaceText.php
index 854c8eea..1d7ade48 100644
--- a/src/SpecialReplaceText.php
+++ b/src/SpecialReplaceText.php
@@ -23,6 +23,7 @@ use ErrorPageError;
use Html;
use JobQueueGroup;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\SlotRecord;
use OOUI;
use PermissionsError;
use SpecialPage;
@@ -276,22 +277,36 @@ class SpecialReplaceText extends SpecialPage {
}
$jobs = [];
+ $pages_to_edit = [];
// These are OOUI checkboxes - we don't determine whether they
// were checked by their value (which will be null), but rather
// by whether they were submitted at all.
foreach ( $request->getValues() as $key => $value ) {
- if ( $key !== 'replace' && $key !== 'use_regex' ) {
- if ( strpos( $key, 'move-' ) !== false ) {
- $title = Title::newFromID( (int)substr( $key, 5 ) );
- $replacement_params['move_page'] = true;
- } else {
- $title = Title::newFromID( (int)$key );
- }
+ if ( $key === 'replace' || $key === 'use_regex' ) {
+ continue;
+ }
+ if ( strpos( $key, 'move-' ) !== false ) {
+ $title = Title::newFromID( (int)substr( $key, 5 ) );
+ $replacement_params['move_page'] = true;
if ( $title !== null ) {
$jobs[] = new Job( $title, $replacement_params );
}
+ unset( $replacement_params['move_page'] );
+ } else {
+ // Bundle multiple edits to the same page for a different slot into one job
+ list( $page_id, $role ) = explode( '|', $key, 2 );
+ $pages_to_edit[$page_id][] = $role;
}
}
+ // Create jobs for the bundled page edits
+ foreach ( $pages_to_edit as $page_id => $roles ) {
+ $title = Title::newFromID( (int)$page_id );
+ $replacement_params['roles'] = $roles;
+ if ( $title !== null ) {
+ $jobs[] = new Job( $title, $replacement_params );
+ }
+ unset( $replacement_params['roles'] );
+ }
return $jobs;
}
@@ -318,9 +333,11 @@ class SpecialReplaceText extends SpecialPage {
if ( $title == null ) {
continue;
}
+
// @phan-suppress-next-line SecurityCheck-ReDoS target could be a regex from user
$context = $this->extractContext( $row->old_text, $this->target, $this->use_regex );
- $titles_for_edit[] = [ $title, $context ];
+ $role = $this->extractRole( (int)$row->slot_role_id );
+ $titles_for_edit[] = [ $title, $context, $role ];
}
return $titles_for_edit;
@@ -687,12 +704,16 @@ class SpecialReplaceText extends SpecialPage {
/**
* @var $title Title
*/
- list( $title, $context ) = $title_and_context;
+ list( $title, $context, $role ) = $title_and_context;
$checkbox = new OOUI\CheckboxInputWidget( [
- 'name' => $title->getArticleID(),
+ 'name' => $title->getArticleID() . "|" . $role,
'selected' => true
] );
- $labelText = $linkRenderer->makeLink( $title, null ) . "
$context";
+ if ( $role === SlotRecord::MAIN ) {
+ $labelText = $linkRenderer->makeLink( $title, null ) . "
$context";
+ } else {
+ $labelText = $linkRenderer->makeLink( $title, null ) . " ($role)
$context";
+ }
$checkboxLabel = new OOUI\LabelWidget( [
'label' => new OOUI\HtmlSnippet( $labelText )
] );
@@ -823,6 +844,17 @@ class SpecialReplaceText extends SpecialPage {
return $context;
}
+ /**
+ * Extracts the role name
+ *
+ * @param int $role_id
+ * @return string
+ */
+ private function extractRole( $role_id ) {
+ $roleStore = MediaWikiServices::getInstance()->getSlotRoleStore();
+ return $roleStore->getName( $role_id );
+ }
+
private function convertWhiteSpaceToHTML( $message ) {
$msg = htmlspecialchars( $message );
$msg = preg_replace( '/^ /m', ' ', $msg );