Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.18% covered (success)
97.18%
69 / 71
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageHTMLHandler
97.18% covered (success)
97.18%
69 / 71
77.78% covered (warning)
77.78%
7 / 9
22
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getRedirectHelper
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 postValidationSetup
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
 run
97.06% covered (success)
97.06%
33 / 34
0.00% covered (danger)
0.00%
0 / 1
6
 getETag
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getLastModified
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getOutputMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 needsWriteAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getParamSettings
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Rest\Handler;
4
5use LogicException;
6use MediaWiki\Rest\Handler\Helper\HtmlOutputHelper;
7use MediaWiki\Rest\Handler\Helper\HtmlOutputRendererHelper;
8use MediaWiki\Rest\Handler\Helper\PageContentHelper;
9use MediaWiki\Rest\Handler\Helper\PageRedirectHelper;
10use MediaWiki\Rest\Handler\Helper\PageRestHelperFactory;
11use MediaWiki\Rest\LocalizedHttpException;
12use MediaWiki\Rest\Response;
13use MediaWiki\Rest\SimpleHandler;
14use MediaWiki\Rest\StringStream;
15use Wikimedia\Assert\Assert;
16
17/**
18 * A handler that returns Parsoid HTML for the following routes:
19 * - /page/{title}/html,
20 * - /page/{title}/with_html
21 *
22 * @package MediaWiki\Rest\Handler
23 */
24class PageHTMLHandler extends SimpleHandler {
25
26    private HtmlOutputHelper $htmlHelper;
27    private PageContentHelper $contentHelper;
28    private PageRestHelperFactory $helperFactory;
29
30    public function __construct(
31        PageRestHelperFactory $helperFactory
32    ) {
33        $this->contentHelper = $helperFactory->newPageContentHelper();
34        $this->helperFactory = $helperFactory;
35    }
36
37    private function getRedirectHelper(): PageRedirectHelper {
38        return $this->helperFactory->newPageRedirectHelper(
39            $this->getResponseFactory(),
40            $this->getRouter(),
41            $this->getPath(),
42            $this->getRequest()
43        );
44    }
45
46    protected function postValidationSetup() {
47        $authority = $this->getAuthority();
48        $this->contentHelper->init( $authority, $this->getValidatedParams() );
49
50        $page = $this->contentHelper->getPageIdentity();
51        $isSystemMessage = $this->contentHelper->useDefaultSystemMessage();
52
53        if ( $page ) {
54            if ( $isSystemMessage ) {
55                $this->htmlHelper = $this->helperFactory->newHtmlMessageOutputHelper( $page );
56            } else {
57                $revision = $this->contentHelper->getTargetRevision();
58                $this->htmlHelper = $this->helperFactory->newHtmlOutputRendererHelper(
59                    $page, $this->getValidatedParams(), $authority, $revision
60                );
61
62                $request = $this->getRequest();
63                $acceptLanguage = $request->getHeaderLine( 'Accept-Language' ) ?: null;
64                if ( $acceptLanguage ) {
65                    $this->htmlHelper->setVariantConversionLanguage(
66                        $acceptLanguage
67                    );
68                }
69            }
70        }
71    }
72
73    /**
74     * @return Response
75     * @throws LocalizedHttpException
76     */
77    public function run(): Response {
78        $this->contentHelper->checkAccessPermission();
79        $page = $this->contentHelper->getPageIdentity();
80
81        $followWikiRedirects = $this->contentHelper->getRedirectsAllowed();
82
83        // The call to $this->contentHelper->getPage() should not return null if
84        // $this->contentHelper->checkAccess() did not throw.
85        Assert::invariant( $page !== null, 'Page should be known' );
86
87        $redirectHelper = $this->getRedirectHelper();
88        $redirectHelper->setFollowWikiRedirects( $followWikiRedirects );
89        // Should treat variant redirects a special case as wiki redirects
90        // if ?redirect=no language variant should do nothing and fall into the 404 path
91        $redirectResponse = $redirectHelper->createRedirectResponseIfNeeded(
92            $page,
93            $this->contentHelper->getTitleText()
94        );
95
96        if ( $redirectResponse !== null ) {
97            return $redirectResponse;
98        }
99
100        // We could have a missing page at this point, check and return 404 if that's the case
101        $this->contentHelper->checkHasContent();
102
103        $parserOutput = $this->htmlHelper->getHtml();
104        $parserOutputHtml = $parserOutput->getRawText();
105
106        $outputMode = $this->getOutputMode();
107        switch ( $outputMode ) {
108            case 'html':
109                $response = $this->getResponseFactory()->create();
110                $this->contentHelper->setCacheControl( $response, $parserOutput->getCacheExpiry() );
111                $response->setBody( new StringStream( $parserOutputHtml ) );
112                break;
113            case 'with_html':
114                $body = $this->contentHelper->constructMetadata();
115                $body['html'] = $parserOutputHtml;
116
117                $redirectTargetUrl = $redirectHelper->getWikiRedirectTargetUrl( $page );
118
119                if ( $redirectTargetUrl ) {
120                    $body['redirect_target'] = $redirectTargetUrl;
121                }
122
123                $response = $this->getResponseFactory()->createJson( $body );
124                $this->contentHelper->setCacheControl( $response, $parserOutput->getCacheExpiry() );
125                break;
126            default:
127                throw new LogicException( "Unknown HTML type $outputMode" );
128        }
129
130        $setContentLanguageHeader = ( $outputMode === 'html' );
131        $this->htmlHelper->putHeaders( $response, $setContentLanguageHeader );
132
133        return $response;
134    }
135
136    /**
137     * Returns an ETag representing a page's source. The ETag assumes a page's source has changed
138     * if the latest revision of a page has been made private, un-readable for another reason,
139     * or a newer revision exists.
140     * @return string|null
141     */
142    protected function getETag(): ?string {
143        if ( !$this->contentHelper->isAccessible() || !$this->contentHelper->hasContent() ) {
144            return null;
145        }
146
147        // Vary eTag based on output mode
148        return $this->htmlHelper->getETag( $this->getOutputMode() );
149    }
150
151    /**
152     * @return string|null
153     */
154    protected function getLastModified(): ?string {
155        if ( !$this->contentHelper->isAccessible() || !$this->contentHelper->hasContent() ) {
156            return null;
157        }
158
159        return $this->htmlHelper->getLastModified();
160    }
161
162    private function getOutputMode(): string {
163        return $this->getConfig()['format'];
164    }
165
166    public function needsWriteAccess(): bool {
167        return false;
168    }
169
170    public function getParamSettings(): array {
171        return array_merge(
172            $this->contentHelper->getParamSettings(),
173            // Note that postValidation we might end up using
174            // a HtmlMessageOutputHelper, but the param settings
175            // for that are a subset of those for HtmlOutputRendererHelper
176            HtmlOutputRendererHelper::getParamSettings()
177        );
178    }
179}