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