diff options
Diffstat (limited to 'www/wiki/extensions/ReplaceText/maintenance/replaceAll.php')
-rwxr-xr-x | www/wiki/extensions/ReplaceText/maintenance/replaceAll.php | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/www/wiki/extensions/ReplaceText/maintenance/replaceAll.php b/www/wiki/extensions/ReplaceText/maintenance/replaceAll.php new file mode 100755 index 00000000..f1078bdc --- /dev/null +++ b/www/wiki/extensions/ReplaceText/maintenance/replaceAll.php @@ -0,0 +1,408 @@ +#!/usr/bin/php +<?php +/** + * Insert jobs into the job queue to replace text bits. + * Or execute immediately... your choice. + * + * Copyright © 2014 NicheWork, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @category Maintenance + * @package ReplaceText + * @author Mark A. Hershberger <mah@nichework.com> + * @license http://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later + * @link https://www.mediawiki.org/wiki/Extension:Replace_Text + * + */ +// @codingStandardsIgnoreStart +$IP = getenv( "MW_INSTALL_PATH" ) ? getenv( "MW_INSTALL_PATH" ) : __DIR__ . "/../../.."; +if ( !is_readable( "$IP/maintenance/Maintenance.php" ) ) { + die( "MW_INSTALL_PATH needs to be set to your MediaWiki installation.\n" ); +} +require_once ( "$IP/maintenance/Maintenance.php" ); +// @codingStandardsIgnoreEnd + +/** + * Maintenance script that replaces text in pages + * + * @ingroup Maintenance + * @SuppressWarnings(StaticAccess) + * @SuppressWarnings(LongVariable) + */ +class ReplaceAll extends Maintenance { + private $user; + private $target; + private $replacement; + private $namespaces; + private $category; + private $prefix; + private $useRegex; + private $titles; + private $defaultContinue; + private $doAnnounce; + + public function __construct() { + parent::__construct(); + $this->mDescription = "CLI utility to replace text wherever it is ". + "found in the wiki."; + + $this->addArg( "target", "Target text to find.", false ); + $this->addArg( "replace", "Text to replace.", false ); + + $this->addOption( "dry-run", "Only find the texts, don't replace.", + false, false, 'n' ); + $this->addOption( "regex", "This is a regex (false).", + false, false, 'r' ); + $this->addOption( "user", "The user to attribute this to (uid 1).", + false, true, 'u' ); + $this->addOption( "yes", "Skip all prompts with an assumed 'yes'.", + false, false, 'y' ); + $this->addOption( "summary", "Alternate edit summary. (%r is where to ". + " place the replacement text, %f the text to look for.)", + false, true, 's' ); + $this->addOption( "nsall", "Search all canonical namespaces (false). " . + "If true, this option overrides the ns option.", false, false, 'a' ); + $this->addOption( "ns", "Comma separated namespaces to search in " . + "(Main) .", false, true ); + $this->addOption( "replacements", "File containing the list of " . + "replacements to be made. Fields in the file are tab-separated. " . + "See --show-file-format for more information.", false, true, "f" ); + $this->addOption( "show-file-format", "Show a description of the " . + "file format to use with --replacements.", false, false ); + $this->addOption( "no-announce", "Do not announce edits on Special:RecentChanges or " . + "watchlists.", false, false, "m" ); + $this->addOption( "debug", "Display replacements being made.", false, false ); + $this->addOption( "listns", "List out the namespaces on this wiki.", + false, false ); + + // MW 1.28 + if ( method_exists( $this, 'requireExtension' ) ) { + $this->requireExtension( 'Replace Text' ); + } + } + + private function getUser() { + $userReplacing = $this->getOption( "user", 1 ); + + $user = is_numeric( $userReplacing ) ? + User::newFromId( $userReplacing ) : + User::newFromName( $userReplacing ); + + if ( get_class( $user ) !== 'User' ) { + $this->error( + "Couldn't translate '$userReplacing' to a user.", true + ); + } + + return $user; + } + + private function getTarget() { + $ret = $this->getArg( 0 ); + if ( !$ret ) { + $this->error( "You have to specify a target.", true ); + } + return [ $ret ]; + } + + private function getReplacement() { + $ret = $this->getArg( 1 ); + if ( !$ret ) { + $this->error( "You have to specify replacement text.", true ); + } + return [ $ret ]; + } + + private function getReplacements() { + $file = $this->getOption( "replacements" ); + if ( !$file ) { + return false; + } + + if ( !is_readable( $file ) ) { + throw new MWException( "File does not exist or is not readable: " + . "$file\n" ); + } + + $handle = fopen( $file, "r" ); + if ( $handle === false ) { + throw new MWException( "Trouble opening file: $file\n" ); + return false; + } + + $this->defaultContinue = true; + // @codingStandardsIgnoreStart + while ( ( $line = fgets( $handle ) ) !== false ) { + // @codingStandardsIgnoreEnd + $field = explode( "\t", substr( $line, 0, -1 ) ); + if ( !isset( $field[1] ) ) { + continue; + } + + $this->target[] = $field[0]; + $this->replacement[] = $field[1]; + $this->useRegex[] = isset( $field[2] ) ? true : false; + } + return true; + } + + private function shouldContinueByDefault() { + if ( !is_bool( $this->defaultContinue ) ) { + $this->defaultContinue = + $this->getOption( "yes" ) ? + true : + false; + } + return $this->defaultContinue; + } + + private function getSummary( $target, $replacement ) { + $msg = wfMessage( 'replacetext_editsummary', $target, $replacement )-> + plain(); + if ( $this->getOption( "summary" ) !== null ) { + $msg = str_replace( [ '%f', '%r' ], + [ $this->target, $this->replacement ], + $this->getOption( "summary" ) ); + } + return $msg; + } + + private function listNamespaces() { + echo "Index\tNamespace\n"; + $nsList = MWNamespace::getCanonicalNamespaces(); + ksort( $nsList ); + foreach ( $nsList as $int => $val ) { + if ( $val == "" ) { + $val = "(main)"; + } + echo " $int\t$val\n"; + } + } + + private function showFileFormat() { +echo <<<EOF + +The format of the replacements file is tab separated with three fields. +Any line that does not have a tab is ignored and can be considered a comment. + +Fields are: + + 1. String to search for. + 2. String to replace found text with. + 3. (optional) The presence of this field indicates that the previous two + are considered a regular expression. + +Example: + +This is a comment +TARGET REPLACE +regex(p*) Count the Ps; \\1 true + + +EOF; + } + + private function getNamespaces() { + $nsall = $this->getOption( "nsall" ); + $ns = $this->getOption( "ns" ); + if ( !$nsall && !$ns ) { + $namespaces = [ NS_MAIN ]; + } else { + $canonical = MWNamespace::getCanonicalNamespaces(); + $canonical[NS_MAIN] = "_"; + $namespaces = array_flip( $canonical ); + if ( !$nsall ) { + $namespaces = array_map( + function ( $n ) use ( $canonical, $namespaces ) { + if ( is_numeric( $n ) ) { + if ( isset( $canonical[ $n ] ) ) { + return intval( $n ); + } + } else { + if ( isset( $namespaces[ $n ] ) ) { + return $namespaces[ $n ]; + } + } + return null; + }, explode( ",", $ns ) ); + $namespaces = array_filter( + $namespaces, + function ( $val ) { + return $val !== null; + } ); + } + } + return $namespaces; + } + + private function getCategory() { + $cat = null; + return $cat; + } + + private function getPrefix() { + $prefix = null; + return $prefix; + } + + private function useRegex() { + return [ $this->getOption( "regex" ) ]; + } + + private function getTitles( $res ) { + if ( !$this->titles || count( $this->titles ) == 0 ) { + $this->titles = []; + foreach ( $res as $row ) { + $this->titles[] = Title::makeTitleSafe( + $row->page_namespace, + $row->page_title + ); + } + } + return $this->titles; + } + + private function listTitles( $res ) { + $ret = false; + foreach ( $this->getTitles( $res ) as $title ) { + $ret = true; + echo "$title\n"; + } + return $ret; + } + + private function replaceTitles( $res, $target, $replacement, $useRegex ) { + foreach ( $this->getTitles( $res ) as $title ) { + $param = [ + 'target_str' => $target, + 'replacement_str' => $replacement, + 'use_regex' => $useRegex, + 'user_id' => $this->user->getId(), + 'edit_summary' => $this->getSummary( $target, $replacement ), + 'doAnnounce' => $this->doAnnounce + ]; + echo "Replacing on $title... "; + $job = new ReplaceTextJob( $title, $param ); + if ( $job->run() !== true ) { + $this->error( "Trouble on the page '$title'." ); + } + echo "done.\n"; + } + } + + private function getReply( $question ) { + $reply = ""; + if ( $this->shouldContinueByDefault() ) { + return true; + } + while ( $reply !== "y" && $reply !== "n" ) { + $reply = $this->readconsole( "$question (Y/N) " ); + $reply = substr( strtolower( $reply ), 0, 1 ); + } + return $reply === "y"; + } + + private function localSetup() { + if ( $this->getOption( "listns" ) ) { + $this->listNamespaces(); + return false; + } + if ( $this->getOption( "show-file-format" ) ) { + $this->showFileFormat(); + return false; + } + $this->user = $this->getUser(); + if ( ! $this->getReplacements() ) { + $this->target = $this->getTarget(); + $this->replacement = $this->getReplacement(); + $this->useRegex = $this->useRegex(); + } + $this->namespaces = $this->getNamespaces(); + $this->category = $this->getCategory(); + $this->prefix = $this->getPrefix(); + return true; + } + + /** + * @inheritDoc + */ + public function execute() { + global $wgShowExceptionDetails; + $wgShowExceptionDetails = true; + + $this->doAnnounce = true; + if ( $this->localSetup() ) { + if ( $this->namespaces === [] ) { + $this->error( "No matching namespaces.", true ); + } + + foreach ( array_keys( $this->target ) as $index ) { + $target = $this->target[$index]; + $replacement = $this->replacement[$index]; + $useRegex = $this->useRegex[$index]; + + if ( $this->getOption( "debug" ) ) { + echo "Replacing '$target' with '$replacement'"; + if ( $useRegex ) { + echo " as regular expression."; + } + echo "\n"; + } + $res = ReplaceTextSearch::doSearchQuery( $target, + $this->namespaces, $this->category, $this->prefix, + $useRegex ); + + if ( $res->numRows() === 0 ) { + $this->error( "No targets found to replace.", true ); + } + if ( $this->getOption( "dry-run" ) ) { + $this->listTitles( $res ); + return; + } + if ( !$this->shouldContinueByDefault() && + $this->listTitles( $res ) ) { + if ( !$this->getReply( + "Replace instances on these pages?" + ) ) { + return; + } + } + $comment = ""; + if ( $this->getOption( "user", null ) === null ) { + $comment = " (Use --user to override)"; + } + if ( $this->getOption( "no-announce", false ) ) { + $this->doAnnounce = false; + } + if ( !$this->getReply( + "Attribute changes to the user '{$this->user}'?$comment" + ) ) { + return; + } + if ( $res->numRows() > 0 ) { + $this->replaceTitles( + $res, $target, $replacement, $useRegex + ); + } + } + } + } +} + +$maintClass = "ReplaceAll"; +require_once RUN_MAINTENANCE_IF_MAIN; |