Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
58.82% |
130 / 221 |
|
11.11% |
1 / 9 |
CRAP | |
0.00% |
0 / 1 |
| RepoHooks | |
58.82% |
130 / 221 |
|
11.11% |
1 / 9 |
257.58 | |
0.00% |
0 / 1 |
| registerExtension | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
| onMediaWikiServices | |
77.78% |
14 / 18 |
|
0.00% |
0 / 1 |
6.40 | |||
| onLoadExtensionSchemaUpdates | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
72 | |||
| getDataPath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| createInitialContent | |
77.50% |
31 / 40 |
|
0.00% |
0 / 1 |
7.56 | |||
| insertContentObject | |
82.14% |
46 / 56 |
|
0.00% |
0 / 1 |
16.28 | |||
| initializeZObjectJoinTable | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| updateSecondaryTables | |
72.09% |
31 / 43 |
|
0.00% |
0 / 1 |
12.17 | |||
| ensureZObjectStoreIsPresent | |
11.11% |
2 / 18 |
|
0.00% |
0 / 1 |
9.32 | |||
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * WikiLambda extension 'repo-mode' hooks |
| 5 | * |
| 6 | * @file |
| 7 | * @ingroup Extensions |
| 8 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
| 9 | * @license MIT |
| 10 | */ |
| 11 | |
| 12 | namespace MediaWiki\Extension\WikiLambda\HookHandler; |
| 13 | |
| 14 | use MediaWiki\CommentStore\CommentStoreComment; |
| 15 | use MediaWiki\Config\ConfigException; |
| 16 | use MediaWiki\Deferred\DeferredUpdates; |
| 17 | use MediaWiki\Extension\WikiLambda\AbstractContent\AbstractWikiContentHandler; |
| 18 | use MediaWiki\Extension\WikiLambda\Registry\ZLangRegistry; |
| 19 | use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry; |
| 20 | use MediaWiki\Extension\WikiLambda\WikiLambdaServices; |
| 21 | use MediaWiki\Extension\WikiLambda\ZErrorException; |
| 22 | use MediaWiki\Extension\WikiLambda\ZObjectContentHandler; |
| 23 | use MediaWiki\Extension\WikiLambda\ZObjectSecondaryDataUpdate; |
| 24 | use MediaWiki\Installer\DatabaseUpdater; |
| 25 | use MediaWiki\MediaWikiServices; |
| 26 | use MediaWiki\RecentChanges\RecentChange; |
| 27 | use MediaWiki\Revision\SlotRecord; |
| 28 | use MediaWiki\Title\Title; |
| 29 | use MediaWiki\User\User; |
| 30 | use Wikimedia\Services\NoSuchServiceException; |
| 31 | |
| 32 | class RepoHooks implements |
| 33 | \MediaWiki\Installer\Hook\LoadExtensionSchemaUpdatesHook, |
| 34 | \MediaWiki\Hook\MediaWikiServicesHook |
| 35 | { |
| 36 | |
| 37 | public static function registerExtension() { |
| 38 | // We define the content models regardless of if 'repo mode' or 'abstract mode' are enabled |
| 39 | require_once __DIR__ . '/../defines.php'; |
| 40 | |
| 41 | // Can't use MediaWikiServices or config objects yet, so use globals |
| 42 | global $wgWikiLambdaEnableRepoMode; |
| 43 | |
| 44 | if ( !$wgWikiLambdaEnableRepoMode ) { |
| 45 | // Nothing for us to do. |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | // Register the namespace as using content our content model |
| 50 | global $wgNamespaceContentModels; |
| 51 | $wgNamespaceContentModels[ NS_MAIN ] = CONTENT_MODEL_ZOBJECT; |
| 52 | |
| 53 | // Ensure that the namespace is protected with the right permissions |
| 54 | global $wgNamespaceProtection; |
| 55 | $wgNamespaceProtection[ NS_MAIN ] = [ 'wikilambda-edit', 'wikilambda-create' ]; |
| 56 | |
| 57 | // (T267232) Prevent ZObject pages from being transcluded; sadly this isn't available as |
| 58 | // an extension.json attribute as of yet. |
| 59 | global $wgNonincludableNamespaces; |
| 60 | $wgNonincludableNamespaces[] = NS_MAIN; |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * We do this here because registerExtension() is called too early to use i18n messages. |
| 65 | * |
| 66 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/MediaWikiServices |
| 67 | * |
| 68 | * @param MediaWikiServices $services MediaWikiServices instance |
| 69 | */ |
| 70 | public function onMediaWikiServices( $services ) { |
| 71 | $config = $services->getMainConfig(); |
| 72 | if ( !$config->get( 'WikiLambdaEnableAbstractMode' ) ) { |
| 73 | // Nothing for us to do. |
| 74 | return; |
| 75 | } |
| 76 | |
| 77 | $contentHandler = $services->getContentHandlerFactory(); |
| 78 | if ( !$contentHandler->isDefinedModel( CONTENT_MODEL_ABSTRACT ) ) { |
| 79 | if ( method_exists( $contentHandler, 'defineContentHandler' ) ) { |
| 80 | // @phan-suppress-next-line PhanUndeclaredMethod this apparently phan doesn't take the hint above |
| 81 | $contentHandler->defineContentHandler( CONTENT_MODEL_ABSTRACT, AbstractWikiContentHandler::class ); |
| 82 | } else { |
| 83 | throw new ConfigException( 'Abstract content model is not registered and we cannot inject it.' ); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | $abstractNamespaceConfig = $config->get( 'WikiLambdaAbstractNamespaces' ); |
| 88 | |
| 89 | // We support multiple abstract namespaces, so loop through; initial deployment will only be for one. |
| 90 | foreach ( $abstractNamespaceConfig as $namespaceID => $nsdata ) { |
| 91 | |
| 92 | // We only need to do the below steps if we're in dev-mode, and not occupying the main namespace |
| 93 | // (T420617) HACK: Ignore all NSes lower than 10, not just NS_MAIN, whilst we fix prod config. |
| 94 | if ( $namespaceID > 10 ) { |
| 95 | $namespaceEnglishName = $nsdata[0]; |
| 96 | |
| 97 | // Register the namespace at all (plus its talk) |
| 98 | global $wgExtraNamespaces; |
| 99 | $wgExtraNamespaces[ $namespaceID ] = $namespaceEnglishName; |
| 100 | $wgExtraNamespaces[ $namespaceID + 1 ] = $namespaceEnglishName . '_talk'; |
| 101 | |
| 102 | // Register the namespace as including content |
| 103 | global $wgContentNamespaces; |
| 104 | $wgContentNamespaces[] = $namespaceID; |
| 105 | } |
| 106 | |
| 107 | // Register the namespace as using content our content model |
| 108 | global $wgNamespaceContentModels; |
| 109 | $wgNamespaceContentModels[ $namespaceID ] = CONTENT_MODEL_ABSTRACT; |
| 110 | |
| 111 | // Ensure that the namespace is protected with the right permissions |
| 112 | global $wgNamespaceProtection; |
| 113 | $wgNamespaceProtection[ $namespaceID ] = [ 'wikilambda-abstract-edit', 'wikilambda-abstract-create' ]; |
| 114 | |
| 115 | // Set that our namespace cannot be transcluded |
| 116 | global $wgNonincludableNamespaces; |
| 117 | $wgNonincludableNamespaces[] = $namespaceID; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/LoadExtensionSchemaUpdates |
| 123 | * |
| 124 | * @param DatabaseUpdater $updater DatabaseUpdater subclass |
| 125 | */ |
| 126 | public function onLoadExtensionSchemaUpdates( $updater ) { |
| 127 | $db = $updater->getDB(); |
| 128 | $type = $db->getType(); |
| 129 | $dir = __DIR__ . '/../../sql'; |
| 130 | |
| 131 | if ( !in_array( $type, [ 'mysql', 'sqlite', 'postgres' ] ) ) { |
| 132 | wfWarn( "Database type '$type' is not supported by the WikiLambda extension." ); |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | $config = MediaWikiServices::getInstance()->getMainConfig(); |
| 137 | |
| 138 | // Insert the tables for client-mode, if needed (most production wikis, and development machines) |
| 139 | if ( $config->has( 'WikiLambdaEnableClientMode' ) && $config->get( 'WikiLambdaEnableClientMode' ) ) { |
| 140 | // Note that we're calling ::has() first, as we'll likely be in pre-extension registry mode |
| 141 | $clientTables = [ 'usage' ]; |
| 142 | |
| 143 | foreach ( $clientTables as $table ) { |
| 144 | $updater->addExtensionTable( 'wikifunctionsclient_' . $table, "$dir/$type/table-$table.sql" ); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // Insert the tables for repo-mode, if needed (Wikifunctions.org and development machines only) |
| 149 | if ( $config->has( 'WikiLambdaEnableRepoMode' ) && $config->get( 'WikiLambdaEnableRepoMode' ) ) { |
| 150 | $repoTables = [ |
| 151 | 'zobject_labels', |
| 152 | 'zobject_label_conflicts', |
| 153 | 'zobject_function_join', |
| 154 | 'ztester_results', |
| 155 | 'zlanguages', |
| 156 | 'zobject_join' |
| 157 | ]; |
| 158 | |
| 159 | foreach ( $repoTables as $table ) { |
| 160 | $updater->addExtensionTable( 'wikilambda_' . $table, "$dir/$type/table-$table.sql" ); |
| 161 | } |
| 162 | |
| 163 | // Database updates: |
| 164 | // (T285368) Add primary label field to labels table |
| 165 | $updater->addExtensionField( |
| 166 | 'wikilambda_zobject_labels', |
| 167 | 'wlzl_label_primary', |
| 168 | "$dir/$type/patch-add-primary-label-field.sql" |
| 169 | ); |
| 170 | |
| 171 | // (T262089) Add return type field to labels table |
| 172 | $updater->addExtensionField( |
| 173 | 'wikilambda_zobject_labels', |
| 174 | 'wlzl_return_type', |
| 175 | "$dir/$type/patch-add-return-type-field.sql" |
| 176 | ); |
| 177 | |
| 178 | $updater->addExtensionUpdate( [ [ self::class, 'createInitialContent' ] ] ); |
| 179 | $updater->addExtensionUpdate( [ [ self::class, 'initializeZObjectJoinTable' ] ] ); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Return path of data definition JSON files. |
| 185 | * |
| 186 | * @return string |
| 187 | */ |
| 188 | protected static function getDataPath() { |
| 189 | return dirname( __DIR__ ) . '/../function-schemata/data/definitions/'; |
| 190 | } |
| 191 | |
| 192 | /** |
| 193 | * Installer/Updater callback to create the initial "system" ZObjects on any installation. This |
| 194 | * is a callback so that it runs after the tables have been created/updated. |
| 195 | * |
| 196 | * @param DatabaseUpdater $updater |
| 197 | * @param bool $overwrite If true, overwrites the content, else skips if present |
| 198 | */ |
| 199 | public static function createInitialContent( DatabaseUpdater $updater, $overwrite = false ) { |
| 200 | // Ensure that the extension is set up (namespace is defined) even when running in update.php outside of MW. |
| 201 | self::registerExtension(); |
| 202 | |
| 203 | $config = MediaWikiServices::getInstance()->getMainConfig(); |
| 204 | if ( !$config->get( 'WikiLambdaEnableRepoMode' ) ) { |
| 205 | // Nothing for us to do. |
| 206 | return; |
| 207 | } |
| 208 | |
| 209 | $contentHandler = MediaWikiServices::getInstance()->getContentHandlerFactory(); |
| 210 | if ( !$contentHandler->isDefinedModel( CONTENT_MODEL_ZOBJECT ) ) { |
| 211 | if ( method_exists( $contentHandler, 'defineContentHandler' ) ) { |
| 212 | // @phan-suppress-next-line PhanUndeclaredMethod this apparently phan doesn't take the hint above |
| 213 | $contentHandler->defineContentHandler( CONTENT_MODEL_ZOBJECT, ZObjectContentHandler::class ); |
| 214 | } else { |
| 215 | throw new ConfigException( 'WikiLambda content model is not registered and we cannot inject it.' ); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | // Note: Hard-coding the English version for messages as this can run without a Context and so no language set. |
| 220 | $creatingUserName = wfMessage( 'wikilambda-systemuser' )->inLanguage( 'en' )->text(); |
| 221 | $creatingUser = User::newSystemUser( $creatingUserName, [ 'steal' => true ] ); |
| 222 | // We use wikilambda-bootstrapupdatingeditsummary in maintenance scripts when updating. |
| 223 | $creatingComment = wfMessage( 'wikilambda-bootstrapcreationeditsummary' )->inLanguage( 'en' )->text(); |
| 224 | |
| 225 | if ( !$creatingUser ) { |
| 226 | // Something went wrong, give up. |
| 227 | return; |
| 228 | } |
| 229 | |
| 230 | $initialDataToLoadPath = static::getDataPath(); |
| 231 | |
| 232 | $dependenciesFile = file_get_contents( $initialDataToLoadPath . 'dependencies.json' ); |
| 233 | if ( $dependenciesFile === false ) { |
| 234 | throw new ConfigException( |
| 235 | 'Could not load dependencies file from function-schemata sub-repository of the WikiLambda extension.' |
| 236 | . ' Have you initiated & fetched it? Try `git submodule update --init --recursive`.' |
| 237 | ); |
| 238 | } |
| 239 | $dependencies = json_decode( $dependenciesFile, true ); |
| 240 | |
| 241 | $initialDataToLoadListing = array_filter( |
| 242 | scandir( $initialDataToLoadPath ), |
| 243 | static function ( $key ) { |
| 244 | return (bool)preg_match( '/^Z\d+\.json$/', $key ); |
| 245 | } |
| 246 | ); |
| 247 | |
| 248 | // Naturally sort, so Z2/Persistent Object gets created before others |
| 249 | natsort( $initialDataToLoadListing ); |
| 250 | |
| 251 | $inserted = []; |
| 252 | foreach ( $initialDataToLoadListing as $filename ) { |
| 253 | static::insertContentObject( |
| 254 | $updater, |
| 255 | $filename, |
| 256 | $dependencies, |
| 257 | $creatingUser, |
| 258 | $creatingComment, |
| 259 | $overwrite, |
| 260 | $inserted |
| 261 | ); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * Inserts into the database the ZObject found in a given filename of the data directory. First checks |
| 267 | * whether the ZObject has any dependencies, according to the dependencies.json manifest file, and if so, |
| 268 | * inserts all the dependencies before trying the current ZObject. |
| 269 | * |
| 270 | * Runs in a static context and so can't be part of the normal code in ZObjectStore. |
| 271 | * |
| 272 | * @param DatabaseUpdater $updater |
| 273 | * @param string $filename |
| 274 | * @param array $dependencies |
| 275 | * @param User $user |
| 276 | * @param string $comment |
| 277 | * @param bool $overwrite |
| 278 | * @param string[] &$inserted |
| 279 | * @param string[] $track |
| 280 | * @return bool Has successfully inserted the content object |
| 281 | */ |
| 282 | protected static function insertContentObject( |
| 283 | $updater, $filename, $dependencies, $user, $comment, $overwrite = false, &$inserted = [], $track = [] |
| 284 | ) { |
| 285 | $initialDataToLoadPath = static::getDataPath(); |
| 286 | $updateRowName = "create WikiLambda initial content - $filename"; |
| 287 | |
| 288 | $langReg = ZLangRegistry::singleton(); |
| 289 | $typeReg = ZTypeRegistry::singleton(); |
| 290 | |
| 291 | // Check dependencies |
| 292 | $zid = substr( $filename, 0, -5 ); |
| 293 | |
| 294 | if ( array_key_exists( $zid, $dependencies ) ) { |
| 295 | $deps = $dependencies[ $zid ]; |
| 296 | foreach ( $deps as $dep ) { |
| 297 | if ( |
| 298 | // Avoid circular dependencies |
| 299 | !in_array( $dep, $track ) |
| 300 | && !$langReg->isZidCached( $dep ) |
| 301 | && !$typeReg->isZidCached( $dep ) |
| 302 | ) { |
| 303 | // Call recursively till all dependencies have been added |
| 304 | $success = self::insertContentObject( |
| 305 | $updater, |
| 306 | "$dep.json", |
| 307 | $dependencies, |
| 308 | $user, |
| 309 | $comment, |
| 310 | $overwrite, |
| 311 | $inserted, |
| 312 | array_merge( $track, (array)$dep ) |
| 313 | ); |
| 314 | // If any of the dependencies fail, we desist on the current insertion |
| 315 | if ( !$success ) { |
| 316 | return false; |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | $zid = substr( $filename, 0, -5 ); |
| 323 | $title = Title::newFromText( $zid, NS_MAIN ); |
| 324 | $services = MediaWikiServices::getInstance(); |
| 325 | $page = $services->getWikiPageFactory()->newFromTitle( $title ); |
| 326 | |
| 327 | // If we don't want to overwrite the ZObjects, and if Zid has already been inserted, |
| 328 | // just purge the page to update secondary data and return true |
| 329 | if ( |
| 330 | ( $overwrite && in_array( $zid, $inserted ) ) || |
| 331 | ( !$overwrite && $updater->updateRowExists( $updateRowName ) ) |
| 332 | ) { |
| 333 | $page->doPurge(); |
| 334 | return true; |
| 335 | } |
| 336 | |
| 337 | $data = file_get_contents( $initialDataToLoadPath . $filename ); |
| 338 | if ( !$data ) { |
| 339 | // something went wrong, give up. |
| 340 | $updater->output( "\t❌ Unable to load file contents for {$title->getPrefixedText()}.\n" ); |
| 341 | return false; |
| 342 | } |
| 343 | |
| 344 | try { |
| 345 | $content = ZObjectContentHandler::makeContent( $data, $title ); |
| 346 | } catch ( ZErrorException ) { |
| 347 | $updater->output( "\t❌ Unable to make a ZObject for {$title->getPrefixedText()}.\n" ); |
| 348 | return false; |
| 349 | } |
| 350 | |
| 351 | static::ensureZObjectStoreIsPresent( $services ); |
| 352 | |
| 353 | $pageUpdater = $page->newPageUpdater( $user ); |
| 354 | $pageUpdater->setContent( SlotRecord::MAIN, $content ); |
| 355 | $pageUpdater->setRcPatrolStatus( RecentChange::PRC_PATROLLED ); |
| 356 | |
| 357 | $pageUpdater->saveRevision( |
| 358 | CommentStoreComment::newUnsavedComment( $comment ?? '' ), |
| 359 | EDIT_AUTOSUMMARY | EDIT_NEW |
| 360 | ); |
| 361 | |
| 362 | if ( $pageUpdater->wasSuccessful() ) { |
| 363 | array_push( $inserted, $zid ); |
| 364 | $updater->insertUpdateRow( $updateRowName ); |
| 365 | if ( !defined( 'MW_PHPUNIT_TEST' ) ) { |
| 366 | // Don't log this during unit testing, quibble thinks it means we're broken. |
| 367 | $updater->output( "\tSuccessfully created {$title->getPrefixedText()}.\n" ); |
| 368 | } |
| 369 | } else { |
| 370 | $firstError = $pageUpdater->getStatus()->getErrors()[0]; |
| 371 | $error = wfMessage( $firstError[ 'message' ], $firstError[ 'params' ] )->text(); |
| 372 | $updater->output( "\t❌ Unable to make a page for {$title->getPrefixedText()}: $error\n" ); |
| 373 | } |
| 374 | |
| 375 | return $pageUpdater->wasSuccessful(); |
| 376 | } |
| 377 | |
| 378 | /** |
| 379 | * Installer/Updater callback to ensure that wikilambda_zobject_join has been populated for all |
| 380 | * existing functions (Z8s). This is a callback so that it runs after the tables have been |
| 381 | * created/updated. This function can be removed when we are confident that all WikiLambda |
| 382 | * installations have a fully populated wikilambda_zobject_join table. |
| 383 | * |
| 384 | * @param DatabaseUpdater $updater |
| 385 | */ |
| 386 | public static function initializeZObjectJoinTable( DatabaseUpdater $updater ) { |
| 387 | $updateKey = 'Initialized wikilambda_zobject_join for Z8s'; |
| 388 | |
| 389 | if ( !$updater->updateRowExists( $updateKey ) ) { |
| 390 | static::updateSecondaryTables( $updater, 'Z8' ); |
| 391 | $updater->insertUpdateRow( $updateKey ); |
| 392 | } else { |
| 393 | $updater->output( "...wikilambda_zobject_join table already initialized\n" ); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | /** |
| 398 | * Ensures that secondary DB tables have been populated for ZObjects of the given zType. For |
| 399 | * each such ZObject, a new instance of ZObjectSecondaryDataUpdate is created and added to |
| 400 | * DeferredUpdates. |
| 401 | * |
| 402 | * N.B. This function assumes that wikilambda_zobject_labels is fully populated; it calls |
| 403 | * fetchZidsOfType to get a list of ZObjects of the given zType. |
| 404 | * |
| 405 | * Note there is a WikiLambda maintenance script (updateSecondaryTables.php) that provides |
| 406 | * similar functionality (and with some code that duplicates what's here, which could not |
| 407 | * easily be avoided). |
| 408 | * |
| 409 | * @param DatabaseUpdater $updater |
| 410 | * @param string $zType The type of ZObject for which to do updates. |
| 411 | * @param bool $verbose If true, print the ZID of each ZObject for which updating is done |
| 412 | * (default = false) |
| 413 | * @param bool $dryRun If true, do nothing, just print the output statements |
| 414 | * (default = false) |
| 415 | */ |
| 416 | public static function updateSecondaryTables( |
| 417 | $updater, |
| 418 | $zType, |
| 419 | $verbose = false, |
| 420 | $dryRun = false |
| 421 | ) { |
| 422 | $services = MediaWikiServices::getInstance(); |
| 423 | |
| 424 | $config = $services->getMainConfig(); |
| 425 | if ( !$config->get( 'WikiLambdaEnableRepoMode' ) ) { |
| 426 | // Nothing for us to do. |
| 427 | return; |
| 428 | } |
| 429 | |
| 430 | static::ensureZObjectStoreIsPresent( $services ); |
| 431 | $zObjectStore = WikiLambdaServices::getZObjectStore(); |
| 432 | $zObjectCache = WikiLambdaServices::getMemcachedWrapper(); |
| 433 | $handler = new ZObjectContentHandler( CONTENT_MODEL_ZOBJECT, $config, $zObjectStore, $zObjectCache ); |
| 434 | |
| 435 | $targets = $zObjectStore->fetchZidsOfType( $zType ); |
| 436 | |
| 437 | if ( count( $targets ) === 0 ) { |
| 438 | if ( !defined( 'MW_PHPUNIT_TEST' ) ) { |
| 439 | // Don't output during unit testing; causes the test to be labeled as risky. |
| 440 | $updater->output( "No ZObjects of type " . $zType . " for which secondary tables need updating\n" ); |
| 441 | } |
| 442 | return; |
| 443 | } |
| 444 | |
| 445 | if ( !defined( 'MW_PHPUNIT_TEST' ) ) { |
| 446 | // Don't output during unit testing; causes the test to be labeled as risky. |
| 447 | if ( $dryRun ) { |
| 448 | $updater->output( "Would have updated" ); |
| 449 | } else { |
| 450 | $updater->output( "Updating" ); |
| 451 | } |
| 452 | $updater->output( " secondary tables for " . count( $targets ) . " ZObjects of type " . |
| 453 | $zType . "\n" ); |
| 454 | } |
| 455 | |
| 456 | $offset = 0; |
| 457 | $queryLimit = 10; |
| 458 | do { |
| 459 | $contents = $zObjectStore->fetchBatchZObjects( array_slice( $targets, $offset, |
| 460 | $queryLimit ) ); |
| 461 | $offset += $queryLimit; |
| 462 | |
| 463 | foreach ( $contents as $zid => $persistentObject ) { |
| 464 | if ( $verbose ) { |
| 465 | $updater->output( " $zid" ); |
| 466 | } |
| 467 | if ( $dryRun ) { |
| 468 | continue; |
| 469 | } |
| 470 | $title = Title::newFromText( $zid, NS_MAIN ); |
| 471 | $data = json_encode( $persistentObject->getSerialized() ); |
| 472 | $content = $handler::makeContent( $data, $title ); |
| 473 | $update = new ZObjectSecondaryDataUpdate( |
| 474 | $title, |
| 475 | $content, |
| 476 | $zObjectStore, |
| 477 | $zObjectCache, |
| 478 | // Not trying to update the orchestrator cache here, as we're in a maintenance script |
| 479 | null |
| 480 | ); |
| 481 | DeferredUpdates::addUpdate( $update ); |
| 482 | } |
| 483 | if ( $verbose ) { |
| 484 | $updater->output( "\n" ); |
| 485 | } |
| 486 | |
| 487 | } while ( count( $targets ) - $offset > 0 ); |
| 488 | } |
| 489 | |
| 490 | /** |
| 491 | * Checks for the existence of WikiLambdaZObjectStore in $services, and creates it |
| 492 | * if needed. |
| 493 | * |
| 494 | * @param MediaWikiServices $services |
| 495 | */ |
| 496 | private static function ensureZObjectStoreIsPresent( $services ) { |
| 497 | // If we're in the installer, it won't have registered our extension's services yet. |
| 498 | try { |
| 499 | $services->get( 'WikiLambdaZObjectStore' ); |
| 500 | } catch ( NoSuchServiceException ) { |
| 501 | $zObjectStore = WikiLambdaServices::buildZObjectStore( $services ); |
| 502 | $services->defineService( |
| 503 | 'WikiLambdaZObjectStore', |
| 504 | static function () use ( $zObjectStore ) { |
| 505 | return $zObjectStore; |
| 506 | } |
| 507 | ); |
| 508 | } |
| 509 | |
| 510 | try { |
| 511 | $services->get( 'WikiLambdaMemcachedWrapper' ); |
| 512 | } catch ( NoSuchServiceException ) { |
| 513 | $memcachedWrapper = WikiLambdaServices::buildMemcachedWrapper( $services ); |
| 514 | $services->defineService( |
| 515 | 'WikiLambdaMemcachedWrapper', |
| 516 | static function () use ( $memcachedWrapper ) { |
| 517 | return $memcachedWrapper; |
| 518 | } |
| 519 | ); |
| 520 | } |
| 521 | } |
| 522 | } |