2020-03-03 14:33:54 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
|
2024-08-11 16:32:58 +00:00
|
|
|
namespace MediaWiki\Maintenance;
|
|
|
|
|
|
|
|
|
|
use ExecutableFinder;
|
|
|
|
|
use MediaWiki;
|
2023-09-20 07:54:42 +00:00
|
|
|
use MediaWiki\Config\Config;
|
2024-09-13 16:42:46 +00:00
|
|
|
use MediaWiki\Debug\MWDebug;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
use MediaWiki\HookContainer\HookContainer;
|
|
|
|
|
use MediaWiki\HookContainer\HookRunner;
|
2022-04-29 18:13:47 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2020-03-03 14:33:54 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2024-08-10 05:17:59 +00:00
|
|
|
use MediaWiki\Registration\ExtensionRegistry;
|
2022-01-26 17:46:06 +00:00
|
|
|
use MediaWiki\Settings\SettingsBuilder;
|
2020-03-03 14:33:54 +00:00
|
|
|
use MediaWiki\Shell\Shell;
|
2023-09-19 12:13:45 +00:00
|
|
|
use MediaWiki\User\User;
|
2024-08-11 16:32:58 +00:00
|
|
|
use StatusValue;
|
2024-01-17 18:53:40 +00:00
|
|
|
use Wikimedia\Rdbms\IConnectionProvider;
|
2020-03-03 14:33:54 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
|
|
|
|
use Wikimedia\Rdbms\IMaintainableDatabase;
|
2024-01-17 18:53:40 +00:00
|
|
|
use Wikimedia\Rdbms\IReadableDatabase;
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
// NOTE: MaintenanceParameters is needed in the constructor, and we may not have
|
|
|
|
|
// autoloading enabled at this point?
|
|
|
|
|
require_once __DIR__ . '/MaintenanceParameters.php';
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Abstract maintenance class for quickly writing and churning out
|
|
|
|
|
* maintenance scripts with minimal effort. All that _must_ be defined
|
|
|
|
|
* is the execute() method. See docs/maintenance.txt for more info
|
|
|
|
|
* and a quick demo of how to use it.
|
|
|
|
|
*
|
|
|
|
|
* Terminology:
|
|
|
|
|
* params: registry of named values that may be passed to the script
|
|
|
|
|
* arg list: registry of positional values that may be passed to the script
|
|
|
|
|
* options: passed param values
|
|
|
|
|
* args: passed positional values
|
|
|
|
|
*
|
|
|
|
|
* In the command:
|
|
|
|
|
* mwscript somescript.php --foo=bar baz
|
|
|
|
|
* foo is a param
|
|
|
|
|
* bar is the option value of the option for param foo
|
|
|
|
|
* baz is the arg value at index 0 in the arg list
|
|
|
|
|
*
|
2023-08-21 22:39:57 +00:00
|
|
|
* WARNING: the constructor, MaintenanceRunner::shouldExecute(), setup(), finalSetup(),
|
|
|
|
|
* and getName() are called before Setup.php is complete, which means most of the common
|
|
|
|
|
* infrastructure, like logging or autoloading, is not available. Be careful when changing
|
|
|
|
|
* these methods or the ones called from them. Likewise, be careful with the constructor
|
|
|
|
|
* when subclassing. MediaWikiServices instance is not yet available at this point.
|
2021-04-04 17:57:14 +00:00
|
|
|
*
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to extend
|
2020-03-12 11:13:22 +00:00
|
|
|
*
|
2020-03-03 14:33:54 +00:00
|
|
|
* @since 1.16
|
|
|
|
|
* @ingroup Maintenance
|
|
|
|
|
*/
|
|
|
|
|
abstract class Maintenance {
|
|
|
|
|
/**
|
|
|
|
|
* Constants for DB access type
|
|
|
|
|
* @see Maintenance::getDbType()
|
|
|
|
|
*/
|
2020-05-09 23:22:50 +00:00
|
|
|
public const DB_NONE = 0;
|
|
|
|
|
public const DB_STD = 1;
|
|
|
|
|
public const DB_ADMIN = 2;
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
// Const for getStdin()
|
2021-01-20 15:00:26 +00:00
|
|
|
public const STDIN_ALL = -1;
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-04-16 22:06:46 +00:00
|
|
|
// Help group names
|
2023-01-16 13:19:52 +00:00
|
|
|
public const SCRIPT_DEPENDENT_PARAMETERS = 'Common options';
|
|
|
|
|
public const GENERIC_MAINTENANCE_PARAMETERS = 'Script runner options';
|
2022-04-16 22:06:46 +00:00
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
2022-04-09 20:31:25 +00:00
|
|
|
* @var MaintenanceParameters
|
|
|
|
|
*/
|
|
|
|
|
protected $parameters;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Empty.
|
|
|
|
|
* @deprecated since 1.39, use $this->parameters instead.
|
2020-03-03 14:33:54 +00:00
|
|
|
* @var array[]
|
|
|
|
|
* @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}>
|
|
|
|
|
*/
|
|
|
|
|
protected $mParams = [];
|
|
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
/**
|
|
|
|
|
* @var array This is the list of options that were actually passed
|
2024-07-07 14:45:29 +00:00
|
|
|
* @deprecated since 1.39, use {@see addOption} instead.
|
2022-04-09 20:31:25 +00:00
|
|
|
*/
|
2020-03-03 14:33:54 +00:00
|
|
|
protected $mOptions = [];
|
|
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
/**
|
|
|
|
|
* @var array This is the list of arguments that were actually passed
|
2024-07-07 14:45:29 +00:00
|
|
|
* @deprecated since 1.39, use {@see addArg} instead.
|
2022-04-09 20:31:25 +00:00
|
|
|
*/
|
2020-03-03 14:33:54 +00:00
|
|
|
protected $mArgs = [];
|
|
|
|
|
|
2020-10-28 19:44:09 +00:00
|
|
|
/** @var string|null Name of the script currently running */
|
2020-03-03 14:33:54 +00:00
|
|
|
protected $mSelf;
|
|
|
|
|
|
2020-10-28 19:44:09 +00:00
|
|
|
/** @var bool Special vars for params that are always used */
|
2020-03-03 14:33:54 +00:00
|
|
|
protected $mQuiet = false;
|
2024-04-21 14:43:04 +00:00
|
|
|
protected ?string $mDbUser = null;
|
|
|
|
|
protected ?string $mDbPass = null;
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
/**
|
|
|
|
|
* @var string A description of the script, children should change this via addDescription()
|
2024-07-07 14:45:29 +00:00
|
|
|
* @deprecated since 1.39, use {@see addDescription} instead.
|
2022-04-09 20:31:25 +00:00
|
|
|
*/
|
2020-03-03 14:33:54 +00:00
|
|
|
protected $mDescription = '';
|
|
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
/**
|
|
|
|
|
* @var bool Have we already loaded our user input?
|
|
|
|
|
* @deprecated since 1.39, treat as private to the Maintenance base class
|
|
|
|
|
*/
|
2020-03-03 14:33:54 +00:00
|
|
|
protected $mInputLoaded = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Batch size. If a script supports this, they should set
|
|
|
|
|
* a default with setBatchSize()
|
|
|
|
|
*
|
2021-01-19 18:04:41 +00:00
|
|
|
* @var int|null
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
protected $mBatchSize = null;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used by getDB() / setDB()
|
2021-01-19 18:04:41 +00:00
|
|
|
* @var IMaintainableDatabase|null
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
private $mDb = null;
|
|
|
|
|
|
|
|
|
|
/** @var float UNIX timestamp */
|
|
|
|
|
private $lastReplicationWait = 0.0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used when creating separate schema files.
|
2021-01-19 18:04:41 +00:00
|
|
|
* @var resource|null
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public $fileHandle;
|
|
|
|
|
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
/** @var HookContainer|null */
|
|
|
|
|
private $hookContainer;
|
|
|
|
|
|
|
|
|
|
/** @var HookRunner|null */
|
|
|
|
|
private $hookRunner;
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Accessible via getConfig()
|
|
|
|
|
*
|
2021-01-19 18:04:41 +00:00
|
|
|
* @var Config|null
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @see Maintenance::requireExtension
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
private $requiredExtensions = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used to read the options in the order they were passed.
|
|
|
|
|
* Useful for option chaining (Ex. dumpBackup.php). It will
|
|
|
|
|
* be an empty array if the options are passed in through
|
|
|
|
|
* loadParamsAndArgs( $self, $opts, $args ).
|
|
|
|
|
*
|
|
|
|
|
* This is an array of arrays where
|
|
|
|
|
* 0 => the option and 1 => parameter value.
|
|
|
|
|
*
|
2024-07-07 14:45:29 +00:00
|
|
|
* @deprecated since 1.39, use $this->getParameters()->getOptionsSequence() instead.
|
2020-03-03 14:33:54 +00:00
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
public $orderedOptions = [];
|
2024-01-17 18:53:40 +00:00
|
|
|
private ?IConnectionProvider $dbProvider = null;
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Default constructor. Children should call this *first* if implementing
|
|
|
|
|
* their own constructors
|
2020-03-12 11:13:22 +00:00
|
|
|
*
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to call
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public function __construct() {
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters = new MaintenanceParameters();
|
2022-07-19 13:37:39 +00:00
|
|
|
$this->mOptions =& $this->parameters->getFieldReference( 'mOptions' );
|
|
|
|
|
$this->orderedOptions =& $this->parameters->getFieldReference( 'optionsSequence' );
|
|
|
|
|
$this->mArgs =& $this->parameters->getFieldReference( 'mArgs' );
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->addDefaultParams();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-04-15 22:20:41 +00:00
|
|
|
* @since 1.39
|
|
|
|
|
* @return MaintenanceParameters
|
|
|
|
|
*/
|
|
|
|
|
public function getParameters() {
|
|
|
|
|
return $this->parameters;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Do the actual work. All child classes will need to implement this
|
|
|
|
|
*
|
|
|
|
|
* @return bool|null|void True for success, false for failure. Not returning
|
|
|
|
|
* a value, or returning null, is also interpreted as success. Returning
|
|
|
|
|
* false for failure will cause doMaintenance.php to exit the process
|
|
|
|
|
* with a non-zero exit status.
|
|
|
|
|
*/
|
|
|
|
|
abstract public function execute();
|
|
|
|
|
|
2023-01-25 15:11:10 +00:00
|
|
|
/**
|
|
|
|
|
* Whether this script can run without LocalSettings.php. Scripts that need to be able
|
|
|
|
|
* to run when MediaWiki has not been installed should override this to return true.
|
|
|
|
|
* Scripts that return true from this method must be able to function without
|
|
|
|
|
* a storage backend. When no LocalSettings.php file is present, any attempt to access
|
|
|
|
|
* the database will fail with a fatal error.
|
|
|
|
|
*
|
|
|
|
|
* @note Subclasses that override this method to return true should also override
|
|
|
|
|
* getDbType() to return self::DB_NONE, unless they are going to use the database
|
|
|
|
|
* connection when it is available.
|
|
|
|
|
*
|
|
|
|
|
* @see getDbType()
|
|
|
|
|
* @since 1.40
|
|
|
|
|
* @stable to override
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function canExecuteWithoutLocalSettings(): bool {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Checks to see if a particular option in supported. Normally this means it
|
|
|
|
|
* has been registered by the script via addOption.
|
|
|
|
|
* @param string $name The name of the option
|
|
|
|
|
* @return bool true if the option exists, false otherwise
|
|
|
|
|
*/
|
|
|
|
|
protected function supportsOption( $name ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
return $this->parameters->supportsOption( $name );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a parameter to the script. Will be displayed on --help
|
|
|
|
|
* with the associated description
|
|
|
|
|
*
|
|
|
|
|
* @param string $name The name of the param (help, version, etc)
|
|
|
|
|
* @param string $description The description of the param to show on --help
|
|
|
|
|
* @param bool $required Is the param required?
|
|
|
|
|
* @param bool $withArg Is an argument required with this option?
|
|
|
|
|
* @param string|bool $shortName Character to use as short name
|
|
|
|
|
* @param bool $multiOccurrence Can this option be passed multiple times?
|
|
|
|
|
*/
|
|
|
|
|
protected function addOption( $name, $description, $required = false,
|
|
|
|
|
$withArg = false, $shortName = false, $multiOccurrence = false
|
|
|
|
|
) {
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->addOption(
|
|
|
|
|
$name,
|
|
|
|
|
$description,
|
|
|
|
|
$required,
|
|
|
|
|
$withArg,
|
|
|
|
|
$shortName,
|
|
|
|
|
$multiOccurrence
|
|
|
|
|
);
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-02-24 13:56:29 +00:00
|
|
|
* Checks to see if a particular option was set.
|
|
|
|
|
*
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param string $name The name of the option
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function hasOption( $name ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
return $this->parameters->hasOption( $name );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an option, or return the default.
|
|
|
|
|
*
|
|
|
|
|
* If the option was added to support multiple occurrences,
|
|
|
|
|
* this will return an array.
|
|
|
|
|
*
|
|
|
|
|
* @param string $name The name of the param
|
|
|
|
|
* @param mixed|null $default Anything you want, default null
|
|
|
|
|
* @return mixed
|
|
|
|
|
* @return-taint none
|
|
|
|
|
*/
|
|
|
|
|
protected function getOption( $name, $default = null ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
return $this->parameters->getOption( $name, $default );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add some args that are needed
|
|
|
|
|
* @param string $arg Name of the arg, like 'start'
|
|
|
|
|
* @param string $description Short description of the arg
|
|
|
|
|
* @param bool $required Is this required?
|
2023-01-08 21:40:27 +00:00
|
|
|
* @param bool $multi Does it allow multiple values? (Last arg only)
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
2023-01-08 21:40:27 +00:00
|
|
|
protected function addArg( $arg, $description, $required = true, $multi = false ) {
|
|
|
|
|
$this->parameters->addArg( $arg, $description, $required, $multi );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove an option. Useful for removing options that won't be used in your script.
|
|
|
|
|
* @param string $name The option to remove.
|
|
|
|
|
*/
|
|
|
|
|
protected function deleteOption( $name ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->deleteOption( $name );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets whether to allow unregistered options, which are options passed to
|
|
|
|
|
* a script that do not match an expected parameter.
|
|
|
|
|
* @param bool $allow Should we allow?
|
|
|
|
|
*/
|
|
|
|
|
protected function setAllowUnregisteredOptions( $allow ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->setAllowUnregisteredOptions( $allow );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the description text.
|
|
|
|
|
* @param string $text The text of the description
|
|
|
|
|
*/
|
|
|
|
|
protected function addDescription( $text ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->setDescription( $text );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Does a given argument exist?
|
2022-09-07 12:41:11 +00:00
|
|
|
* @param int|string $argId The index (from zero) of the argument, or
|
|
|
|
|
* the name declared for the argument by addArg().
|
2020-03-03 14:33:54 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function hasArg( $argId = 0 ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
return $this->parameters->hasArg( $argId );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an argument.
|
2022-09-07 12:41:11 +00:00
|
|
|
* @param int|string $argId The index (from zero) of the argument, or
|
|
|
|
|
* the name declared for the argument by addArg().
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param mixed|null $default The default if it doesn't exist
|
|
|
|
|
* @return mixed
|
|
|
|
|
* @return-taint none
|
|
|
|
|
*/
|
|
|
|
|
protected function getArg( $argId = 0, $default = null ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
return $this->parameters->getArg( $argId, $default );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-29 19:54:14 +00:00
|
|
|
/**
|
|
|
|
|
* Get arguments.
|
|
|
|
|
* @since 1.40
|
|
|
|
|
*
|
|
|
|
|
* @param int|string $offset The index (from zero) of the first argument, or
|
|
|
|
|
* the name declared for the argument by addArg().
|
|
|
|
|
* @return string[]
|
|
|
|
|
*/
|
|
|
|
|
protected function getArgs( $offset = 0 ) {
|
|
|
|
|
return $this->parameters->getArgs( $offset );
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-04 14:28:00 +00:00
|
|
|
/**
|
|
|
|
|
* Get the name of an argument.
|
|
|
|
|
* @since 1.43
|
|
|
|
|
*
|
|
|
|
|
* @param int $argId The index (from zero) of the argument.
|
|
|
|
|
*
|
|
|
|
|
* @return string|null The name of the argument, or null if the argument does not exist.
|
|
|
|
|
*/
|
|
|
|
|
protected function getArgName( int $argId ): ?string {
|
|
|
|
|
return $this->parameters->getArgName( $argId );
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 13:37:39 +00:00
|
|
|
/**
|
|
|
|
|
* Programmatically set the value of the given option.
|
|
|
|
|
* Useful for setting up child scripts, see runChild().
|
|
|
|
|
*
|
|
|
|
|
* @since 1.39
|
|
|
|
|
*
|
|
|
|
|
* @param string $name
|
|
|
|
|
* @param mixed|null $value
|
|
|
|
|
*/
|
|
|
|
|
public function setOption( string $name, $value ): void {
|
|
|
|
|
$this->parameters->setOption( $name, $value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Programmatically set the value of the given argument.
|
|
|
|
|
* Useful for setting up child scripts, see runChild().
|
|
|
|
|
*
|
|
|
|
|
* @since 1.39
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $argId Arg index or name
|
|
|
|
|
* @param mixed|null $value
|
|
|
|
|
*/
|
|
|
|
|
public function setArg( $argId, $value ): void {
|
|
|
|
|
$this->parameters->setArg( $argId, $value );
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Returns batch size
|
|
|
|
|
*
|
|
|
|
|
* @since 1.31
|
|
|
|
|
*
|
|
|
|
|
* @return int|null
|
|
|
|
|
*/
|
|
|
|
|
protected function getBatchSize() {
|
|
|
|
|
return $this->mBatchSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param int $s The number of operations to do in a batch
|
|
|
|
|
*/
|
|
|
|
|
protected function setBatchSize( $s = 0 ) {
|
|
|
|
|
$this->mBatchSize = $s;
|
|
|
|
|
|
|
|
|
|
// If we support $mBatchSize, show the option.
|
|
|
|
|
// Used to be in addDefaultParams, but in order for that to
|
|
|
|
|
// work, subclasses would have to call this function in the constructor
|
|
|
|
|
// before they called parent::__construct which is just weird
|
|
|
|
|
// (and really wasn't done).
|
|
|
|
|
if ( $this->mBatchSize ) {
|
|
|
|
|
$this->addOption( 'batch-size', 'Run this many operations ' .
|
|
|
|
|
'per batch, default: ' . $this->mBatchSize, false, true );
|
2022-04-09 20:31:25 +00:00
|
|
|
if ( $this->supportsOption( 'batch-size' ) ) {
|
2020-03-03 14:33:54 +00:00
|
|
|
// This seems a little ugly...
|
2022-04-16 22:06:46 +00:00
|
|
|
$this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, [ 'batch-size' ] );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the script's name
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getName() {
|
|
|
|
|
return $this->mSelf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return input from stdin.
|
2021-01-20 15:00:26 +00:00
|
|
|
* @param int|null $len The number of bytes to read. If null, just
|
|
|
|
|
* return the handle. Maintenance::STDIN_ALL returns the full content
|
2020-03-03 14:33:54 +00:00
|
|
|
* @return mixed
|
|
|
|
|
*/
|
|
|
|
|
protected function getStdin( $len = null ) {
|
|
|
|
|
if ( $len == self::STDIN_ALL ) {
|
|
|
|
|
return file_get_contents( 'php://stdin' );
|
|
|
|
|
}
|
|
|
|
|
$f = fopen( 'php://stdin', 'rt' );
|
|
|
|
|
if ( !$len ) {
|
|
|
|
|
return $f;
|
|
|
|
|
}
|
|
|
|
|
$input = fgets( $f, $len );
|
|
|
|
|
fclose( $f );
|
|
|
|
|
|
|
|
|
|
return rtrim( $input );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isQuiet() {
|
|
|
|
|
return $this->mQuiet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Throw some output to the user. Scripts can call this with no fears,
|
|
|
|
|
* as we handle all --quiet stuff here
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param string $out The text to show to the user
|
|
|
|
|
* @param mixed|null $channel Unique identifier for the channel. See function outputChanneled.
|
|
|
|
|
*/
|
|
|
|
|
protected function output( $out, $channel = null ) {
|
|
|
|
|
// This is sometimes called very early, before Setup.php is included.
|
2023-01-13 19:00:13 +00:00
|
|
|
if ( defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' ) ) {
|
2021-09-30 23:01:06 +00:00
|
|
|
// Flush stats periodically in long-running CLI scripts to avoid OOM (T181385)
|
2023-08-21 22:47:44 +00:00
|
|
|
$stats = $this->getServiceContainer()->getStatsdDataFactory();
|
2020-03-03 14:33:54 +00:00
|
|
|
if ( $stats->getDataCount() > 1000 ) {
|
|
|
|
|
MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $this->mQuiet ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( $channel === null ) {
|
|
|
|
|
$this->cleanupChanneled();
|
|
|
|
|
print $out;
|
|
|
|
|
} else {
|
|
|
|
|
$out = preg_replace( '/\n\z/', '', $out );
|
|
|
|
|
$this->outputChanneled( $out, $channel );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Throw an error to the user. Doesn't respect --quiet, so don't use
|
|
|
|
|
* this for non-error output
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
Maintenance: Print errors from StatusValue objects in a consistent way
Allow Maintenance::error() and Maintenance::fatalError() to take
StatusValue objects. They now print each error message from the
status on a separate line, in English, ignoring on-wiki message
overrides, as wikitext but after parser function expansion.
Thoughts on the previously commonly used methods:
- $status->getMessage( false, false, 'en' )->text()
Almost the same as the new output, but it allows on-wiki message
overrides, and if there is more than one error, it prefixes each
line with a '*' (like a wikitext list).
- $status->getMessage( false, false, 'en' )->plain()
- $status->getWikiText( false, false, 'en' )
As above, but these forms do not expand parser functions
such as {{GENDER:}}.
- print_r( $status->getErrorsArray(), true )
- print_r( $status->getErrors(), true )
These forms output the message keys instead of the message text,
which is not very human-readable.
The error messages are now always printed using error() rather
than output(), which means they go to STDERR rather than STDOUT
and they're printed even with the --quiet flag.
Change-Id: I5b8e7c7ed2a896a1029f58857a478d3f1b4b0589
2024-06-06 23:50:00 +00:00
|
|
|
* @param string|StatusValue $err The error to display
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param int $die Deprecated since 1.31, use Maintenance::fatalError() instead
|
|
|
|
|
*/
|
|
|
|
|
protected function error( $err, $die = 0 ) {
|
|
|
|
|
if ( intval( $die ) !== 0 ) {
|
|
|
|
|
wfDeprecated( __METHOD__ . '( $err, $die )', '1.31' );
|
|
|
|
|
$this->fatalError( $err, intval( $die ) );
|
|
|
|
|
}
|
Maintenance: Print errors from StatusValue objects in a consistent way
Allow Maintenance::error() and Maintenance::fatalError() to take
StatusValue objects. They now print each error message from the
status on a separate line, in English, ignoring on-wiki message
overrides, as wikitext but after parser function expansion.
Thoughts on the previously commonly used methods:
- $status->getMessage( false, false, 'en' )->text()
Almost the same as the new output, but it allows on-wiki message
overrides, and if there is more than one error, it prefixes each
line with a '*' (like a wikitext list).
- $status->getMessage( false, false, 'en' )->plain()
- $status->getWikiText( false, false, 'en' )
As above, but these forms do not expand parser functions
such as {{GENDER:}}.
- print_r( $status->getErrorsArray(), true )
- print_r( $status->getErrors(), true )
These forms output the message keys instead of the message text,
which is not very human-readable.
The error messages are now always printed using error() rather
than output(), which means they go to STDERR rather than STDOUT
and they're printed even with the --quiet flag.
Change-Id: I5b8e7c7ed2a896a1029f58857a478d3f1b4b0589
2024-06-06 23:50:00 +00:00
|
|
|
if ( $err instanceof StatusValue ) {
|
|
|
|
|
foreach ( [ 'warning' => 'Warning: ', 'error' => 'Error: ' ] as $type => $prefix ) {
|
|
|
|
|
foreach ( $err->getMessages( $type ) as $msg ) {
|
|
|
|
|
$this->error(
|
|
|
|
|
$prefix . wfMessage( $msg )
|
|
|
|
|
->inLanguage( 'en' )
|
|
|
|
|
->useDatabase( false )
|
|
|
|
|
->text()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->outputChanneled( false );
|
|
|
|
|
if (
|
|
|
|
|
( PHP_SAPI == 'cli' || PHP_SAPI == 'phpdbg' ) &&
|
|
|
|
|
!defined( 'MW_PHPUNIT_TEST' )
|
|
|
|
|
) {
|
|
|
|
|
fwrite( STDERR, $err . "\n" );
|
|
|
|
|
} else {
|
2024-06-06 23:49:49 +00:00
|
|
|
print $err . "\n";
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output a message and terminate the current script.
|
|
|
|
|
*
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
Maintenance: Print errors from StatusValue objects in a consistent way
Allow Maintenance::error() and Maintenance::fatalError() to take
StatusValue objects. They now print each error message from the
status on a separate line, in English, ignoring on-wiki message
overrides, as wikitext but after parser function expansion.
Thoughts on the previously commonly used methods:
- $status->getMessage( false, false, 'en' )->text()
Almost the same as the new output, but it allows on-wiki message
overrides, and if there is more than one error, it prefixes each
line with a '*' (like a wikitext list).
- $status->getMessage( false, false, 'en' )->plain()
- $status->getWikiText( false, false, 'en' )
As above, but these forms do not expand parser functions
such as {{GENDER:}}.
- print_r( $status->getErrorsArray(), true )
- print_r( $status->getErrors(), true )
These forms output the message keys instead of the message text,
which is not very human-readable.
The error messages are now always printed using error() rather
than output(), which means they go to STDERR rather than STDOUT
and they're printed even with the --quiet flag.
Change-Id: I5b8e7c7ed2a896a1029f58857a478d3f1b4b0589
2024-06-06 23:50:00 +00:00
|
|
|
* @param string|StatusValue $msg Error message
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param int $exitCode PHP exit status. Should be in range 1-254.
|
|
|
|
|
* @since 1.31
|
2021-09-04 01:42:33 +00:00
|
|
|
* @return never
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
protected function fatalError( $msg, $exitCode = 1 ) {
|
|
|
|
|
$this->error( $msg );
|
2024-08-03 21:00:55 +00:00
|
|
|
// If running PHPUnit tests we don't want to call exit, as it will end the test suite early.
|
|
|
|
|
// Instead, throw an exception that will still cause the relevant test to fail if the ::fatalError
|
|
|
|
|
// call was not expected.
|
|
|
|
|
if ( defined( 'MW_PHPUNIT_TEST' ) ) {
|
|
|
|
|
throw new MaintenanceFatalError( $exitCode );
|
|
|
|
|
} else {
|
|
|
|
|
exit( $exitCode );
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-12 19:59:28 +00:00
|
|
|
/** @var bool */
|
2020-03-03 14:33:54 +00:00
|
|
|
private $atLineStart = true;
|
2024-09-12 19:59:28 +00:00
|
|
|
/** @var string|null */
|
2020-03-03 14:33:54 +00:00
|
|
|
private $lastChannel = null;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clean up channeled output. Output a newline if necessary.
|
|
|
|
|
*/
|
|
|
|
|
public function cleanupChanneled() {
|
|
|
|
|
if ( !$this->atLineStart ) {
|
|
|
|
|
print "\n";
|
|
|
|
|
$this->atLineStart = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Message outputter with channeled message support. Messages on the
|
|
|
|
|
* same channel are concatenated, but any intervening messages in another
|
|
|
|
|
* channel start a new line.
|
2022-03-03 20:33:11 +00:00
|
|
|
* @param string|false $msg The message without trailing newline
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param string|null $channel Channel identifier or null for no
|
|
|
|
|
* channel. Channel comparison uses ===.
|
|
|
|
|
*/
|
|
|
|
|
public function outputChanneled( $msg, $channel = null ) {
|
|
|
|
|
if ( $msg === false ) {
|
|
|
|
|
$this->cleanupChanneled();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// End the current line if necessary
|
|
|
|
|
if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
|
|
|
|
|
print "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print $msg;
|
|
|
|
|
|
|
|
|
|
$this->atLineStart = false;
|
|
|
|
|
if ( $channel === null ) {
|
|
|
|
|
// For unchanneled messages, output trailing newline immediately
|
|
|
|
|
print "\n";
|
|
|
|
|
$this->atLineStart = true;
|
|
|
|
|
}
|
|
|
|
|
$this->lastChannel = $channel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Does the script need different DB access? By default, we give Maintenance
|
|
|
|
|
* scripts normal rights to the DB. Sometimes, a script needs admin rights
|
|
|
|
|
* access for a reason and sometimes they want no access. Subclasses should
|
|
|
|
|
* override and return one of the following values, as needed:
|
|
|
|
|
* Maintenance::DB_NONE - For no DB access at all
|
|
|
|
|
* Maintenance::DB_STD - For normal DB access, default
|
|
|
|
|
* Maintenance::DB_ADMIN - For admin DB access
|
2023-01-25 15:11:10 +00:00
|
|
|
*
|
|
|
|
|
* @note Subclasses that override this method to return self::DB_NONE should
|
|
|
|
|
* also override canExecuteWithoutLocalSettings() to return true, unless they
|
|
|
|
|
* need the wiki to be set up for reasons beyond access to a database connection.
|
|
|
|
|
*
|
|
|
|
|
* @see canExecuteWithoutLocalSettings()
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
* @return int
|
|
|
|
|
*/
|
|
|
|
|
public function getDbType() {
|
|
|
|
|
return self::DB_STD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add the default parameters to the scripts
|
|
|
|
|
*/
|
|
|
|
|
protected function addDefaultParams() {
|
2021-02-02 14:15:55 +00:00
|
|
|
# Generic (non-script-dependent) options:
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
$this->addOption( 'help', 'Display this help message', false, false, 'h' );
|
|
|
|
|
$this->addOption( 'quiet', 'Whether to suppress non-error output', false, false, 'q' );
|
|
|
|
|
|
|
|
|
|
# Save generic options to display them separately in help
|
2022-04-16 22:06:46 +00:00
|
|
|
$generic = [ 'help', 'quiet' ];
|
|
|
|
|
$this->parameters->assignGroup( self::GENERIC_MAINTENANCE_PARAMETERS, $generic );
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2021-02-02 14:15:55 +00:00
|
|
|
# Script-dependent options:
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
// If we support a DB, show the options
|
|
|
|
|
if ( $this->getDbType() > 0 ) {
|
|
|
|
|
$this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
|
|
|
|
|
$this->addOption( 'dbpass', 'The password to use for this script', false, true );
|
|
|
|
|
$this->addOption( 'dbgroupdefault', 'The default DB group to use.', false, true );
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 14:15:55 +00:00
|
|
|
# Save additional script-dependent options to display
|
2022-04-09 20:31:25 +00:00
|
|
|
# them separately in help
|
2022-04-16 22:06:46 +00:00
|
|
|
$dependent = array_diff(
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->getOptionNames(),
|
2022-04-16 22:06:46 +00:00
|
|
|
$generic
|
2022-04-09 20:31:25 +00:00
|
|
|
);
|
2022-04-16 22:06:46 +00:00
|
|
|
$this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, $dependent );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @since 1.24
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
* @return Config
|
|
|
|
|
*/
|
|
|
|
|
public function getConfig() {
|
|
|
|
|
if ( $this->config === null ) {
|
2023-08-21 22:47:44 +00:00
|
|
|
$this->config = $this->getServiceContainer()->getMainConfig();
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->config;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 19:54:14 +00:00
|
|
|
/**
|
|
|
|
|
* Returns the main service container.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.40
|
|
|
|
|
* @return MediaWikiServices
|
|
|
|
|
*/
|
2023-08-21 22:47:44 +00:00
|
|
|
protected function getServiceContainer() {
|
2023-01-29 19:54:14 +00:00
|
|
|
return MediaWikiServices::getInstance();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* @since 1.24
|
|
|
|
|
* @param Config $config
|
|
|
|
|
*/
|
|
|
|
|
public function setConfig( Config $config ) {
|
|
|
|
|
$this->config = $config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Indicate that the specified extension must be
|
|
|
|
|
* loaded before the script can run.
|
|
|
|
|
*
|
|
|
|
|
* This *must* be called in the constructor.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.28
|
|
|
|
|
* @param string $name
|
|
|
|
|
*/
|
|
|
|
|
protected function requireExtension( $name ) {
|
|
|
|
|
$this->requiredExtensions[] = $name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify that the required extensions are installed
|
|
|
|
|
*
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function checkRequiredExtensions() {
|
|
|
|
|
$registry = ExtensionRegistry::getInstance();
|
|
|
|
|
$missing = [];
|
|
|
|
|
foreach ( $this->requiredExtensions as $name ) {
|
|
|
|
|
if ( !$registry->isLoaded( $name ) ) {
|
|
|
|
|
$missing[] = $name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $missing ) {
|
2020-10-17 06:55:20 +00:00
|
|
|
if ( count( $missing ) === 1 ) {
|
|
|
|
|
$msg = 'The "' . $missing[ 0 ] . '" extension must be installed for this script to run. '
|
|
|
|
|
. 'Please enable it and then try again.';
|
|
|
|
|
} else {
|
|
|
|
|
$msg = 'The following extensions must be installed for this script to run: "'
|
|
|
|
|
. implode( '", "', $missing ) . '". Please enable them and then try again.';
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->fatalError( $msg );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2024-09-02 14:47:48 +00:00
|
|
|
* Returns an instance of the given maintenance script, with all of the current arguments
|
|
|
|
|
* passed to it.
|
|
|
|
|
*
|
|
|
|
|
* Callers are expected to run the returned maintenance script instance by calling {@link Maintenance::execute}
|
|
|
|
|
*
|
|
|
|
|
* @deprecated Since 1.43. Use {@link Maintenance::createChild} instead. This method is an alias to that method.
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param string $maintClass A name of a child maintenance class
|
|
|
|
|
* @param string|null $classFile Full path of where the child is
|
2024-09-02 14:47:48 +00:00
|
|
|
* @return Maintenance The created instance, which the caller is expected to run by calling
|
|
|
|
|
* {@link Maintenance::execute} on the returned object.
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public function runChild( $maintClass, $classFile = null ) {
|
2024-09-13 16:42:46 +00:00
|
|
|
MWDebug::detectDeprecatedOverride( $this, __CLASS__, 'runChild', '1.43' );
|
2024-09-02 14:47:48 +00:00
|
|
|
return self::createChild( $maintClass, $classFile );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an instance of the given maintenance script, with all of the current arguments
|
|
|
|
|
* passed to it.
|
|
|
|
|
*
|
|
|
|
|
* Callers are expected to run the returned maintenance script instance by calling {@link Maintenance::execute}
|
|
|
|
|
*
|
|
|
|
|
* @param string $maintClass A name of a child maintenance class
|
|
|
|
|
* @param string|null $classFile Full path of where the child is
|
|
|
|
|
* @stable to override
|
|
|
|
|
* @return Maintenance The created instance, which the caller is expected to run by calling
|
|
|
|
|
* {@link Maintenance::execute} on the returned object.
|
|
|
|
|
*/
|
|
|
|
|
public function createChild( string $maintClass, ?string $classFile = null ): Maintenance {
|
2020-03-03 14:33:54 +00:00
|
|
|
// Make sure the class is loaded first
|
|
|
|
|
if ( !class_exists( $maintClass ) ) {
|
|
|
|
|
if ( $classFile ) {
|
|
|
|
|
require_once $classFile;
|
|
|
|
|
}
|
|
|
|
|
if ( !class_exists( $maintClass ) ) {
|
2021-01-19 18:58:11 +00:00
|
|
|
$this->fatalError( "Cannot spawn child: $maintClass" );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var Maintenance $child
|
|
|
|
|
*/
|
|
|
|
|
$child = new $maintClass();
|
2022-04-09 20:31:25 +00:00
|
|
|
$child->loadParamsAndArgs(
|
|
|
|
|
$this->mSelf,
|
|
|
|
|
$this->parameters->getOptions(),
|
|
|
|
|
$this->parameters->getArgs()
|
|
|
|
|
);
|
2020-03-03 14:33:54 +00:00
|
|
|
if ( $this->mDb !== null ) {
|
|
|
|
|
$child->setDB( $this->mDb );
|
|
|
|
|
}
|
2024-01-17 18:53:40 +00:00
|
|
|
if ( $this->dbProvider !== null ) {
|
|
|
|
|
$child->setDBProvider( $this->dbProvider );
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
return $child;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-07-31 10:39:13 +00:00
|
|
|
* Provides subclasses with an opportunity to perform initial checks.
|
|
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public function setup() {
|
2022-07-31 10:39:13 +00:00
|
|
|
// noop
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normally we disable the memory_limit when running admin scripts.
|
|
|
|
|
* Some scripts may wish to actually set a limit, however, to avoid
|
2022-04-16 22:06:46 +00:00
|
|
|
* blowing up unexpectedly.
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function memoryLimit() {
|
2022-04-16 22:06:46 +00:00
|
|
|
return 'max';
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear all params and arguments.
|
|
|
|
|
*/
|
|
|
|
|
public function clearParamsAndArgs() {
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->clear();
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->mInputLoaded = false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-31 10:39:13 +00:00
|
|
|
/**
|
|
|
|
|
* @since 1.40
|
|
|
|
|
* @internal
|
|
|
|
|
* @param string $name
|
|
|
|
|
*/
|
|
|
|
|
public function setName( string $name ) {
|
|
|
|
|
$this->mSelf = $name;
|
|
|
|
|
$this->parameters->setName( $this->mSelf );
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Load params and arguments from a given array
|
|
|
|
|
* of command-line arguments
|
|
|
|
|
*
|
|
|
|
|
* @since 1.27
|
2022-07-31 10:39:13 +00:00
|
|
|
* @param array $argv The argument array, not including the script itself.
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public function loadWithArgv( $argv ) {
|
2022-04-09 20:31:25 +00:00
|
|
|
if ( $this->mDescription ) {
|
|
|
|
|
$this->parameters->setDescription( $this->mDescription );
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->loadWithArgv( $argv );
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-04-09 20:31:25 +00:00
|
|
|
if ( $this->parameters->hasErrors() ) {
|
|
|
|
|
$errors = "\nERROR: " . implode( "\nERROR: ", $this->parameters->getErrors() ) . "\n";
|
|
|
|
|
$this->error( $errors );
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->maybeHelp( true );
|
|
|
|
|
}
|
2022-04-09 20:31:25 +00:00
|
|
|
|
|
|
|
|
$this->loadSpecialVars();
|
|
|
|
|
$this->mInputLoaded = true;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-07-31 10:39:13 +00:00
|
|
|
* Process command line arguments when running as a child script
|
2020-03-03 14:33:54 +00:00
|
|
|
*
|
|
|
|
|
* @param string|null $self The name of the script, if any
|
|
|
|
|
* @param array|null $opts An array of options, in form of key=>value
|
|
|
|
|
* @param array|null $args An array of command line arguments
|
|
|
|
|
*/
|
|
|
|
|
public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
|
|
|
|
|
# If we were given opts or args, set those and return early
|
2022-04-09 20:31:25 +00:00
|
|
|
if ( $self !== null || $opts !== null || $args !== null ) {
|
|
|
|
|
if ( $self !== null ) {
|
|
|
|
|
$this->mSelf = $self;
|
|
|
|
|
$this->parameters->setName( $self );
|
|
|
|
|
}
|
|
|
|
|
$this->parameters->setOptionsAndArgs( $opts ?? [], $args ?? [] );
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->mInputLoaded = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# If we've already loaded input (either by user values or from $argv)
|
2022-07-31 10:39:13 +00:00
|
|
|
# skip on loading it again.
|
2020-03-03 14:33:54 +00:00
|
|
|
if ( $this->mInputLoaded ) {
|
|
|
|
|
$this->loadSpecialVars();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global $argv;
|
|
|
|
|
$this->mSelf = $argv[0];
|
2022-04-09 20:31:25 +00:00
|
|
|
$this->parameters->setName( $this->mSelf );
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->loadWithArgv( array_slice( $argv, 1 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Run some validation checks on the params, etc
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public function validateParamsAndArgs() {
|
2022-04-09 20:31:25 +00:00
|
|
|
$valid = $this->parameters->validate();
|
|
|
|
|
|
|
|
|
|
$this->maybeHelp( !$valid );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle the special variables that are global to all scripts
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
protected function loadSpecialVars() {
|
|
|
|
|
if ( $this->hasOption( 'dbuser' ) ) {
|
|
|
|
|
$this->mDbUser = $this->getOption( 'dbuser' );
|
|
|
|
|
}
|
|
|
|
|
if ( $this->hasOption( 'dbpass' ) ) {
|
|
|
|
|
$this->mDbPass = $this->getOption( 'dbpass' );
|
|
|
|
|
}
|
|
|
|
|
if ( $this->hasOption( 'quiet' ) ) {
|
|
|
|
|
$this->mQuiet = true;
|
|
|
|
|
}
|
|
|
|
|
if ( $this->hasOption( 'batch-size' ) ) {
|
|
|
|
|
$this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Maybe show the help. If the help is shown, exit.
|
|
|
|
|
*
|
|
|
|
|
* @param bool $force Whether to force the help to show, default false
|
|
|
|
|
*/
|
|
|
|
|
protected function maybeHelp( $force = false ) {
|
|
|
|
|
if ( !$force && !$this->hasOption( 'help' ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-01-08 21:40:27 +00:00
|
|
|
|
|
|
|
|
if ( $this->parameters->hasErrors() && !$this->hasOption( 'help' ) ) {
|
|
|
|
|
$errors = "\nERROR: " . implode( "\nERROR: ", $this->parameters->getErrors() ) . "\n";
|
|
|
|
|
$this->error( $errors );
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->showHelp();
|
2024-08-11 17:36:47 +00:00
|
|
|
$this->fatalError( '' );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Definitely show the help. Does not exit.
|
|
|
|
|
*/
|
|
|
|
|
protected function showHelp() {
|
|
|
|
|
$this->mQuiet = false;
|
2022-04-09 20:31:25 +00:00
|
|
|
$help = $this->parameters->getHelp();
|
|
|
|
|
$this->output( $help );
|
2021-01-21 13:04:41 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
|
|
|
|
* Handle some last-minute setup here.
|
2022-01-26 17:46:06 +00:00
|
|
|
*
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2022-01-26 17:46:06 +00:00
|
|
|
*
|
2024-01-02 14:12:21 +00:00
|
|
|
* @param SettingsBuilder $settingsBuilder
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
2024-01-02 14:12:21 +00:00
|
|
|
public function finalSetup( SettingsBuilder $settingsBuilder ) {
|
2022-01-26 17:46:06 +00:00
|
|
|
$config = $settingsBuilder->getConfig();
|
|
|
|
|
$overrides = [];
|
2022-04-29 18:13:47 +00:00
|
|
|
$overrides['DBadminuser'] = $config->get( MainConfigNames::DBadminuser );
|
|
|
|
|
$overrides['DBadminpassword'] = $config->get( MainConfigNames::DBadminpassword );
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
# Turn off output buffering again, it might have been turned on in the settings files
|
|
|
|
|
if ( ob_get_level() ) {
|
|
|
|
|
ob_end_flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Override $wgServer
|
|
|
|
|
if ( $this->hasOption( 'server' ) ) {
|
2022-04-29 18:13:47 +00:00
|
|
|
$overrides['Server'] = $this->getOption( 'server', $config->get( MainConfigNames::Server ) );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# If these were passed, use them
|
|
|
|
|
if ( $this->mDbUser ) {
|
2022-01-26 17:46:06 +00:00
|
|
|
$overrides['DBadminuser'] = $this->mDbUser;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
if ( $this->mDbPass ) {
|
2022-01-26 17:46:06 +00:00
|
|
|
$overrides['DBadminpassword'] = $this->mDbPass;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
if ( $this->hasOption( 'dbgroupdefault' ) ) {
|
2022-01-26 17:46:06 +00:00
|
|
|
$overrides['DBDefaultGroup'] = $this->getOption( 'dbgroupdefault', null );
|
Don't access MWServices prematurely in Maintenence.php
In case of the referenced addWiki bug, the following was happening:
1. MWServices were initialized before Setup.php is complete
and before extensions were loaded. Deprecation warning was emitted.
2. onMWServices hook was run, thus hook container was created, but
without any extensions loaded.
3. When global MWServices instance was allowed, and services were
reset, hook container was salvaged. When that happens, already
instantiated handlers are preserved, so onMediaWikiServices handlers
(none at this point) were preserved.
4. GlobalPreferences refefine PreferencesFactory, but it was not
actually redefined, since onMediaWikiServices handler array was
salvaged in 3
5. The rest of the hooks work ok, since they are dynamically initialized
after extensions were loaded.
6. One of the hook handlers, implemented in GlobalPreferences,
expected PreferencesFactory to be redefined, but it was not.
7. Boom!
Bug: T285894
Bug: T275453
Change-Id: I8b16529e93dc12484e3501fab4fd34fca70114ea
2021-06-30 19:22:10 +00:00
|
|
|
// TODO: once MediaWikiServices::getInstance() starts throwing exceptions
|
|
|
|
|
// and not deprecation warnings for premature access to service container,
|
|
|
|
|
// we can remove this line. This method is called before Setup.php,
|
|
|
|
|
// so it would be guaranteed DBLoadBalancerFactory is not yet initialized.
|
|
|
|
|
if ( MediaWikiServices::hasInstance() ) {
|
2023-08-21 22:47:44 +00:00
|
|
|
$service = $this->getServiceContainer()->peekService( 'DBLoadBalancerFactory' );
|
Don't access MWServices prematurely in Maintenence.php
In case of the referenced addWiki bug, the following was happening:
1. MWServices were initialized before Setup.php is complete
and before extensions were loaded. Deprecation warning was emitted.
2. onMWServices hook was run, thus hook container was created, but
without any extensions loaded.
3. When global MWServices instance was allowed, and services were
reset, hook container was salvaged. When that happens, already
instantiated handlers are preserved, so onMediaWikiServices handlers
(none at this point) were preserved.
4. GlobalPreferences refefine PreferencesFactory, but it was not
actually redefined, since onMediaWikiServices handler array was
salvaged in 3
5. The rest of the hooks work ok, since they are dynamically initialized
after extensions were loaded.
6. One of the hook handlers, implemented in GlobalPreferences,
expected PreferencesFactory to be redefined, but it was not.
7. Boom!
Bug: T285894
Bug: T275453
Change-Id: I8b16529e93dc12484e3501fab4fd34fca70114ea
2021-06-30 19:22:10 +00:00
|
|
|
if ( $service ) {
|
|
|
|
|
$service->destroy();
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-26 17:46:06 +00:00
|
|
|
if ( $this->getDbType() == self::DB_ADMIN && isset( $overrides[ 'DBadminuser' ] ) ) {
|
|
|
|
|
$overrides['DBuser'] = $overrides[ 'DBadminuser' ];
|
|
|
|
|
$overrides['DBpassword'] = $overrides[ 'DBadminpassword' ];
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-01-26 17:46:06 +00:00
|
|
|
/** @var array $dbServers */
|
2022-04-29 18:13:47 +00:00
|
|
|
$dbServers = $config->get( MainConfigNames::DBservers );
|
2022-01-26 17:46:06 +00:00
|
|
|
if ( $dbServers ) {
|
|
|
|
|
foreach ( $dbServers as $i => $server ) {
|
|
|
|
|
$dbServers[$i]['user'] = $overrides['DBuser'];
|
|
|
|
|
$dbServers[$i]['password'] = $overrides['DBpassword'];
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2022-01-26 17:46:06 +00:00
|
|
|
$overrides['DBservers'] = $dbServers;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2022-01-26 17:46:06 +00:00
|
|
|
|
2022-04-29 18:13:47 +00:00
|
|
|
$lbFactoryConf = $config->get( MainConfigNames::LBFactoryConf );
|
2022-01-26 17:46:06 +00:00
|
|
|
if ( isset( $lbFactoryConf['serverTemplate'] ) ) {
|
|
|
|
|
$lbFactoryConf['serverTemplate']['user'] = $overrides['DBuser'];
|
|
|
|
|
$lbFactoryConf['serverTemplate']['password'] = $overrides['DBpassword'];
|
|
|
|
|
$overrides['LBFactoryConf'] = $lbFactoryConf;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2022-01-26 17:46:06 +00:00
|
|
|
|
Don't access MWServices prematurely in Maintenence.php
In case of the referenced addWiki bug, the following was happening:
1. MWServices were initialized before Setup.php is complete
and before extensions were loaded. Deprecation warning was emitted.
2. onMWServices hook was run, thus hook container was created, but
without any extensions loaded.
3. When global MWServices instance was allowed, and services were
reset, hook container was salvaged. When that happens, already
instantiated handlers are preserved, so onMediaWikiServices handlers
(none at this point) were preserved.
4. GlobalPreferences refefine PreferencesFactory, but it was not
actually redefined, since onMediaWikiServices handler array was
salvaged in 3
5. The rest of the hooks work ok, since they are dynamically initialized
after extensions were loaded.
6. One of the hook handlers, implemented in GlobalPreferences,
expected PreferencesFactory to be redefined, but it was not.
7. Boom!
Bug: T285894
Bug: T275453
Change-Id: I8b16529e93dc12484e3501fab4fd34fca70114ea
2021-06-30 19:22:10 +00:00
|
|
|
// TODO: once MediaWikiServices::getInstance() starts throwing exceptions
|
|
|
|
|
// and not deprecation warnings for premature access to service container,
|
|
|
|
|
// we can remove this line. This method is called before Setup.php,
|
|
|
|
|
// so it would be guaranteed DBLoadBalancerFactory is not yet initialized.
|
|
|
|
|
if ( MediaWikiServices::hasInstance() ) {
|
2023-08-21 22:47:44 +00:00
|
|
|
$service = $this->getServiceContainer()->peekService( 'DBLoadBalancerFactory' );
|
Don't access MWServices prematurely in Maintenence.php
In case of the referenced addWiki bug, the following was happening:
1. MWServices were initialized before Setup.php is complete
and before extensions were loaded. Deprecation warning was emitted.
2. onMWServices hook was run, thus hook container was created, but
without any extensions loaded.
3. When global MWServices instance was allowed, and services were
reset, hook container was salvaged. When that happens, already
instantiated handlers are preserved, so onMediaWikiServices handlers
(none at this point) were preserved.
4. GlobalPreferences refefine PreferencesFactory, but it was not
actually redefined, since onMediaWikiServices handler array was
salvaged in 3
5. The rest of the hooks work ok, since they are dynamically initialized
after extensions were loaded.
6. One of the hook handlers, implemented in GlobalPreferences,
expected PreferencesFactory to be redefined, but it was not.
7. Boom!
Bug: T285894
Bug: T275453
Change-Id: I8b16529e93dc12484e3501fab4fd34fca70114ea
2021-06-30 19:22:10 +00:00
|
|
|
if ( $service ) {
|
|
|
|
|
$service->destroy();
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->afterFinalSetup();
|
|
|
|
|
|
2022-01-26 17:46:06 +00:00
|
|
|
$overrides['ShowExceptionDetails'] = true;
|
|
|
|
|
$overrides['ShowHostname'] = true;
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2022-11-16 16:44:29 +00:00
|
|
|
ini_set( 'max_execution_time', '0' );
|
|
|
|
|
$settingsBuilder->putConfigValues( $overrides );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-04-05 18:30:55 +00:00
|
|
|
* Override to perform any required operation at the end of initialisation
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
protected function afterFinalSetup() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Support function for cleaning up redundant text records
|
|
|
|
|
* @param bool $delete Whether or not to actually delete the records
|
|
|
|
|
* @author Rob Church <robchur@gmail.com>
|
|
|
|
|
*/
|
|
|
|
|
public function purgeRedundantText( $delete = true ) {
|
|
|
|
|
# Data should come off the master, wrapped in a transaction
|
2024-01-17 18:53:40 +00:00
|
|
|
$dbw = $this->getPrimaryDB();
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->beginTransaction( $dbw, __METHOD__ );
|
|
|
|
|
|
|
|
|
|
# Get "active" text records via the content table
|
|
|
|
|
$cur = [];
|
|
|
|
|
$this->output( 'Searching for active text records via contents table...' );
|
2023-09-21 11:54:38 +00:00
|
|
|
$res = $dbw->newSelectQueryBuilder()
|
|
|
|
|
->select( 'content_address' )
|
|
|
|
|
->distinct()
|
|
|
|
|
->from( 'content' )
|
|
|
|
|
->caller( __METHOD__ )->fetchResultSet();
|
2023-08-21 22:47:44 +00:00
|
|
|
$blobStore = $this->getServiceContainer()->getBlobStore();
|
2020-03-03 14:33:54 +00:00
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
// @phan-suppress-next-line PhanUndeclaredMethod
|
|
|
|
|
$textId = $blobStore->getTextIdFromAddress( $row->content_address );
|
|
|
|
|
if ( $textId ) {
|
|
|
|
|
$cur[] = $textId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
|
|
|
|
|
# Get the IDs of all text records not in these sets
|
|
|
|
|
$this->output( 'Searching for inactive text records...' );
|
2024-08-19 09:43:35 +00:00
|
|
|
$textTableQueryBuilder = $dbw->newSelectQueryBuilder()
|
2023-09-21 11:54:38 +00:00
|
|
|
->select( 'old_id' )
|
|
|
|
|
->distinct()
|
2024-08-19 09:43:35 +00:00
|
|
|
->from( 'text' );
|
|
|
|
|
if ( count( $cur ) ) {
|
|
|
|
|
$textTableQueryBuilder->where( $dbw->expr( 'old_id', '!=', $cur ) );
|
|
|
|
|
}
|
|
|
|
|
$res = $textTableQueryBuilder
|
|
|
|
|
->caller( __METHOD__ )
|
|
|
|
|
->fetchResultSet();
|
2020-03-03 14:33:54 +00:00
|
|
|
$old = [];
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
$old[] = $row->old_id;
|
|
|
|
|
}
|
|
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
|
|
|
|
|
# Inform the user of what we're going to do
|
|
|
|
|
$count = count( $old );
|
|
|
|
|
$this->output( "$count inactive items found.\n" );
|
|
|
|
|
|
|
|
|
|
# Delete as appropriate
|
|
|
|
|
if ( $delete && $count ) {
|
|
|
|
|
$this->output( 'Deleting...' );
|
2024-04-12 18:12:05 +00:00
|
|
|
$dbw->newDeleteQueryBuilder()
|
|
|
|
|
->deleteFrom( 'text' )
|
|
|
|
|
->where( [ 'old_id' => $old ] )
|
|
|
|
|
->caller( __METHOD__ )
|
|
|
|
|
->execute();
|
2020-03-03 14:33:54 +00:00
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->commitTransaction( $dbw, __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the maintenance directory.
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function getDir() {
|
|
|
|
|
return __DIR__ . '/../';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a database to be used by current maintenance script.
|
|
|
|
|
*
|
|
|
|
|
* This uses the main LBFactory instance by default unless overriden via setDB().
|
|
|
|
|
*
|
|
|
|
|
* This function has the same parameters as LoadBalancer::getConnection().
|
|
|
|
|
*
|
2024-01-17 18:53:40 +00:00
|
|
|
* For simple cases, use ::getReplicaDB() or ::getPrimaryDB() instead.
|
|
|
|
|
*
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-12 11:13:22 +00:00
|
|
|
*
|
2021-04-19 01:02:08 +00:00
|
|
|
* @param int $db DB index (DB_REPLICA/DB_PRIMARY)
|
2020-03-03 14:33:54 +00:00
|
|
|
* @param string|string[] $groups default: empty array
|
|
|
|
|
* @param string|bool $dbDomain default: current wiki
|
|
|
|
|
* @return IMaintainableDatabase
|
|
|
|
|
*/
|
|
|
|
|
protected function getDB( $db, $groups = [], $dbDomain = false ) {
|
|
|
|
|
if ( $this->mDb === null ) {
|
2023-08-21 22:47:44 +00:00
|
|
|
return $this->getServiceContainer()
|
2020-03-03 14:33:54 +00:00
|
|
|
->getDBLoadBalancerFactory()
|
|
|
|
|
->getMainLB( $dbDomain )
|
|
|
|
|
->getMaintenanceConnectionRef( $db, $groups, $dbDomain );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->mDb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets database object to be returned by getDB().
|
2021-07-08 02:51:13 +00:00
|
|
|
* @stable to override
|
2020-03-03 14:33:54 +00:00
|
|
|
*
|
|
|
|
|
* @param IMaintainableDatabase $db
|
|
|
|
|
*/
|
|
|
|
|
public function setDB( IMaintainableDatabase $db ) {
|
|
|
|
|
$this->mDb = $db;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 18:53:40 +00:00
|
|
|
/**
|
|
|
|
|
* @return IReadableDatabase
|
|
|
|
|
* @since 1.42
|
|
|
|
|
*/
|
|
|
|
|
protected function getReplicaDB(): IReadableDatabase {
|
|
|
|
|
if ( $this->dbProvider === null ) {
|
2024-01-23 16:09:20 +00:00
|
|
|
$this->dbProvider = $this->getServiceContainer()->getConnectionProvider();
|
2024-01-17 18:53:40 +00:00
|
|
|
}
|
|
|
|
|
return $this->dbProvider->getReplicaDatabase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return IDatabase
|
|
|
|
|
* @since 1.42
|
|
|
|
|
*/
|
|
|
|
|
protected function getPrimaryDB(): IDatabase {
|
|
|
|
|
if ( $this->dbProvider === null ) {
|
2024-01-23 16:09:20 +00:00
|
|
|
$this->dbProvider = $this->getServiceContainer()->getConnectionProvider();
|
2024-01-17 18:53:40 +00:00
|
|
|
}
|
|
|
|
|
return $this->dbProvider->getPrimaryDatabase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @param IConnectionProvider $dbProvider
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function setDBProvider( IConnectionProvider $dbProvider ) {
|
|
|
|
|
$this->dbProvider = $dbProvider;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
/**
|
2021-03-10 17:29:10 +00:00
|
|
|
* Begin a transaction on a DB
|
2020-03-03 14:33:54 +00:00
|
|
|
*
|
|
|
|
|
* This method makes it clear that begin() is called from a maintenance script,
|
|
|
|
|
* which has outermost scope. This is safe, unlike $dbw->begin() called in other places.
|
|
|
|
|
*
|
|
|
|
|
* @param IDatabase $dbw
|
|
|
|
|
* @param string $fname Caller name
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
|
|
|
|
protected function beginTransaction( IDatabase $dbw, $fname ) {
|
|
|
|
|
$dbw->begin( $fname );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-03-10 17:29:10 +00:00
|
|
|
* Commit the transaction on a DB handle and wait for replica DBs to catch up
|
2020-03-03 14:33:54 +00:00
|
|
|
*
|
|
|
|
|
* This method makes it clear that commit() is called from a maintenance script,
|
|
|
|
|
* which has outermost scope. This is safe, unlike $dbw->commit() called in other places.
|
|
|
|
|
*
|
|
|
|
|
* @param IDatabase $dbw
|
|
|
|
|
* @param string $fname Caller name
|
|
|
|
|
* @return bool Whether the replica DB wait succeeded
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
|
|
|
|
protected function commitTransaction( IDatabase $dbw, $fname ) {
|
|
|
|
|
$dbw->commit( $fname );
|
2021-03-17 22:52:54 +00:00
|
|
|
return $this->waitForReplication();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-05-24 13:50:02 +00:00
|
|
|
* Wait for replica DBs to catch up.
|
|
|
|
|
*
|
|
|
|
|
* @note Since 1.39, this also calls LBFactory::autoReconfigure().
|
2021-03-17 22:52:54 +00:00
|
|
|
*
|
|
|
|
|
* @return bool Whether the replica DB wait succeeded
|
|
|
|
|
* @since 1.36
|
|
|
|
|
*/
|
|
|
|
|
protected function waitForReplication() {
|
2023-08-21 22:47:44 +00:00
|
|
|
$lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
|
2020-03-03 14:33:54 +00:00
|
|
|
$waitSucceeded = $lbFactory->waitForReplication(
|
|
|
|
|
[ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
|
|
|
|
|
);
|
|
|
|
|
$this->lastReplicationWait = microtime( true );
|
2022-05-24 13:50:02 +00:00
|
|
|
|
|
|
|
|
// If possible, apply changes to the database configuration.
|
|
|
|
|
// The primary use case for this is taking replicas out of rotation.
|
|
|
|
|
// Long-running scripts may otherwise keep connections to
|
|
|
|
|
// de-pooled database hosts, and may even re-connect to them.
|
|
|
|
|
// If no config callback was configured, this has no effect.
|
|
|
|
|
$lbFactory->autoReconfigure();
|
|
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
return $waitSucceeded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-03-10 17:29:10 +00:00
|
|
|
* Rollback the transaction on a DB handle
|
2020-03-03 14:33:54 +00:00
|
|
|
*
|
|
|
|
|
* This method makes it clear that rollback() is called from a maintenance script,
|
|
|
|
|
* which has outermost scope. This is safe, unlike $dbw->rollback() called in other places.
|
|
|
|
|
*
|
|
|
|
|
* @param IDatabase $dbw
|
|
|
|
|
* @param string $fname Caller name
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
|
|
|
|
protected function rollbackTransaction( IDatabase $dbw, $fname ) {
|
|
|
|
|
$dbw->rollback( $fname );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Count down from $seconds to zero on the terminal, with a one-second pause
|
|
|
|
|
* between showing each number. If the maintenance script is in quiet mode,
|
|
|
|
|
* this function does nothing.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.31
|
|
|
|
|
*
|
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
|
* @param int $seconds
|
|
|
|
|
*/
|
|
|
|
|
protected function countDown( $seconds ) {
|
|
|
|
|
if ( $this->isQuiet() ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for ( $i = $seconds; $i >= 0; $i-- ) {
|
|
|
|
|
if ( $i != $seconds ) {
|
2022-03-03 21:57:43 +00:00
|
|
|
$this->output( str_repeat( "\x08", strlen( (string)( $i + 1 ) ) ) );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2022-03-05 20:05:01 +00:00
|
|
|
$this->output( (string)$i );
|
2020-03-03 14:33:54 +00:00
|
|
|
if ( $i ) {
|
|
|
|
|
sleep( 1 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrapper for posix_isatty()
|
|
|
|
|
* We default as considering stdin a tty (for nice readline methods)
|
|
|
|
|
* but treating stout as not a tty to avoid color codes
|
|
|
|
|
*
|
|
|
|
|
* @param mixed $fd File descriptor
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public static function posix_isatty( $fd ) {
|
|
|
|
|
if ( !function_exists( 'posix_isatty' ) ) {
|
|
|
|
|
return !$fd;
|
|
|
|
|
}
|
2021-11-08 13:31:14 +00:00
|
|
|
|
|
|
|
|
return posix_isatty( $fd );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Prompt the console for input
|
|
|
|
|
* @param string $prompt What to begin the line with, like '> '
|
2022-03-14 21:02:22 +00:00
|
|
|
* @return string|false Response
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public static function readconsole( $prompt = '> ' ) {
|
|
|
|
|
static $isatty = null;
|
2024-08-15 09:48:40 +00:00
|
|
|
$isatty ??= self::posix_isatty( 0 /*STDIN*/ );
|
2020-03-03 14:33:54 +00:00
|
|
|
|
|
|
|
|
if ( $isatty && function_exists( 'readline' ) ) {
|
|
|
|
|
return readline( $prompt );
|
2021-11-08 13:31:14 +00:00
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
|
2021-11-08 13:31:14 +00:00
|
|
|
if ( $isatty ) {
|
|
|
|
|
$st = self::readlineEmulation( $prompt );
|
|
|
|
|
} elseif ( feof( STDIN ) ) {
|
|
|
|
|
$st = false;
|
|
|
|
|
} else {
|
|
|
|
|
$st = fgets( STDIN, 1024 );
|
|
|
|
|
}
|
|
|
|
|
if ( $st === false ) {
|
|
|
|
|
return false;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2021-11-08 13:31:14 +00:00
|
|
|
|
|
|
|
|
return trim( $st );
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Emulate readline()
|
|
|
|
|
* @param string $prompt What to begin the line with, like '> '
|
2022-03-03 20:33:11 +00:00
|
|
|
* @return string|false
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
private static function readlineEmulation( $prompt ) {
|
|
|
|
|
$bash = ExecutableFinder::findInDefaultPaths( 'bash' );
|
|
|
|
|
if ( !wfIsWindows() && $bash ) {
|
|
|
|
|
$encPrompt = Shell::escape( $prompt );
|
|
|
|
|
$command = "read -er -p $encPrompt && echo \"\$REPLY\"";
|
Don't pass stdin to commands by default
By default on Linux, any maintenance script that runs a command that reads
from stdin hangs with SIGTTIN until it is killed by the timeout. So:
* Instead of passing the stdin FD to subprocesses by default, make the
default input be an empty string. This only really affects maintenance
scripts, since in web requests, stdin is /dev/null, effectively an
empty string already.
* Add Command::passStdin(), which reverts to the previous behaviour,
except that the wall clock timeout is disabled to avoid a hang due to
SIGTTOU/SIGTTIN.
* Use passStdin() when running "stty size".
* Fix the hilariously broken Maintenance::readlineEmulation(), which
accidentally tried to use Shell::escape() to run a command, as if it
worked like wfShellExec().
* Add Command::forwardStderr(), to support readlineEmulation().
Bug: T206957
Change-Id: I42a0ae8be885ab371ae9bd58c68a0f75b4b3bc79
2020-08-20 06:12:27 +00:00
|
|
|
$result = Shell::command( $bash, '-c', $command )
|
|
|
|
|
->passStdin()
|
|
|
|
|
->forwardStderr()
|
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
|
|
if ( $result->getExitCode() == 0 ) {
|
|
|
|
|
return $result->getStdout();
|
2021-11-08 13:31:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $result->getExitCode() == 127 ) {
|
2020-03-03 14:33:54 +00:00
|
|
|
// Couldn't execute bash even though we thought we saw it.
|
|
|
|
|
// Shell probably spit out an error message, sorry :(
|
|
|
|
|
// Fall through to fgets()...
|
|
|
|
|
} else {
|
|
|
|
|
// EOF/ctrl+D
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback... we'll have no editing controls, EWWW
|
|
|
|
|
if ( feof( STDIN ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
print $prompt;
|
|
|
|
|
|
|
|
|
|
return fgets( STDIN, 1024 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the terminal size as a two-element array where the first element
|
|
|
|
|
* is the width (number of columns) and the second element is the height
|
|
|
|
|
* (number of rows).
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public static function getTermSize() {
|
2021-01-31 09:43:49 +00:00
|
|
|
static $termSize = null;
|
|
|
|
|
|
|
|
|
|
if ( $termSize !== null ) {
|
|
|
|
|
return $termSize;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2021-01-31 09:43:49 +00:00
|
|
|
|
|
|
|
|
$default = [ 80, 50 ];
|
|
|
|
|
|
|
|
|
|
if ( wfIsWindows() || Shell::isDisabled() ) {
|
|
|
|
|
$termSize = $default;
|
|
|
|
|
|
|
|
|
|
return $termSize;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2021-01-31 09:43:49 +00:00
|
|
|
|
2020-03-03 14:33:54 +00:00
|
|
|
// It's possible to get the screen size with VT-100 terminal escapes,
|
|
|
|
|
// but reading the responses is not possible without setting raw mode
|
|
|
|
|
// (unless you want to require the user to press enter), and that
|
|
|
|
|
// requires an ioctl(), which we can't do. So we have to shell out to
|
|
|
|
|
// something that can do the relevant syscalls. There are a few
|
|
|
|
|
// options. Linux and Mac OS X both have "stty size" which does the
|
|
|
|
|
// job directly.
|
2021-01-31 09:43:49 +00:00
|
|
|
$result = Shell::command( 'stty', 'size' )->passStdin()->execute();
|
|
|
|
|
if ( $result->getExitCode() !== 0 ||
|
|
|
|
|
!preg_match( '/^(\d+) (\d+)$/', $result->getStdout(), $m )
|
|
|
|
|
) {
|
|
|
|
|
$termSize = $default;
|
|
|
|
|
|
|
|
|
|
return $termSize;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2021-01-31 09:43:49 +00:00
|
|
|
|
|
|
|
|
$termSize = [ intval( $m[2] ), intval( $m[1] ) ];
|
|
|
|
|
|
|
|
|
|
return $termSize;
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Call this to set up the autoloader to allow classes to be used from the
|
|
|
|
|
* tests directory.
|
2023-03-27 17:25:03 +00:00
|
|
|
*
|
|
|
|
|
* @deprecated since 1.41. Set the MW_AUTOLOAD_TEST_CLASSES in file scope instead.
|
2020-03-03 14:33:54 +00:00
|
|
|
*/
|
|
|
|
|
public static function requireTestsAutoloader() {
|
|
|
|
|
require_once __DIR__ . '/../../tests/common/TestsAutoLoader.php';
|
|
|
|
|
}
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a HookContainer, for running extension hooks or for hook metadata.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.35
|
|
|
|
|
* @return HookContainer
|
|
|
|
|
*/
|
|
|
|
|
protected function getHookContainer() {
|
|
|
|
|
if ( !$this->hookContainer ) {
|
2023-08-21 22:47:44 +00:00
|
|
|
$this->hookContainer = $this->getServiceContainer()->getHookContainer();
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
}
|
|
|
|
|
return $this->hookContainer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a HookRunner for running core hooks.
|
|
|
|
|
*
|
|
|
|
|
* @internal This is for use by core only. Hook interfaces may be removed
|
|
|
|
|
* without notice.
|
|
|
|
|
* @since 1.35
|
|
|
|
|
* @return HookRunner
|
|
|
|
|
*/
|
|
|
|
|
protected function getHookRunner() {
|
|
|
|
|
if ( !$this->hookRunner ) {
|
|
|
|
|
$this->hookRunner = new HookRunner( $this->getHookContainer() );
|
|
|
|
|
}
|
|
|
|
|
return $this->hookRunner;
|
|
|
|
|
}
|
2020-09-07 21:53:07 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Utility function to parse a string (perhaps from a command line option)
|
|
|
|
|
* into a list of integers (perhaps some kind of numeric IDs).
|
|
|
|
|
*
|
2020-09-23 16:50:48 +00:00
|
|
|
* @since 1.35
|
|
|
|
|
*
|
2020-09-07 21:53:07 +00:00
|
|
|
* @param string $text
|
|
|
|
|
*
|
|
|
|
|
* @return int[]
|
|
|
|
|
*/
|
|
|
|
|
protected function parseIntList( $text ) {
|
|
|
|
|
$ids = preg_split( '/[\s,;:|]+/', $text );
|
|
|
|
|
$ids = array_map(
|
2021-02-07 02:17:10 +00:00
|
|
|
static function ( $id ) {
|
2020-09-07 21:53:07 +00:00
|
|
|
return (int)$id;
|
|
|
|
|
},
|
|
|
|
|
$ids
|
|
|
|
|
);
|
|
|
|
|
return array_filter( $ids );
|
|
|
|
|
}
|
2021-07-13 14:37:04 +00:00
|
|
|
|
|
|
|
|
/**
|
2024-08-10 09:51:38 +00:00
|
|
|
* @param string $errorMsg Error message to be displayed if neither --user or --userid are passed.
|
2021-07-13 14:37:04 +00:00
|
|
|
*
|
|
|
|
|
* @since 1.37
|
|
|
|
|
*
|
|
|
|
|
* @return User
|
|
|
|
|
*/
|
|
|
|
|
protected function validateUserOption( $errorMsg ) {
|
|
|
|
|
if ( $this->hasOption( "user" ) ) {
|
|
|
|
|
$user = User::newFromName( $this->getOption( 'user' ) );
|
|
|
|
|
} elseif ( $this->hasOption( "userid" ) ) {
|
|
|
|
|
$user = User::newFromId( $this->getOption( 'userid' ) );
|
|
|
|
|
} else {
|
|
|
|
|
$this->fatalError( $errorMsg );
|
|
|
|
|
}
|
2022-04-29 18:32:20 +00:00
|
|
|
if ( !$user || !$user->isRegistered() ) {
|
2021-07-13 14:37:04 +00:00
|
|
|
if ( $this->hasOption( "user" ) ) {
|
|
|
|
|
$this->fatalError( "No such user: " . $this->getOption( 'user' ) );
|
|
|
|
|
} elseif ( $this->hasOption( "userid" ) ) {
|
|
|
|
|
$this->fatalError( "No such user id: " . $this->getOption( 'userid' ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $user;
|
|
|
|
|
}
|
2024-05-04 14:28:00 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $prompt The prompt to display to the user
|
|
|
|
|
* @param string|null $default The default value to return if the user just presses enter
|
|
|
|
|
*
|
|
|
|
|
* @return string|null
|
|
|
|
|
*
|
|
|
|
|
* @since 1.43
|
|
|
|
|
*/
|
2024-10-16 18:58:33 +00:00
|
|
|
protected function prompt( string $prompt, ?string $default = null ): ?string {
|
2024-05-04 14:28:00 +00:00
|
|
|
$defaultText = $default === null ? ' > ' : " [{$default}] > ";
|
|
|
|
|
$promptWithDefault = $prompt . $defaultText;
|
|
|
|
|
$line = self::readconsole( $promptWithDefault );
|
|
|
|
|
if ( $line === false ) {
|
|
|
|
|
return $default;
|
|
|
|
|
}
|
|
|
|
|
if ( $line === '' ) {
|
|
|
|
|
return $default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $line;
|
|
|
|
|
}
|
2020-03-03 14:33:54 +00:00
|
|
|
}
|
2024-08-11 16:32:58 +00:00
|
|
|
|
|
|
|
|
/** @deprecated class alias since 1.43 */
|
|
|
|
|
class_alias( Maintenance::class, 'Maintenance' );
|