Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 59 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
| ExtensionsProvider | |
0.00% |
0 / 59 |
|
0.00% |
0 / 8 |
342 | |
0.00% |
0 / 1 |
| getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getProvidedNames | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| execute | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
20 | |||
| getExtensionsDir | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getSkinsDir | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getAutoExtensionLegacySchemaHooks | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
| includeExtensionFiles | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| getAutoExtensionData | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace MediaWiki\Installer\Task; |
| 4 | |
| 5 | use AutoLoader; |
| 6 | use MediaWiki\HookContainer\HookContainer; |
| 7 | use MediaWiki\HookContainer\StaticHookRegistry; |
| 8 | use MediaWiki\MainConfigSchema; |
| 9 | use MediaWiki\MediaWikiServices; |
| 10 | use MediaWiki\Registration\ExtensionProcessor; |
| 11 | use MediaWiki\Status\Status; |
| 12 | |
| 13 | /** |
| 14 | * A scheduled provider which loads extensions |
| 15 | * |
| 16 | * @internal For use by the installer |
| 17 | */ |
| 18 | class ExtensionsProvider extends Task { |
| 19 | /** @inheritDoc */ |
| 20 | public function getName() { |
| 21 | return 'extensions'; |
| 22 | } |
| 23 | |
| 24 | /** @inheritDoc */ |
| 25 | public function getProvidedNames() { |
| 26 | return [ 'HookContainer', 'VirtualDomains', 'ExtensionTaskSpecs' ]; |
| 27 | } |
| 28 | |
| 29 | public function execute(): Status { |
| 30 | if ( !$this->getOption( 'Extensions' ) ) { |
| 31 | $this->getContext()->provide( 'VirtualDomains', [] ); |
| 32 | $this->getContext()->provide( 'ExtensionTaskSpecs', [] ); |
| 33 | return Status::newGood(); |
| 34 | } |
| 35 | |
| 36 | // Marker for DatabaseUpdater::loadExtensions so we don't |
| 37 | // double load extensions |
| 38 | define( 'MW_EXTENSIONS_LOADED', true ); |
| 39 | |
| 40 | $legacySchemaHooks = $this->getAutoExtensionLegacySchemaHooks(); |
| 41 | $data = $this->getAutoExtensionData(); |
| 42 | if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { |
| 43 | $legacySchemaHooks = array_merge( $legacySchemaHooks, |
| 44 | $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ); |
| 45 | } |
| 46 | $extDeprecatedHooks = $data['attributes']['DeprecatedHooks'] ?? []; |
| 47 | |
| 48 | $legacyHooks = $legacySchemaHooks ? [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ] : []; |
| 49 | $this->getContext()->provide( 'HookContainer', |
| 50 | new HookContainer( |
| 51 | new StaticHookRegistry( |
| 52 | $legacyHooks, |
| 53 | $data['attributes']['Hooks'] ?? [], |
| 54 | $extDeprecatedHooks |
| 55 | ), |
| 56 | MediaWikiServices::getInstance()->getObjectFactory() |
| 57 | ) |
| 58 | ); |
| 59 | $this->getContext()->provide( 'VirtualDomains', |
| 60 | $data['attributes']['DatabaseVirtualDomains'] ?? [] ); |
| 61 | $this->getContext()->provide( 'ExtensionTaskSpecs', |
| 62 | $data['attributes']['InstallerTasks'] ?? [] ); |
| 63 | |
| 64 | return Status::newGood(); |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * @return string |
| 69 | */ |
| 70 | protected function getExtensionsDir() { |
| 71 | return MW_INSTALL_PATH . '/extensions'; |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * @return string |
| 76 | */ |
| 77 | protected function getSkinsDir() { |
| 78 | return MW_INSTALL_PATH . '/skins'; |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Auto-detect extensions with an old style .php registration file, load |
| 83 | * the extensions, and return the LoadExtensionSchemaUpdates legacy handlers. |
| 84 | * |
| 85 | * @return array |
| 86 | */ |
| 87 | private function getAutoExtensionLegacySchemaHooks() { |
| 88 | $exts = $this->getOption( 'Extensions' ); |
| 89 | $extensionsDir = $this->getExtensionsDir(); |
| 90 | $files = []; |
| 91 | foreach ( $exts as $e ) { |
| 92 | if ( file_exists( "$extensionsDir/$e/$e.php" ) ) { |
| 93 | $files[] = "$extensionsDir/$e/$e.php"; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | if ( $files ) { |
| 98 | return $this->includeExtensionFiles( $files ); |
| 99 | } else { |
| 100 | return []; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Include the specified extension PHP files. Populate $wgAutoloadClasses |
| 106 | * and return the LoadExtensionSchemaUpdates hooks. |
| 107 | * |
| 108 | * @param string[] $files |
| 109 | * @return string[] LoadExtensionSchemaUpdates legacy hooks |
| 110 | */ |
| 111 | private function includeExtensionFiles( $files ) { |
| 112 | /** |
| 113 | * We need to define the $wgXyz variables before including extensions to avoid |
| 114 | * warnings about unset variables. However, the only thing we really |
| 115 | * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work |
| 116 | * if the extension has hidden hook registration in $wgExtensionFunctions, |
| 117 | * but we're not opening that can of worms |
| 118 | * @see https://phabricator.wikimedia.org/T28857 |
| 119 | */ |
| 120 | // Extract the defaults into the current scope |
| 121 | foreach ( MainConfigSchema::listDefaultValues( 'wg' ) as $var => $value ) { |
| 122 | $$var = $value; |
| 123 | } |
| 124 | |
| 125 | // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables |
| 126 | global $IP, $wgAutoloadClasses, $wgExtensionDirectory, $wgStyleDirectory; |
| 127 | $wgExtensionDirectory = $this->getExtensionsDir(); |
| 128 | $wgStyleDirectory = $this->getSkinsDir(); |
| 129 | |
| 130 | foreach ( $files as $file ) { |
| 131 | require_once $file; |
| 132 | } |
| 133 | |
| 134 | // Ignore everyone else's hooks. Lord knows what someone might be doing |
| 135 | // in ParserFirstCallInit (see T29171) |
| 136 | // @phpcs:disable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks |
| 137 | // @phpcs:ignore Generic.Files.LineLength.TooLong |
| 138 | // @phan-suppress-next-line PhanUndeclaredVariable,PhanCoalescingAlwaysNull $wgHooks is defined by MainConfigSchema |
| 139 | $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? []; |
| 140 | // @phpcs:enable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks |
| 141 | return $hooksWeWant; |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Auto-detect extensions with an extension.json file. Load the extensions, |
| 146 | * register classes with the autoloader and return the merged registry data. |
| 147 | * |
| 148 | * @return array |
| 149 | */ |
| 150 | private function getAutoExtensionData() { |
| 151 | $exts = $this->getOption( 'Extensions' ); |
| 152 | |
| 153 | $extensionProcessor = new ExtensionProcessor(); |
| 154 | foreach ( $exts as $e ) { |
| 155 | $jsonPath = $this->getExtensionsDir() . "/$e/extension.json"; |
| 156 | if ( file_exists( $jsonPath ) ) { |
| 157 | $extensionProcessor->extractInfoFromFile( $jsonPath ); |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | $autoload = $extensionProcessor->getExtractedAutoloadInfo(); |
| 162 | AutoLoader::loadFiles( $autoload['files'] ); |
| 163 | AutoLoader::registerClasses( $autoload['classes'] ); |
| 164 | AutoLoader::registerNamespaces( $autoload['namespaces'] ); |
| 165 | |
| 166 | return $extensionProcessor->getExtractedInfo(); |
| 167 | } |
| 168 | |
| 169 | } |