mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/ReplaceText
synced 2024-11-27 16:10:15 +00:00
Add support for slots other than 'main'
This commit adds support for replacing text in multiple content slots. The names of all the slots that contain the target string are shown on the special page, and the user can select or deselect which slots they want to edit. Edits to different slots for the same page are bundled into a multi-content revision. If a replace is performed through the 'replaceAll.php' maintenance script, all slots are taken into account. Change-Id: Ic4f36fa76e1eaeac8200c60f196c53c2ed78fa45
This commit is contained in:
parent
9c5199b4f9
commit
ed9c752309
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Replace Text",
|
||||
"version": "1.6",
|
||||
"version": "1.7",
|
||||
"author": [
|
||||
"Yaron Koren",
|
||||
"Niklas Laxström",
|
||||
|
|
84
src/Job.php
84
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 ) . "<br /><small>$context</small>";
|
||||
if ( $role === SlotRecord::MAIN ) {
|
||||
$labelText = $linkRenderer->makeLink( $title, null ) . "<br /><small>$context</small>";
|
||||
} else {
|
||||
$labelText = $linkRenderer->makeLink( $title, null ) . " ($role) <br /><small>$context</small>";
|
||||
}
|
||||
$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 );
|
||||
|
|
Loading…
Reference in a new issue