Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExtensionsProvider
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 8
342
0.00% covered (danger)
0.00%
0 / 1
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProvidedNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
 getExtensionsDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSkinsDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAutoExtensionLegacySchemaHooks
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 includeExtensionFiles
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getAutoExtensionData
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Installer\Task;
4
5use AutoLoader;
6use MediaWiki\HookContainer\HookContainer;
7use MediaWiki\HookContainer\StaticHookRegistry;
8use MediaWiki\MainConfigSchema;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Registration\ExtensionProcessor;
11use MediaWiki\Status\Status;
12
13/**
14 * A scheduled provider which loads extensions
15 *
16 * @internal For use by the installer
17 */
18class 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}