Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MissingExtensionException
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 5
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 renderHtml
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 renderText
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 render
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getMWLogo
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace MediaWiki\Registration;
4
5use Exception;
6use HttpStatus;
7use MediaWiki\Html\TemplateParser;
8use Wikimedia\ObjectCache\EmptyBagOStuff;
9
10/**
11 * Thrown when ExtensionRegistry cannot open the extension.json or skin.json file.
12 *
13 * We handle this case specially, because it is one of the more
14 * common errors a new MW sysadmin is likely to encounter and we
15 * want their initial experience to be good. wfLoadExtension()
16 * generally happens before MWExceptionRenderer gets installed
17 * so we cannot use that.
18 *
19 * @ingroup ExtensionRegistry
20 * @internal
21 */
22class MissingExtensionException extends Exception {
23    private bool $isSkin;
24    private string $extName = 'unknown';
25    private string $path;
26    private string $error;
27
28    /**
29     * @param string $path Path of file that cannot be read
30     * @param string $error Text of error mtime gave
31     */
32    public function __construct( string $path, string $error ) {
33        $this->isSkin = str_ends_with( $path, "/skin.json" );
34        $m = [];
35        preg_match( "!/([^/]*)/[^/]*.json$!", $path, $m );
36        if ( $m ) {
37            $this->extName = $m[1];
38        }
39        $this->path = $path;
40        $this->error = $error;
41
42        parent::__construct( "Error Loading extension. Unable to open file $path$error" );
43    }
44
45    /**
46     * Output error message as html.
47     *
48     * Avoid relying on MW stuff, as it might not be setup yet.
49     * We don't bother translating, as the user may not have even set lang yet.
50     *
51     */
52    private function renderHtml() {
53        if ( !headers_sent() ) {
54            HttpStatus::header( 500 );
55            header( 'Content-Type: text/html; charset=UTF-8' );
56        }
57
58        $templateParser = new TemplateParser( null, new EmptyBagOStuff() );
59
60        try {
61            echo $templateParser->processTemplate(
62                'ExtensionConfigError',
63                [
64                    'version' => MW_VERSION,
65                    'path' => $this->path,
66                    'type' => $this->isSkin ? 'skin' : 'extension',
67                    'error' => $this->error,
68                    'extName' => $this->extName,
69                    'trace' => $this->getTraceAsString(),
70                    'mwLogo' => $this->getMWLogo(),
71                ]
72            );
73        } catch ( Exception $e ) {
74            echo 'Error: ' . htmlspecialchars( $e->getMessage() );
75        }
76    }
77
78    /**
79     * Render the error for CLI
80     */
81    private function renderText() {
82        $type = $this->isSkin ? 'skin' : 'extension';
83        echo "Error: The $this->extName $type cannot be loaded. "
84            . "Check that all of its files are installed properly.\n\n";
85        echo $this->getTraceAsString();
86        echo "\n";
87    }
88
89    /**
90     * Output an error response and exit.
91     *
92     * @return never
93     */
94    public function render() {
95        if ( wfIsCli() ) {
96            $this->renderText();
97        } else {
98            $this->renderHtml();
99        }
100        // Make sure that the error gets into logs.
101        // This will also stop execution.
102        trigger_error( $this->getMessage(), E_USER_ERROR );
103    }
104
105    /**
106     * Get the url for the MW logo
107     *
108     * @return string
109     */
110    private function getMWLogo() {
111        global $wgResourceBasePath;
112        $suffix = "/resources/assets/mediawiki.png";
113        if ( $wgResourceBasePath !== null ) {
114            // We are early in setup, so we can't rely on this.
115            return $wgResourceBasePath . $suffix;
116        }
117        $path = '/';
118        foreach ( array_filter( explode( '/', $_SERVER['PHP_SELF'] ) ) as $part ) {
119            if ( !preg_match( '/\.php$/', $part ) ) {
120                $path .= "$part/";
121            } else {
122                break;
123            }
124        }
125
126        return $path . $suffix;
127    }
128}
129
130/** @deprecated class alias since 1.43 */
131class_alias( MissingExtensionException::class, 'MissingExtensionException' );