MediaWiki  1.34.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->addDescription( "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;
RUN_MAINTENANCE_IF_MAIN
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:39
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:539
ReplaceAll\showFileFormat
showFileFormat()
Definition: replaceAll.php:199
ReplaceAll\$useRegex
$useRegex
Definition: replaceAll.php:53
MWNamespace\getCanonicalNamespaces
static getCanonicalNamespaces()
Returns array of all defined namespaces with their canonical (English) names.
Definition: MWNamespace.php:145
ReplaceAll\$rename
$rename
Definition: replaceAll.php:57
ReplaceAll\getReply
getReply( $question)
Definition: replaceAll.php:309
Maintenance\addDescription
addDescription( $text)
Set the description text.
Definition: Maintenance.php:348
ReplaceAll\$category
$category
Definition: replaceAll.php:51
true
return true
Definition: router.php:92
ReplaceAll\$target
$target
Definition: replaceAll.php:48
Maintenance\readconsole
static readconsole( $prompt='> ')
Prompt the console for input.
Definition: Maintenance.php:1609
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()
Do the actual work.All child classes will need to implement thisbool|null|void True for success,...
Definition: replaceAll.php:347
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
Maintenance
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:82
$res
$res
Definition: testCompression.php:52
ReplaceAll\__construct
__construct()
Default constructor.
Definition: replaceAll.php:59
ReplaceAll\$prefix
$prefix
Definition: replaceAll.php:52
NS_MAIN
const NS_MAIN
Definition: Defines.php:60
MWException
MediaWiki exception.
Definition: MWException.php:26
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
Maintenance\addOption
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
Definition: Maintenance.php:267
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:638
ReplaceAll\getSummary
getSummary( $target, $replacement)
Definition: replaceAll.php:176
$title
$title
Definition: testCompression.php:34
ReplaceAll\useRegex
useRegex()
Definition: replaceAll.php:263
$line
$line
Definition: cdb.php:59
ReplaceAll\listNamespaces
listNamespaces()
Definition: replaceAll.php:187
ReplaceAll\getRename
getRename()
Definition: replaceAll.php:267
ReplaceAll\shouldContinueByDefault
shouldContinueByDefault()
Definition: replaceAll.php:166
ReplaceAll\$user
$user
Definition: replaceAll.php:47
ReplaceAll\$defaultContinue
$defaultContinue
Definition: replaceAll.php:55
ReplaceAll\getNamespaces
getNamespaces()
Definition: replaceAll.php:222
ReplaceAll\listTitles
listTitles( $titles, $target, $replacement, $regex, $rename)
Definition: replaceAll.php:271
ReplaceTextSearch\getReplacedTitle
static getReplacedTitle(Title $title, $search, $replacement, $regex)
Do a replacement on a title.
Definition: ReplaceTextSearch.php:178
Maintenance\getOption
getOption( $name, $default=null)
Get an option, or return the default.
Definition: Maintenance.php:302
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
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:50
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:319
$wgShowExceptionDetails
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
Definition: DefaultSettings.php:6310
$IP
$IP
Definition: replaceAll.php:32
Maintenance\error
error( $err, $die=0)
Throw an error to the user.
Definition: Maintenance.php:481
Maintenance\output
output( $out, $channel=null)
Throw some output to the user.
Definition: Maintenance.php:453
ReplaceAll\getReplacements
getReplacements()
Definition: replaceAll.php:133
Maintenance\hasOption
hasOption( $name)
Checks to see if a particular option exists.
Definition: Maintenance.php:288
Maintenance\getArg
getArg( $argId=0, $default=null)
Get an argument.
Definition: Maintenance.php:371
ReplaceAll\$namespaces
$namespaces
Definition: replaceAll.php:50
ReplaceTextSearch\getMatchingTitles
static getMatchingTitles( $str, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:122