Merge "Added release notes for 'ContentHandler::runLegacyHooks' removal"
[mediawiki.git] / maintenance / populateContentModel.php
blobb41e0e0d6e74551319085e9e02afbb06ed1e995f
1 <?php
2 /**
3 * Populate the page_content_model and {rev,ar}_content_{model,format} fields.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
21 * @ingroup Maintenance
24 require_once __DIR__ . '/Maintenance.php';
26 /**
27 * Usage:
28 * populateContentModel.php --ns=1 --table=page
30 class PopulateContentModel extends Maintenance {
31 protected $wikiId;
33 protected $wanCache;
35 public function __construct() {
36 parent::__construct();
37 $this->addDescription( 'Populate the various content_* fields' );
38 $this->addOption( 'ns', 'Namespace to run in, or "all" for all namespaces', true, true );
39 $this->addOption( 'table', 'Table to run in', true, true );
40 $this->setBatchSize( 100 );
43 public function execute() {
44 $dbw = $this->getDB( DB_MASTER );
46 $this->wikiId = $dbw->getWikiID();
48 $this->wanCache = ObjectCache::getMainWANInstance();
50 $ns = $this->getOption( 'ns' );
51 if ( !ctype_digit( $ns ) && $ns !== 'all' ) {
52 $this->error( 'Invalid namespace', 1 );
54 $ns = $ns === 'all' ? 'all' : (int)$ns;
55 $table = $this->getOption( 'table' );
56 switch ( $table ) {
57 case 'revision':
58 case 'archive':
59 $this->populateRevisionOrArchive( $dbw, $table, $ns );
60 break;
61 case 'page':
62 $this->populatePage( $dbw, $ns );
63 break;
64 default:
65 $this->error( "Invalid table name: $table", 1 );
69 protected function clearCache( $page_id, $rev_id ) {
70 $contentModelKey = $this->wanCache->makeKey( 'page', 'content-model', $rev_id );
71 $revisionKey =
72 $this->wanCache->makeGlobalKey( 'revision', $this->wikiId, $page_id, $rev_id );
74 // WikiPage content model cache
75 $this->wanCache->delete( $contentModelKey );
77 // Revision object cache, which contains a content model
78 $this->wanCache->delete( $revisionKey );
81 private function updatePageRows( Database $dbw, $pageIds, $model ) {
82 $count = count( $pageIds );
83 $this->output( "Setting $count rows to $model..." );
84 $dbw->update(
85 'page',
86 [ 'page_content_model' => $model ],
87 [ 'page_id' => $pageIds ],
88 __METHOD__
90 wfWaitForSlaves();
91 $this->output( "done.\n" );
94 protected function populatePage( Database $dbw, $ns ) {
95 $toSave = [];
96 $lastId = 0;
97 $nsCondition = $ns === 'all' ? [] : [ 'page_namespace' => $ns ];
98 do {
99 $rows = $dbw->select(
100 'page',
101 [ 'page_namespace', 'page_title', 'page_id' ],
103 'page_content_model' => null,
104 'page_id > ' . $dbw->addQuotes( $lastId ),
105 ] + $nsCondition,
106 __METHOD__,
107 [ 'LIMIT' => $this->mBatchSize, 'ORDER BY' => 'page_id ASC' ]
109 $this->output( "Fetched {$rows->numRows()} rows.\n" );
110 foreach ( $rows as $row ) {
111 $title = Title::newFromRow( $row );
112 $model = ContentHandler::getDefaultModelFor( $title );
113 $toSave[$model][] = $row->page_id;
114 if ( count( $toSave[$model] ) >= $this->mBatchSize ) {
115 $this->updatePageRows( $dbw, $toSave[$model], $model );
116 unset( $toSave[$model] );
118 $lastId = $row->page_id;
120 } while ( $rows->numRows() >= $this->mBatchSize );
121 foreach ( $toSave as $model => $pages ) {
122 $this->updatePageRows( $dbw, $pages, $model );
126 private function updateRevisionOrArchiveRows( Database $dbw, $ids, $model, $table ) {
127 $prefix = $table === 'archive' ? 'ar' : 'rev';
128 $model_column = "{$prefix}_content_model";
129 $format_column = "{$prefix}_content_format";
130 $key = "{$prefix}_id";
132 $count = count( $ids );
133 $format = ContentHandler::getForModelID( $model )->getDefaultFormat();
134 $this->output( "Setting $count rows to $model / $format..." );
135 $dbw->update(
136 $table,
137 [ $model_column => $model, $format_column => $format ],
138 [ $key => $ids ],
139 __METHOD__
142 $this->output( "done.\n" );
145 protected function populateRevisionOrArchive( Database $dbw, $table, $ns ) {
146 $prefix = $table === 'archive' ? 'ar' : 'rev';
147 $model_column = "{$prefix}_content_model";
148 $format_column = "{$prefix}_content_format";
149 $key = "{$prefix}_id";
150 if ( $table === 'archive' ) {
151 $selectTables = 'archive';
152 $fields = [ 'ar_namespace', 'ar_title' ];
153 $join_conds = [];
154 $where = $ns === 'all' ? [] : [ 'ar_namespace' => $ns ];
155 $page_id_column = 'ar_page_id';
156 $rev_id_column = 'ar_rev_id';
157 } else { // revision
158 $selectTables = [ 'revision', 'page' ];
159 $fields = [ 'page_title', 'page_namespace' ];
160 $join_conds = [ 'page' => [ 'INNER JOIN', 'rev_page=page_id' ] ];
161 $where = $ns === 'all' ? [] : [ 'page_namespace' => $ns ];
162 $page_id_column = 'rev_page';
163 $rev_id_column = 'rev_id';
166 $toSave = [];
167 $idsToClear = [];
168 $lastId = 0;
169 do {
170 $rows = $dbw->select(
171 $selectTables,
172 array_merge(
173 $fields,
174 [ $model_column, $format_column, $key, $page_id_column, $rev_id_column ]
176 // @todo support populating format if model is already set
178 $model_column => null,
179 "$key > " . $dbw->addQuotes( $lastId ),
180 ] + $where,
181 __METHOD__,
182 [ 'LIMIT' => $this->mBatchSize, 'ORDER BY' => "$key ASC" ],
183 $join_conds
185 $this->output( "Fetched {$rows->numRows()} rows.\n" );
186 foreach ( $rows as $row ) {
187 if ( $table === 'archive' ) {
188 $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
189 } else {
190 $title = Title::newFromRow( $row );
192 $lastId = $row->{$key};
193 try {
194 $handler = ContentHandler::getForTitle( $title );
195 } catch ( MWException $e ) {
196 $this->error( "Invalid content model for $title" );
197 continue;
199 $defaultModel = $handler->getModelID();
200 $defaultFormat = $handler->getDefaultFormat();
201 $dbModel = $row->{$model_column};
202 $dbFormat = $row->{$format_column};
203 $id = $row->{$key};
204 if ( $dbModel === null && $dbFormat === null ) {
205 // Set the defaults
206 $toSave[$defaultModel][] = $row->{$key};
207 $idsToClear[] = [
208 'page_id' => $row->{$page_id_column},
209 'rev_id' => $row->{$rev_id_column},
211 } else { // $dbModel === null, $dbFormat set.
212 if ( $dbFormat === $defaultFormat ) {
213 $toSave[$defaultModel][] = $row->{$key};
214 $idsToClear[] = [
215 'page_id' => $row->{$page_id_column},
216 'rev_id' => $row->{$rev_id_column},
218 } else { // non-default format, just update now
219 $this->output( "Updating model to match format for $table $id of $title... " );
220 $dbw->update(
221 $table,
222 [ $model_column => $defaultModel ],
223 [ $key => $id ],
224 __METHOD__
226 wfWaitForSlaves();
227 $this->clearCache( $row->{$page_id_column}, $row->{$rev_id_column} );
228 $this->output( "done.\n" );
229 continue;
233 if ( count( $toSave[$defaultModel] ) >= $this->mBatchSize ) {
234 $this->updateRevisionOrArchiveRows( $dbw, $toSave[$defaultModel], $defaultModel, $table );
235 unset( $toSave[$defaultModel] );
238 } while ( $rows->numRows() >= $this->mBatchSize );
239 foreach ( $toSave as $model => $ids ) {
240 $this->updateRevisionOrArchiveRows( $dbw, $ids, $model, $table );
243 foreach ( $idsToClear as $idPair ) {
244 $this->clearCache( $idPair['page_id'], $idPair['rev_id'] );
249 $maintClass = 'PopulateContentModel';
250 require_once RUN_MAINTENANCE_IF_MAIN;