MediaWiki  master
rebuildFileCache.php
Go to the documentation of this file.
1 <?php
27 use Wikimedia\AtEase\AtEase;
28 
29 require_once __DIR__ . '/Maintenance.php';
30 
37  private $enabled = true;
38 
39  public function __construct() {
40  parent::__construct();
41  $this->addDescription( 'Build the file cache' );
42  $this->addOption( 'start', 'Page_id to start from', false, true );
43  $this->addOption( 'end', 'Page_id to end on', false, true );
44  $this->addOption( 'overwrite', 'Refresh page cache' );
45  $this->addOption( 'all', 'Build the file cache for pages in all namespaces, not just content pages' );
46  $this->setBatchSize( 100 );
47  }
48 
49  public function finalSetup( SettingsBuilder $settingsBuilder = null ) {
50  $this->enabled = $settingsBuilder->getConfig()->get( MainConfigNames::UseFileCache );
51  // Script will handle capturing output and saving it itself
52  $settingsBuilder->putConfigValue( MainConfigNames::UseFileCache, false );
53 
54  // Avoid DB writes (like enotif/counters)
55  MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode()
56  ->setReason( 'Building cache' );
57 
58  // Ensure no debug-specific logic ends up in the cache (must be after Setup.php)
60 
61  parent::finalSetup( $settingsBuilder );
62  }
63 
64  public function execute() {
65  if ( !$this->enabled ) {
66  $this->fatalError( "Nothing to do -- \$wgUseFileCache is disabled." );
67  }
68 
69  $start = $this->getOption( 'start', "0" );
70  if ( !ctype_digit( $start ) ) {
71  $this->fatalError( "Invalid value for start parameter." );
72  }
73  $start = intval( $start );
74 
75  $end = $this->getOption( 'end', "0" );
76  if ( !ctype_digit( $end ) ) {
77  $this->fatalError( "Invalid value for end parameter." );
78  }
79  $end = intval( $end );
80 
81  $this->output( "Building page file cache from page_id {$start}!\n" );
82 
83  $dbr = $this->getDB( DB_REPLICA );
84  $batchSize = $this->getBatchSize();
85  $overwrite = $this->hasOption( 'overwrite' );
86  $start = ( $start > 0 )
87  ? $start
88  : $dbr->selectField( 'page', 'MIN(page_id)', '', __METHOD__ );
89  $end = ( $end > 0 )
90  ? $end
91  : $dbr->selectField( 'page', 'MAX(page_id)', '', __METHOD__ );
92  if ( !$start ) {
93  $this->fatalError( "Nothing to do." );
94  }
95 
96  $where = [];
97  if ( !$this->getOption( 'all' ) ) {
98  // If 'all' isn't passed as an option, just fall back to previous behaviour
99  // of using content namespaces
100  $where['page_namespace'] =
101  MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces();
102  }
103 
104  // Mock request (hack, no real client)
105  $_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip';
106 
107  # Do remaining chunk
108  $end += $batchSize - 1;
109  $blockStart = $start;
110  $blockEnd = $start + $batchSize - 1;
111 
112  $dbw = $this->getDB( DB_PRIMARY );
113  // Go through each page and save the output
114  while ( $blockEnd <= $end ) {
115  // Get the pages
116  $res = $dbr->select( 'page',
117  [ 'page_namespace', 'page_title', 'page_id' ],
118  $where + [ "page_id BETWEEN " . (int)$blockStart . " AND " . (int)$blockEnd ],
119  __METHOD__,
120  [ 'ORDER BY' => 'page_id ASC', 'USE INDEX' => 'PRIMARY' ]
121  );
122 
123  $this->beginTransaction( $dbw, __METHOD__ ); // for any changes
124  foreach ( $res as $row ) {
125  $rebuilt = false;
126 
127  $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
128  if ( $title === null ) {
129  $this->output( "Page {$row->page_id} has bad title\n" );
130  continue; // broken title?
131  }
132 
133  $context = new RequestContext();
134  $context->setTitle( $title );
135  $article = Article::newFromTitle( $title, $context );
136  $context->setWikiPage( $article->getPage() );
137 
138  // Some extensions like FlaggedRevs while error out if this is unset
139  RequestContext::getMain()->setTitle( $title );
140 
141  // If the article is cacheable, then load it
142  if ( $article->isFileCacheable( HTMLFileCache::MODE_REBUILD ) ) {
143  $viewCache = new HTMLFileCache( $title, 'view' );
144  $historyCache = new HTMLFileCache( $title, 'history' );
145  if ( $viewCache->isCacheGood() && $historyCache->isCacheGood() ) {
146  if ( $overwrite ) {
147  $rebuilt = true;
148  } else {
149  $this->output( "Page '$title' (id {$row->page_id}) already cached\n" );
150  continue; // done already!
151  }
152  }
153 
154  AtEase::suppressWarnings(); // header notices
155 
156  // 1. Cache ?action=view
157  // Be sure to reset the mocked request time (T24852)
158  $_SERVER['REQUEST_TIME_FLOAT'] = microtime( true );
159  ob_start();
160  $article->view();
161  $context->getOutput()->output();
162  $context->getOutput()->clearHTML();
163  $viewHtml = ob_get_clean();
164  $viewCache->saveToFileCache( $viewHtml );
165 
166  // 2. Cache ?action=history
167  // Be sure to reset the mocked request time (T24852)
168  $_SERVER['REQUEST_TIME_FLOAT'] = microtime( true );
169  ob_start();
170  Action::factory( 'history', $article, $context )->show();
171  $context->getOutput()->output();
172  $context->getOutput()->clearHTML();
173  $historyHtml = ob_get_clean();
174  $historyCache->saveToFileCache( $historyHtml );
175 
176  AtEase::restoreWarnings();
177 
178  if ( $rebuilt ) {
179  $this->output( "Re-cached page '$title' (id {$row->page_id})..." );
180  } else {
181  $this->output( "Cached page '$title' (id {$row->page_id})..." );
182  }
183  $this->output( "[view: " . strlen( $viewHtml ) . " bytes; " .
184  "history: " . strlen( $historyHtml ) . " bytes]\n" );
185  } else {
186  $this->output( "Page '$title' (id {$row->page_id}) not cacheable\n" );
187  }
188  }
189  $this->commitTransaction( $dbw, __METHOD__ ); // commit any changes
190 
191  $blockStart += $batchSize;
192  $blockEnd += $batchSize;
193  }
194  $this->output( "Done!\n" );
195  }
196 }
197 
198 $maintClass = RebuildFileCache::class;
199 require_once RUN_MAINTENANCE_IF_MAIN;
static factory(string $action, Article $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:86
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:168
Page view caching in the file system.
static deinit()
Disable the debugger.
Definition: MWDebug.php:117
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:66
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
beginTransaction(IDatabase $dbw, $fname)
Begin a transaction on a DB.
commitTransaction(IDatabase $dbw, $fname)
Commit the transaction on a DB handle and wait for replica DBs to catch up.
output( $out, $channel=null)
Throw some output to the user.
hasOption( $name)
Checks to see if a particular option was set.
getBatchSize()
Returns batch size.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
setBatchSize( $s=0)
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Utility for loading settings files.
Maintenance script that builds the file cache.
execute()
Do the actual work.
finalSetup(SettingsBuilder $settingsBuilder=null)
Handle some last-minute setup here.
__construct()
Default constructor.
Group all the pieces relevant to the context of a request into one instance.
static getMain()
Get the RequestContext object associated with the main request.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:664
const DB_REPLICA
Definition: defines.php:26
const DB_PRIMARY
Definition: defines.php:28