populateContentModel.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. /**
  3. * Populate the page_content_model and {rev,ar}_content_{model,format} fields.
  4. *
  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.
  9. *
  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.
  14. *
  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
  19. *
  20. * @file
  21. * @ingroup Maintenance
  22. */
  23. require_once __DIR__ . '/Maintenance.php';
  24. /**
  25. * Usage:
  26. * populateContentModel.php --ns=1 --table=page
  27. */
  28. class PopulateContentModel extends Maintenance {
  29. public function __construct() {
  30. parent::__construct();
  31. $this->addDescription( 'Populate the various content_* fields' );
  32. $this->addOption( 'ns', 'Namespace to run in, or "all" for all namespaces', true, true );
  33. $this->addOption( 'table', 'Table to run in', true, true );
  34. $this->setBatchSize( 100 );
  35. }
  36. public function execute() {
  37. $dbw = $this->getDB( DB_MASTER );
  38. $ns = $this->getOption( 'ns' );
  39. if ( !ctype_digit( $ns ) && $ns !== 'all' ) {
  40. $this->error( 'Invalid namespace', 1 );
  41. }
  42. $ns = $ns === 'all' ? 'all' : (int)$ns;
  43. $table = $this->getOption( 'table' );
  44. switch ( $table ) {
  45. case 'revision':
  46. case 'archive':
  47. $this->populateRevisionOrArchive( $dbw, $table, $ns );
  48. break;
  49. case 'page':
  50. $this->populatePage( $dbw, $ns );
  51. break;
  52. default:
  53. $this->error( "Invalid table name: $table", 1 );
  54. }
  55. }
  56. private function updatePageRows( Database $dbw, $pageIds, $model ) {
  57. $count = count( $pageIds );
  58. $this->output( "Setting $count rows to $model..." );
  59. $dbw->update(
  60. 'page',
  61. [ 'page_content_model' => $model ],
  62. [ 'page_id' => $pageIds ],
  63. __METHOD__
  64. );
  65. wfWaitForSlaves();
  66. $this->output( "done.\n" );
  67. }
  68. protected function populatePage( Database $dbw, $ns ) {
  69. $toSave = [];
  70. $lastId = 0;
  71. $nsCondition = $ns === 'all' ? [] : [ 'page_namespace' => $ns ];
  72. do {
  73. $rows = $dbw->select(
  74. 'page',
  75. [ 'page_namespace', 'page_title', 'page_id' ],
  76. [
  77. 'page_content_model' => null,
  78. 'page_id > ' . $dbw->addQuotes( $lastId ),
  79. ] + $nsCondition,
  80. __METHOD__,
  81. [ 'LIMIT' => $this->mBatchSize, 'ORDER BY' => 'page_id ASC' ]
  82. );
  83. $this->output( "Fetched {$rows->numRows()} rows.\n" );
  84. foreach ( $rows as $row ) {
  85. $title = Title::newFromRow( $row );
  86. $model = ContentHandler::getDefaultModelFor( $title );
  87. $toSave[$model][] = $row->page_id;
  88. if ( count( $toSave[$model] ) >= $this->mBatchSize ) {
  89. $this->updatePageRows( $dbw, $toSave[$model], $model );
  90. unset( $toSave[$model] );
  91. }
  92. $lastId = $row->page_id;
  93. }
  94. } while ( $rows->numRows() >= $this->mBatchSize );
  95. foreach ( $toSave as $model => $pages ) {
  96. $this->updatePageRows( $dbw, $pages, $model );
  97. }
  98. }
  99. private function updateRevisionOrArchiveRows( Database $dbw, $ids, $model, $table ) {
  100. $prefix = $table === 'archive' ? 'ar' : 'rev';
  101. $model_column = "{$prefix}_content_model";
  102. $format_column = "{$prefix}_content_format";
  103. $key = "{$prefix}_id";
  104. $count = count( $ids );
  105. $format = ContentHandler::getForModelID( $model )->getDefaultFormat();
  106. $this->output( "Setting $count rows to $model / $format..." );
  107. $dbw->update(
  108. $table,
  109. [ $model_column => $model, $format_column => $format ],
  110. [ $key => $ids ],
  111. __METHOD__
  112. );
  113. $this->output( "done.\n" );
  114. }
  115. protected function populateRevisionOrArchive( Database $dbw, $table, $ns ) {
  116. $prefix = $table === 'archive' ? 'ar' : 'rev';
  117. $model_column = "{$prefix}_content_model";
  118. $format_column = "{$prefix}_content_format";
  119. $key = "{$prefix}_id";
  120. if ( $table === 'archive' ) {
  121. $selectTables = 'archive';
  122. $fields = [ 'ar_namespace', 'ar_title' ];
  123. $join_conds = [];
  124. $where = $ns === 'all' ? [] : [ 'ar_namespace' => $ns ];
  125. } else { // revision
  126. $selectTables = [ 'revision', 'page' ];
  127. $fields = [ 'page_title', 'page_namespace' ];
  128. $join_conds = [ 'page' => [ 'INNER JOIN', 'rev_page=page_id' ] ];
  129. $where = $ns === 'all' ? [] : [ 'page_namespace' => $ns ];
  130. }
  131. $toSave = [];
  132. $lastId = 0;
  133. do {
  134. $rows = $dbw->select(
  135. $selectTables,
  136. array_merge( $fields, [ $model_column, $format_column, $key ] ),
  137. // @todo support populating format if model is already set
  138. [
  139. $model_column => null,
  140. "$key > " . $dbw->addQuotes( $lastId ),
  141. ] + $where,
  142. __METHOD__,
  143. [ 'LIMIT' => $this->mBatchSize, 'ORDER BY' => "$key ASC" ],
  144. $join_conds
  145. );
  146. $this->output( "Fetched {$rows->numRows()} rows.\n" );
  147. foreach ( $rows as $row ) {
  148. if ( $table === 'archive' ) {
  149. $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
  150. } else {
  151. $title = Title::newFromRow( $row );
  152. }
  153. $lastId = $row->{$key};
  154. try {
  155. $handler = ContentHandler::getForTitle( $title );
  156. } catch ( MWException $e ) {
  157. $this->error( "Invalid content model for $title" );
  158. continue;
  159. }
  160. $defaultModel = $handler->getModelID();
  161. $defaultFormat = $handler->getDefaultFormat();
  162. $dbModel = $row->{$model_column};
  163. $dbFormat = $row->{$format_column};
  164. $id = $row->{$key};
  165. if ( $dbModel === null && $dbFormat === null ) {
  166. // Set the defaults
  167. $toSave[$defaultModel][] = $row->{$key};
  168. } else { // $dbModel === null, $dbFormat set.
  169. if ( $dbFormat === $defaultFormat ) {
  170. $toSave[$defaultModel][] = $row->{$key};
  171. } else { // non-default format, just update now
  172. $this->output( "Updating model to match format for $table $id of $title... " );
  173. $dbw->update(
  174. $table,
  175. [ $model_column => $defaultModel ],
  176. [ $key => $id ],
  177. __METHOD__
  178. );
  179. wfWaitForSlaves();
  180. $this->output( "done.\n" );
  181. continue;
  182. }
  183. }
  184. if ( count( $toSave[$defaultModel] ) >= $this->mBatchSize ) {
  185. $this->updateRevisionOrArchiveRows( $dbw, $toSave[$defaultModel], $defaultModel, $table );
  186. unset( $toSave[$defaultModel] );
  187. }
  188. }
  189. } while ( $rows->numRows() >= $this->mBatchSize );
  190. foreach ( $toSave as $model => $ids ) {
  191. $this->updateRevisionOrArchiveRows( $dbw, $ids, $model, $table );
  192. }
  193. }
  194. }
  195. $maintClass = 'PopulateContentModel';
  196. require_once RUN_MAINTENANCE_IF_MAIN;