Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.81% covered (warning)
86.81%
79 / 91
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialRestSandbox
86.81% covered (warning)
86.81%
79 / 91
33.33% covered (danger)
33.33%
2 / 6
13.39
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 isListed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSpecUrl
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 execute
81.82% covered (warning)
81.82%
36 / 44
0.00% covered (danger)
0.00%
0 / 1
4.10
 showForm
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Config\ServiceOptions;
10use MediaWiki\Html\Html;
11use MediaWiki\HTMLForm\HTMLForm;
12use MediaWiki\Message\MessageFormatterFactory;
13use MediaWiki\Rest\Module\ModuleManager;
14use MediaWiki\Rest\ResponseFactory;
15use MediaWiki\SpecialPage\SpecialPage;
16use MediaWiki\Utils\UrlUtils;
17use Wikimedia\ObjectCache\BagOStuff;
18
19/**
20 * A special page showing a Swagger UI for exploring REST APIs.
21 *
22 * @ingroup SpecialPage
23 * @since 1.43
24 */
25class SpecialRestSandbox extends SpecialPage {
26
27    private UrlUtils $urlUtils;
28    private ModuleManager $moduleManager;
29
30    public function __construct(
31        UrlUtils $urlUtils, MessageFormatterFactory $messageFormatterFactory, BagOStuff $srvCache
32    ) {
33        parent::__construct( 'RestSandbox' );
34
35        $this->urlUtils = $urlUtils;
36
37        $textFormatter = $messageFormatterFactory->getTextFormatter(
38            $this->getContentLanguage()->getCode()
39        );
40        $responseFactory = new ResponseFactory( [ $textFormatter ] );
41
42        $this->moduleManager = new ModuleManager(
43            new ServiceOptions( ModuleManager::CONSTRUCTOR_OPTIONS, $this->getConfig() ),
44            $srvCache,
45            $responseFactory
46        );
47    }
48
49    /** @inheritDoc */
50    public function isListed() {
51        // Hide the special pages if there are no APIs to explore.
52        return $this->moduleManager->hasApiSpecs();
53    }
54
55    /** @inheritDoc */
56    protected function getGroupName() {
57        return 'wiki';
58    }
59
60    private function getSpecUrl( array $apiSpecs, string $apiId ): ?string {
61        if ( $apiId !== '' ) {
62            $spec = $apiSpecs[$apiId] ?? null;
63        } else {
64            $spec = reset( $apiSpecs ) ?: null;
65        }
66
67        if ( !$spec ) {
68            return null;
69        }
70
71        return $this->urlUtils->expand( $spec['url'] );
72    }
73
74    /** @inheritDoc */
75    public function execute( $subPage ) {
76        $this->setHeaders();
77        $out = $this->getOutput();
78        $this->addHelpLink( 'Help:RestSandbox' );
79
80        $apiId = $this->getRequest()->getRawVal( 'api' ) ?? $subPage ?? '';
81        $apiSpecs = $this->moduleManager->getApiSpecs();
82        $specUrl = $this->getSpecUrl( $apiSpecs, $apiId );
83
84        $out->addJsConfigVars( [
85            'specUrl' => $specUrl
86        ] );
87
88        $out->addModuleStyles( [
89            'mediawiki.special',
90            'mediawiki.hlist',
91            'mediawiki.special.restsandbox.styles'
92        ] );
93
94        if ( !$apiSpecs ) {
95            $out->addHTML( Html::errorBox(
96                $out->msg( 'restsandbox-no-specs-configured' )->parse()
97            ) );
98            return;
99        }
100
101        if ( $out->getLanguage()->getCode() !== 'en' ) {
102            $out->addHTML( Html::noticeBox( $out->msg( 'restsandbox-disclaimer' )->parse() ) );
103        }
104
105        $this->showForm( $apiSpecs, $apiId );
106
107        if ( !$specUrl ) {
108            $out->addHTML( Html::errorBox(
109                $out->msg( 'restsandbox-no-such-api', $apiId )->parse()
110            ) );
111            return;
112        }
113
114        $out->addModules( [
115            'mediawiki.codex.messagebox.styles',
116            'mediawiki.special.restsandbox'
117        ] );
118
119        $out->addHTML( Html::openElement( 'div', [ 'id' => 'mw-restsandbox' ] ) );
120
121        // Hidden when JS is available
122        $out->addHTML( Html::errorBox(
123            $out->msg( 'restsandbox-jsonly' )->parse(),
124            '',
125            'mw-restsandbox-client-nojs'
126        ) );
127
128        // To be replaced by Swagger UI.
129        $out->addElement( 'div', [
130            'id' => 'mw-restsandbox-swagger-ui',
131            // Force direction to "LTR" with swagger-ui.
132            // Since the swagger content is not internationalized, the information is always in English.
133            // We have to force the direction to "LTR" to avoid the content (specifically json strings)
134            // from being mangled.
135            'dir' => 'ltr',
136            'lang' => 'en',
137            // For dark mode compatibility
138            'class' => 'skin-invert'
139        ] );
140
141        $out->addHTML( Html::closeElement( 'div' ) ); // #mw-restsandbox
142    }
143
144    private function showForm( array $apiSpecs, string $apiId ) {
145        $apis = [];
146
147        foreach ( $apiSpecs as $key => $spec ) {
148            $apis[$spec['name']] = $key;
149        }
150
151        $formDescriptor = [
152            'intro' => [
153                'type' => 'info',
154                'raw' => true,
155                'default' => $this->msg( 'restsandbox-text' )->parseAsBlock()
156            ],
157            'api' => [
158                'type' => 'select',
159                'name' => 'api',
160                'label-message' => 'restsandbox-select-api',
161                'options' => $apis,
162                'default' => $apiId
163            ],
164            'title' => [
165                'type' => 'hidden',
166                'name' => 'title',
167                'default' => $this->getPageTitle()->getPrefixedDBkey()
168            ],
169        ];
170
171        $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
172
173        $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
174        $htmlForm->setAction( $action );
175        $htmlForm->setMethod( 'GET' );
176        $htmlForm->setId( 'mw-restsandbox-form' );
177        $htmlForm->prepareForm()->displayForm( false );
178    }
179}