MediaWiki  1.33.0
replaceAll.php
Go to the documentation of this file.
1 #!/usr/bin/php
2 <?php
31 // @codingStandardsIgnoreStart
32 $IP = getenv( "MW_INSTALL_PATH" ) ?: __DIR__ . "/../../..";
33 if ( !is_readable( "$IP/maintenance/Maintenance.php" ) ) {
34  die( "MW_INSTALL_PATH needs to be set to your MediaWiki installation.\n" );
35 }
36 require_once ( "$IP/maintenance/Maintenance.php" );
37 // @codingStandardsIgnoreEnd
38 
46 class ReplaceAll extends Maintenance {
47  private $user;
48  private $target;
49  private $replacement;
50  private $namespaces;
51  private $category;
52  private $prefix;
53  private $useRegex;
54  private $titles;
56  private $doAnnounce;
57  private $rename;
58 
59  public function __construct() {
60  parent::__construct();
61  $this->mDescription = "CLI utility to replace text wherever it is " .
62  "found in the wiki.";
63 
64  $this->addArg( "target", "Target text to find.", false );
65  $this->addArg( "replace", "Text to replace.", false );
66 
67  $this->addOption( "dry-run", "Only find the texts, don't replace.",
68  false, false, 'n' );
69  $this->addOption( "regex", "This is a regex (false).",
70  false, false, 'r' );
71  $this->addOption( "user", "The user to attribute this to (uid 1).",
72  false, true, 'u' );
73  $this->addOption( "yes", "Skip all prompts with an assumed 'yes'.",
74  false, false, 'y' );
75  $this->addOption( "summary", "Alternate edit summary. (%r is where to " .
76  " place the replacement text, %f the text to look for.)",
77  false, true, 's' );
78  $this->addOption( "nsall", "Search all canonical namespaces (false). " .
79  "If true, this option overrides the ns option.", false, false, 'a' );
80  $this->addOption( "ns", "Comma separated namespaces to search in " .
81  "(Main) .", false, true );
82  $this->addOption( "replacements", "File containing the list of " .
83  "replacements to be made. Fields in the file are tab-separated. " .
84  "See --show-file-format for more information.", false, true, "f" );
85  $this->addOption( "show-file-format", "Show a description of the " .
86  "file format to use with --replacements.", false, false );
87  $this->addOption( "no-announce", "Do not announce edits on Special:RecentChanges or " .
88  "watchlists.", false, false, "m" );
89  $this->addOption( "debug", "Display replacements being made.", false, false );
90  $this->addOption( "listns", "List out the namespaces on this wiki.",
91  false, false );
92  $this->addOption( 'rename', "Rename page titles instead of replacing contents.",
93  false, false );
94 
95  // MW 1.28
96  if ( method_exists( $this, 'requireExtension' ) ) {
97  $this->requireExtension( 'Replace Text' );
98  }
99  }
100 
101  private function getUser() {
102  $userReplacing = $this->getOption( "user", 1 );
103 
104  $user = is_numeric( $userReplacing ) ?
105  User::newFromId( $userReplacing ) :
106  User::newFromName( $userReplacing );
107 
108  if ( get_class( $user ) !== 'User' ) {
109  $this->error(
110  "Couldn't translate '$userReplacing' to a user.", true
111  );
112  }
113 
114  return $user;
115  }
116 
117  private function getTarget() {
118  $ret = $this->getArg( 0 );
119  if ( !$ret ) {
120  $this->error( "You have to specify a target.", true );
121  }
122  return [ $ret ];
123  }
124 
125  private function getReplacement() {
126  $ret = $this->getArg( 1 );
127  if ( !$ret ) {
128  $this->error( "You have to specify replacement text.", true );
129  }
130  return [ $ret ];
131  }
132 
133  private function getReplacements() {
134  $file = $this->getOption( "replacements" );
135  if ( !$file ) {
136  return false;
137  }
138 
139  if ( !is_readable( $file ) ) {
140  throw new MWException( "File does not exist or is not readable: "
141  . "$file\n" );
142  }
143 
144  $handle = fopen( $file, "r" );
145  if ( $handle === false ) {
146  throw new MWException( "Trouble opening file: $file\n" );
147  return false;
148  }
149 
150  $this->defaultContinue = true;
151  // @codingStandardsIgnoreStart
152  while ( ( $line = fgets( $handle ) ) !== false ) {
153  // @codingStandardsIgnoreEnd
154  $field = explode( "\t", substr( $line, 0, -1 ) );
155  if ( !isset( $field[1] ) ) {
156  continue;
157  }
158 
159  $this->target[] = $field[0];
160  $this->replacement[] = $field[1];
161  $this->useRegex[] = isset( $field[2] ) ? true : false;
162  }
163  return true;
164  }
165 
166  private function shouldContinueByDefault() {
167  if ( !is_bool( $this->defaultContinue ) ) {
168  $this->defaultContinue =
169  $this->getOption( "yes" ) ?
170  true :
171  false;
172  }
173  return $this->defaultContinue;
174  }
175 
176  private function getSummary( $target, $replacement ) {
177  $msg = wfMessage( 'replacetext_editsummary', $target, $replacement )->
178  plain();
179  if ( $this->getOption( "summary" ) !== null ) {
180  $msg = str_replace( [ '%f', '%r' ],
181  [ $this->target, $this->replacement ],
182  $this->getOption( "summary" ) );
183  }
184  return $msg;
185  }
186 
187  private function listNamespaces() {
188  echo "Index\tNamespace\n";
190  ksort( $nsList );
191  foreach ( $nsList as $int => $val ) {
192  if ( $val == "" ) {
193  $val = "(main)";
194  }
195  echo " $int\t$val\n";
196  }
197  }
198 
199  private function showFileFormat() {
200 echo <<<EOF
201 
202 The format of the replacements file is tab separated with three fields.
203 Any line that does not have a tab is ignored and can be considered a comment.
204 
205 Fields are:
206 
207  1. String to search for.
208  2. String to replace found text with.
209  3. (optional) The presence of this field indicates that the previous two
210  are considered a regular expression.
211 
212 Example:
213 
214 This is a comment
215 TARGET REPLACE
216 regex(p*) Count the Ps; \\1 true
217 
218 
219 EOF;
220  }
221 
222  private function getNamespaces() {
223  $nsall = $this->getOption( "nsall" );
224  $ns = $this->getOption( "ns" );
225  if ( !$nsall && !$ns ) {
226  $namespaces = [ NS_MAIN ];
227  } else {
229  $canonical[NS_MAIN] = "_";
230  $namespaces = array_flip( $canonical );
231  if ( !$nsall ) {
232  $namespaces = array_map(
233  function ( $n ) use ( $canonical, $namespaces ) {
234  if ( is_numeric( $n ) ) {
235  if ( isset( $canonical[ $n ] ) ) {
236  return intval( $n );
237  }
238  } else {
239  if ( isset( $namespaces[ $n ] ) ) {
240  return $namespaces[ $n ];
241  }
242  }
243  return null;
244  }, explode( ",", $ns ) );
245  $namespaces = array_filter(
246  $namespaces,
247  function ( $val ) {
248  return $val !== null;
249  } );
250  }
251  }
252  return $namespaces;
253  }
254 
255  private function getCategory() {
256  return null;
257  }
258 
259  private function getPrefix() {
260  return null;
261  }
262 
263  private function useRegex() {
264  return [ $this->getOption( "regex" ) ];
265  }
266 
267  private function getRename() {
268  return $this->hasOption( 'rename' );
269  }
270 
271  private function listTitles( $titles, $target, $replacement, $regex, $rename ) {
272  foreach ( $titles as $title ) {
273  if ( $rename ) {
275  // Implicit conversion of objects to strings
276  $this->output( "$title -> $newTitle\n" );
277  } else {
278  echo "$title\n";
279  }
280  }
281  }
282 
284  foreach ( $titles as $title ) {
285  $params = [
286  'target_str' => $target,
287  'replacement_str' => $replacement,
288  'use_regex' => $useRegex,
289  'user_id' => $this->user->getId(),
290  'edit_summary' => $this->getSummary( $target, $replacement ),
291  'doAnnounce' => $this->doAnnounce
292  ];
293 
294  if ( $rename ) {
295  $params[ 'move_page' ] = true;
296  $params[ 'create_redirect' ] = false;
297  $params[ 'watch_page' ] = false;
298  }
299 
300  echo "Replacing on $title... ";
301  $job = new ReplaceTextJob( $title, $params );
302  if ( $job->run() !== true ) {
303  $this->error( "Trouble on the page '$title'." );
304  }
305  echo "done.\n";
306  }
307  }
308 
309  private function getReply( $question ) {
310  $reply = "";
311  if ( $this->shouldContinueByDefault() ) {
312  return true;
313  }
314  while ( $reply !== "y" && $reply !== "n" ) {
315  $reply = $this->readconsole( "$question (Y/N) " );
316  $reply = substr( strtolower( $reply ), 0, 1 );
317  }
318  return $reply === "y";
319  }
320 
321  private function localSetup() {
322  if ( $this->getOption( "listns" ) ) {
323  $this->listNamespaces();
324  return false;
325  }
326  if ( $this->getOption( "show-file-format" ) ) {
327  $this->showFileFormat();
328  return false;
329  }
330  $this->user = $this->getUser();
331  if ( !$this->getReplacements() ) {
332  $this->target = $this->getTarget();
333  $this->replacement = $this->getReplacement();
334  $this->useRegex = $this->useRegex();
335  }
336  $this->namespaces = $this->getNamespaces();
337  $this->category = $this->getCategory();
338  $this->prefix = $this->getPrefix();
339  $this->rename = $this->getRename();
340 
341  return true;
342  }
343 
347  public function execute() {
350 
351  $this->doAnnounce = true;
352  if ( !$this->localSetup() ) {
353  return;
354  }
355 
356  if ( $this->namespaces === [] ) {
357  $this->error( "No matching namespaces.", true );
358  }
359 
360  foreach ( array_keys( $this->target ) as $index ) {
361  $target = $this->target[$index];
362  $replacement = $this->replacement[$index];
363  $useRegex = $this->useRegex[$index];
364 
365  if ( $this->getOption( "debug" ) ) {
366  echo "Replacing '$target' with '$replacement'";
367  if ( $useRegex ) {
368  echo " as regular expression.";
369  }
370  echo "\n";
371  }
372 
373  if ( $this->rename ) {
375  $target,
376  $this->namespaces,
377  $this->category,
378  $this->prefix,
379  $useRegex
380  );
381  } else {
383  $target,
384  $this->namespaces,
385  $this->category,
386  $this->prefix,
387  $useRegex
388  );
389  }
390 
392 
393  if ( count( $titles ) === 0 ) {
394  $this->error( 'No targets found to replace.', true );
395  }
396 
397  if ( $this->getOption( "dry-run" ) ) {
398  $this->listTitles( $titles, $target, $replacement, $useRegex, $this->rename );
399  continue;
400  }
401 
402  if (
403  !$this->shouldContinueByDefault() &&
404  $this->listTitles( $titles, $target, $replacement, $useRegex, $this->rename )
405  ) {
406  if ( !$this->getReply( 'Replace instances on these pages?' ) ) {
407  return;
408  }
409  }
410 
411  $comment = "";
412  if ( $this->getOption( "user", null ) === null ) {
413  $comment = " (Use --user to override)";
414  }
415  if ( $this->getOption( "no-announce", false ) ) {
416  $this->doAnnounce = false;
417  }
418  if ( !$this->getReply(
419  "Attribute changes to the user '{$this->user}'?$comment"
420  ) ) {
421  return;
422  }
423 
424  $this->replaceTitles(
425  $titles, $target, $replacement, $useRegex, $this->rename
426  );
427  }
428  }
429 }
430 
431 $maintClass = "ReplaceAll";
432 require_once RUN_MAINTENANCE_IF_MAIN;
ReplaceAll\$titles
$titles
Definition: replaceAll.php:54
$maintClass
$maintClass
Definition: replaceAll.php:412
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:609
file
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing we can concentrate it all in an extension file
Definition: hooks.txt:91
ReplaceAll\showFileFormat
showFileFormat()
Definition: replaceAll.php:199
ReplaceAll\$useRegex
$useRegex
Definition: replaceAll.php:53
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
ReplaceAll\$rename
$rename
Definition: replaceAll.php:57
ReplaceAll\getReply
getReply( $question)
Definition: replaceAll.php:309
captcha-old.count
count
Definition: captcha-old.py:249
ReplaceAll\$category
$category
Definition: replaceAll.php:51
ReplaceAll\$target
$target
Definition: replaceAll.php:48
Maintenance\readconsole
static readconsole( $prompt='> ')
Prompt the console for input.
Definition: Maintenance.php:1582
ReplaceAll\getTarget
getTarget()
Definition: replaceAll.php:117
ReplaceAll\$doAnnounce
$doAnnounce
Definition: replaceAll.php:56
ReplaceAll\getCategory
getCategory()
Definition: replaceAll.php:255
ReplaceAll\getReplacement
getReplacement()
Definition: replaceAll.php:125
ReplaceAll\execute
execute()
@inheritDoc
Definition: replaceAll.php:347
RUN_MAINTENANCE_IF_MAIN
require_once RUN_MAINTENANCE_IF_MAIN
Definition: maintenance.txt:50
$params
$params
Definition: styleTest.css.php:44
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
a
</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre >< span ></span >< span class="kd"> var</span >< span class="nx"> a</span >< span class="p"></span ></pre ></div > ! end ! test Multiline< source/> in lists !input *< source > a b</source > *foo< source > a b</source > ! html< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! html tidy< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! end ! test Custom attributes !input< source lang="javascript" id="foo" class="bar" dir="rtl" style="font-size: larger;"> var a
Definition: parserTests.txt:85
$res
$res
Definition: database.txt:21
Maintenance
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: maintenance.txt:39
is
This document provides an overview of the usage of PageUpdater and that is
Definition: pageupdater.txt:3
ReplaceAll\__construct
__construct()
Default constructor.
Definition: replaceAll.php:59
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
ReplaceAll\$prefix
$prefix
Definition: replaceAll.php:52
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
namespaces
to move a page</td >< td > &*You are moving the page across namespaces
Definition: All_system_messages.txt:2670
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
user
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Definition: distributors.txt:9
ReplaceAll\getPrefix
getPrefix()
Definition: replaceAll.php:259
ReplaceAll\localSetup
localSetup()
Definition: replaceAll.php:321
ReplaceTextJob
Background job to replace text in a given page.
Definition: ReplaceTextJob.php:29
not
if not
Definition: COPYING.txt:307
Maintenance\addOption
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
Definition: Maintenance.php:248
TitleArrayFromResult
Definition: TitleArrayFromResult.php:29
Maintenance\requireExtension
requireExtension( $name)
Indicate that the specified extension must be loaded before the script can run.
Definition: Maintenance.php:619
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
ReplaceAll\getSummary
getSummary( $target, $replacement)
Definition: replaceAll.php:176
ReplaceAll\useRegex
useRegex()
Definition: replaceAll.php:263
$line
$line
Definition: cdb.php:59
ReplaceAll\listNamespaces
listNamespaces()
Definition: replaceAll.php:187
replace
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place replace
Definition: hooks.txt:2220
ReplaceAll\getRename
getRename()
Definition: replaceAll.php:267
ReplaceAll\shouldContinueByDefault
shouldContinueByDefault()
Definition: replaceAll.php:166
ReplaceAll\$user
$user
Definition: replaceAll.php:47
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1985
captcha-old.p
p
Definition: captcha-old.py:275
format
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1644
plain
either a plain
Definition: hooks.txt:2046
ReplaceAll\$defaultContinue
$defaultContinue
Definition: replaceAll.php:55
ReplaceAll\getNamespaces
getNamespaces()
Definition: replaceAll.php:222
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
ReplaceAll\listTitles
listTitles( $titles, $target, $replacement, $regex, $rename)
Definition: replaceAll.php:271
and
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
ReplaceTextSearch\getReplacedTitle
static getReplacedTitle(Title $title, $search, $replacement, $regex)
Do a replacement on a title.
Definition: ReplaceTextSearch.php:176
Maintenance\getOption
getOption( $name, $default=null)
Get an option, or return the default.
Definition: Maintenance.php:283
ReplaceAll\getUser
getUser()
Definition: replaceAll.php:101
ReplaceAll\replaceTitles
replaceTitles( $titles, $target, $replacement, $useRegex, $rename)
Definition: replaceAll.php:283
ReplaceAll\$replacement
$replacement
Definition: replaceAll.php:49
ReplaceTextSearch\doSearchQuery
static doSearchQuery( $search, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:34
are
The ContentHandler facility adds support for arbitrary content types on wiki instead of relying on wikitext for everything It was introduced in MediaWiki Each kind of and so on Built in content types are
Definition: contenthandler.txt:5
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:49
ReplaceAll
Maintenance script that replaces text in pages.
Definition: replaceAll.php:46
Maintenance\addArg
addArg( $arg, $description, $required=true)
Add some args that are needed.
Definition: Maintenance.php:300
$wgShowExceptionDetails
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
Definition: DefaultSettings.php:6288
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
MWNamespace\getCanonicalNamespaces
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
Definition: MWNamespace.php:231
$IP
$IP
Definition: replaceAll.php:32
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1985
Maintenance\error
error( $err, $die=0)
Throw an error to the user.
Definition: Maintenance.php:462
Maintenance\output
output( $out, $channel=null)
Throw some output to the user.
Definition: Maintenance.php:434
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
that
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if that
Definition: deferred.txt:11
ReplaceAll\getReplacements
getReplacements()
Definition: replaceAll.php:133
Maintenance\hasOption
hasOption( $name)
Checks to see if a particular option exists.
Definition: Maintenance.php:269
Maintenance\getArg
getArg( $argId=0, $default=null)
Get an argument.
Definition: Maintenance.php:352
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
ReplaceAll\$namespaces
$namespaces
Definition: replaceAll.php:50
ReplaceTextSearch\getMatchingTitles
static getMatchingTitles( $str, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:120