24 if ( !defined(
'MEDIAWIKI' ) ) {
26 require_once __DIR__ .
'/../commandLine.inc';
30 if ( isset(
$args[0] ) ) {
35 $cs->check( $fix, $xml );
52 'restore text' =>
'Damaged text, need to be restored from a backup',
53 'restore revision' =>
'Damaged revision row, need to be restored from a backup',
54 'unfixable' =>
'Unexpected errors with no automated fixing method',
55 'fixed' =>
'Errors already fixed',
56 'fixable' =>
'Errors which would already be fixed if --fix was specified',
59 function check( $fix =
false, $xml =
'' ) {
62 print "Checking, will fix errors if possible...\n";
64 print "Checking...\n";
66 $maxRevId =
$dbr->selectField(
'revision',
'MAX(rev_id)',
false, __METHOD__ );
70 $knownFlags = [
'external',
'gzip',
'object',
'utf-8' ];
73 'restore revision' => [],
79 for ( $chunkStart = 1; $chunkStart < $maxRevId; $chunkStart += $chunkSize ) {
80 $chunkEnd = $chunkStart + $chunkSize - 1;
86 $res =
$dbr->select(
'revision', [
'rev_id',
'rev_text_id' ],
87 [
"rev_id BETWEEN $chunkStart AND $chunkEnd" ], __METHOD__ );
89 $this->oldIdMap[$row->rev_id] = $row->rev_text_id;
93 if ( !count( $this->oldIdMap ) ) {
98 $missingTextRows = array_flip( $this->oldIdMap );
101 $res =
$dbr->select(
'text', [
'old_id',
'old_flags' ],
102 'old_id IN (' . implode(
',', $this->oldIdMap ) .
')', __METHOD__ );
103 foreach (
$res as $row ) {
111 $flagStats = $flagStats + [
$flags => 0 ];
116 unset( $missingTextRows[$row->old_id] );
119 if ( $flags ==
'' ) {
122 $flagArray = explode(
',', $flags );
124 if ( in_array(
'external', $flagArray ) ) {
125 $externalRevs[] = $id;
126 } elseif ( in_array(
'object', $flagArray ) ) {
131 if ( $flags ==
'0' ) {
135 $this->
error(
'fixed',
"Warning: old_flags set to 0", $id );
138 $dbw->update(
'text', [
'old_flags' =>
'' ],
139 [
'old_id' => $id ], __METHOD__ );
142 $this->
error(
'fixable',
"Warning: old_flags set to 0", $id );
144 } elseif ( count( array_diff( $flagArray, $knownFlags ) ) ) {
145 $this->
error(
'unfixable',
"Error: invalid flags field \"$flags\"", $id );
151 foreach ( $missingTextRows
as $oldId =>
$revId ) {
152 $this->
error(
'restore revision',
"Error: missing text row", $oldId );
156 $externalConcatBlobs = [];
157 $externalNormalBlobs = [];
158 if ( count( $externalRevs ) ) {
159 $res =
$dbr->select(
'text', [
'old_id',
'old_flags',
'old_text' ],
160 [
'old_id IN (' . implode(
',', $externalRevs ) .
')' ], __METHOD__ );
161 foreach (
$res as $row ) {
162 $urlParts = explode(
'://', $row->old_text, 2 );
163 if ( count( $urlParts ) !== 2 || $urlParts[1] ==
'' ) {
164 $this->
error(
'restore text',
"Error: invalid URL \"{$row->old_text}\"", $row->old_id );
167 list( $proto, ) = $urlParts;
168 if ( $proto !=
'DB' ) {
169 $this->
error(
'restore text',
"Error: invalid external protocol \"$proto\"", $row->old_id );
172 $path = explode(
'/', $row->old_text );
175 if ( isset(
$path[4] ) ) {
176 $externalConcatBlobs[$cluster][$id][] = $row->old_id;
178 $externalNormalBlobs[$cluster][$id][] = $row->old_id;
188 if ( count( $externalNormalBlobs ) ) {
189 if ( is_null( $this->dbStore ) ) {
192 foreach ( $externalConcatBlobs
as $cluster => $xBlobIds ) {
193 $blobIds = array_keys( $xBlobIds );
194 $extDb =& $this->dbStore->getSlave( $cluster );
195 $blobsTable = $this->dbStore->getTable( $extDb );
196 $res = $extDb->select( $blobsTable,
198 [
'blob_id IN( ' . implode(
',', $blobIds ) .
')' ], __METHOD__ );
199 foreach (
$res as $row ) {
200 unset( $xBlobIds[$row->blob_id] );
202 $extDb->freeResult(
$res );
204 foreach ( $xBlobIds
as $blobId => $oldId ) {
205 $this->
error(
'restore text',
"Error: missing target $blobId for one-part ES URL", $oldId );
214 if ( count( $objectRevs ) ) {
218 [
'old_id',
'old_flags',
"LEFT(old_text, $headerLength) AS header" ],
219 [
'old_id IN (' . implode(
',', $objectRevs ) .
')' ],
222 foreach (
$res as $row ) {
223 $oldId = $row->old_id;
225 if ( !preg_match(
'/^O:(\d+):"(\w+)"/', $row->header,
$matches ) ) {
226 $this->
error(
'restore text',
"Error: invalid object header", $oldId );
230 $className = strtolower(
$matches[2] );
231 if ( strlen( $className ) !=
$matches[1] ) {
234 "Error: invalid object header, wrong class name length",
240 $objectStats = $objectStats + [ $className => 0 ];
241 $objectStats[$className]++;
243 switch ( $className ) {
244 case 'concatenatedgziphistoryblob':
247 case 'historyblobstub':
248 case 'historyblobcurstub':
249 if ( strlen( $row->header ) == $headerLength ) {
250 $this->
error(
'unfixable',
"Error: overlong stub header", $oldId );
254 if ( !is_object( $stubObj ) ) {
255 $this->
error(
'restore text',
"Error: unable to unserialize stub object", $oldId );
258 if ( $className ==
'historyblobstub' ) {
259 $concatBlobs[$stubObj->mOldId][] = $oldId;
261 $curIds[$stubObj->mCurId][] = $oldId;
265 $this->
error(
'unfixable',
"Error: unrecognised object class \"$className\"", $oldId );
272 $externalConcatBlobs = [];
273 if ( count( $concatBlobs ) ) {
277 [
'old_id',
'old_flags',
"LEFT(old_text, $headerLength) AS header" ],
278 [
'old_id IN (' . implode(
',', array_keys( $concatBlobs ) ) .
')' ],
281 foreach (
$res as $row ) {
282 $flags = explode(
',', $row->old_flags );
283 if ( in_array(
'external',
$flags ) ) {
285 if ( in_array(
'object',
$flags ) ) {
286 $urlParts = explode(
'/', $row->header );
287 if ( $urlParts[0] !=
'DB:' ) {
290 "Error: unrecognised external storage type \"{$urlParts[0]}",
294 $cluster = $urlParts[2];
296 if ( !isset( $externalConcatBlobs[$cluster][$id] ) ) {
297 $externalConcatBlobs[$cluster][$id] = [];
299 $externalConcatBlobs[$cluster][$id] = array_merge(
300 $externalConcatBlobs[$cluster][$id], $concatBlobs[$row->old_id]
306 "Error: invalid flags \"{$row->old_flags}\" on concat bulk row {$row->old_id}",
307 $concatBlobs[$row->old_id] );
309 } elseif ( strcasecmp(
310 substr( $row->header, 0, strlen( self::CONCAT_HEADER ) ),
315 "Error: Incorrect object header for concat bulk row {$row->old_id}",
316 $concatBlobs[$row->old_id]
320 unset( $concatBlobs[$row->old_id] );
330 print "\n\nErrors:\n";
332 if ( count( $errors ) ) {
333 $description = $this->errorDescriptions[
$name];
334 echo
"$description: " . implode(
',', array_keys( $errors ) ) .
"\n";
338 if ( count( $this->
errors[
'restore text'] ) && $fix ) {
339 if ( (
string)$xml !==
'' ) {
342 echo
"Can't fix text, no XML backup specified\n";
346 print "\nFlag statistics:\n";
347 $total = array_sum( $flagStats );
348 foreach ( $flagStats
as $flag =>
$count ) {
349 printf(
"%-30s %10d %5.2f%%\n", $flag,
$count,
$count / $total * 100 );
351 print "\nLocal object statistics:\n";
352 $total = array_sum( $objectStats );
353 foreach ( $objectStats
as $className =>
$count ) {
354 printf(
"%-30s %10d %5.2f%%\n", $className,
$count,
$count / $total * 100 );
359 if ( is_array( $ids ) && count( $ids ) == 1 ) {
360 $ids = reset( $ids );
362 if ( is_array( $ids ) ) {
364 foreach ( $ids
as $id ) {
365 $revIds = array_merge( $revIds, array_keys( $this->oldIdMap, $id ) );
367 print "$msg in text rows " . implode(
', ', $ids ) .
368 ", revisions " . implode(
', ', $revIds ) .
"\n";
371 $revIds = array_keys( $this->oldIdMap, $id );
372 if ( count( $revIds ) == 1 ) {
373 print "$msg in old_id $id, rev_id {$revIds[0]}\n";
375 print "$msg in old_id $id, revisions " . implode(
', ', $revIds ) .
"\n";
382 if ( !count( $externalConcatBlobs ) ) {
386 if ( is_null( $this->dbStore ) ) {
390 foreach ( $externalConcatBlobs
as $cluster => $oldIds ) {
391 $blobIds = array_keys( $oldIds );
392 $extDb =& $this->dbStore->getSlave( $cluster );
393 $blobsTable = $this->dbStore->getTable( $extDb );
394 $headerLength = strlen( self::CONCAT_HEADER );
395 $res = $extDb->select( $blobsTable,
396 [
'blob_id',
"LEFT(blob_text, $headerLength) AS header" ],
397 [
'blob_id IN( ' . implode(
',', $blobIds ) .
')' ], __METHOD__ );
398 foreach (
$res as $row ) {
399 if ( strcasecmp( $row->header, self::CONCAT_HEADER ) ) {
402 "Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL",
403 $oldIds[$row->blob_id]
406 unset( $oldIds[$row->blob_id] );
408 $extDb->freeResult(
$res );
411 foreach ( $oldIds
as $blobId => $oldIds2 ) {
414 "Error: missing target $cluster/$blobId for two-part ES URL",
425 if ( !count( $revIds ) ) {
429 print "Restoring text from XML backup...\n";
431 $revFileName =
"$tmpDir/broken-revlist-$wgDBname";
432 $filteredXmlFileName =
"$tmpDir/filtered-$wgDBname.xml";
435 if ( !file_put_contents( $revFileName, implode(
"\n", $revIds ) ) ) {
436 echo
"Error writing revision list, can't restore text\n";
442 echo
"Filtering XML dump...\n";
444 passthru(
'mwdumper ' .
446 "--output=file:$filteredXmlFileName",
447 "--filter=revlist:$revFileName",
453 echo
"mwdumper died with exit status $exitStatus\n";
458 $file = fopen( $filteredXmlFileName,
'r' );
460 echo
"Unable to open filtered XML file\n";
475 $importer->setRevisionCallback( [ $this,
'importRevision' ] );
476 $importer->doImport();
480 $id = $revision->getID();
482 $id = $id ? $id :
'';
485 echo
"Revision $id is broken, we have no content available\n";
491 if ( $text ===
'' ) {
497 echo
"Revision $id is blank in the dump, may have been broken before export\n";
504 echo
"No id tag in revision, can't import\n";
511 $oldId =
$dbr->selectField(
'revision',
'rev_text_id', [
'rev_id' => $id ], __METHOD__ );
513 echo
"Missing revision row for rev_id $id\n";
523 $dbw->update(
'text',
524 [
'old_flags' =>
$flags,
'old_text' => $text ],
525 [
'old_id' => $oldId ],
526 __METHOD__, [
'LIMIT' => 1 ]
530 unset( $this->
errors[
'restore text'][$id] );
531 $this->
errors[
'fixed'][$id] =
true;
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
checkExternalConcatBlobs($externalConcatBlobs)
XML file reader for the page data importer.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
it s the revision text itself In either if gzip is the revision text is gzipped $flags
when a variable name is used in a it is silently declared as a new local masking the global
check($fix=false, $xml= '')
Maintenance script to do various checks on external storage.
wfTempDir()
Tries to get the system directory for temporary files.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
Imports a XML dump from a file (either from file upload, files on disk, or HTTP)
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
if the prop value should be in the metadata multi language array can modify can modify indexed by page_id indexed by prefixed DB keys can modify can modify can modify this should be populated with an alert message to that effect to be fed to an HTMLForm object and populate $result with the reason in the form of error messages should be plain text with no special etc to show that they re errors
static getDefaultInstance()
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
wfEscapeShellArg()
Windows-compatible version of escapeshellarg() Windows doesn't recognise single-quotes in the shell...
importRevision(&$revision, &$importer)
restoreText($revIds, $xml)
controlled by $wgMainCacheType controlled by $wgParserCacheType controlled by $wgMessageCacheType If you set CACHE_NONE to one of the three control default value for MediaWiki still create a but requests to it are no ops and we always fall through to the database If the cache daemon can t be it should also disable itself fairly smoothly By $wgMemc is used but when it is $parserMemc or $messageMemc this is mentioned $wgDBname
DB accessable external objects.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
global $optionsWithoutArgs
Allows to change the fields on the form that will be generated $name