Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.23% covered (success)
96.23%
51 / 53
81.82% covered (warning)
81.82%
9 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageSourceHandler
96.23% covered (success)
96.23%
51 / 53
81.82% covered (warning)
81.82%
9 / 11
17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
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%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 constructHtmlUrl
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 run
96.97% covered (success)
96.97%
32 / 33
0.00% covered (danger)
0.00%
0 / 1
7
 getETag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLastModified
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRepresentation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Rest\Handler;
4
5use LogicException;
6use MediaWiki\Page\PageReference;
7use MediaWiki\Rest\Handler\Helper\PageContentHelper;
8use MediaWiki\Rest\Handler\Helper\PageRedirectHelper;
9use MediaWiki\Rest\Handler\Helper\PageRestHelperFactory;
10use MediaWiki\Rest\LocalizedHttpException;
11use MediaWiki\Rest\Response;
12use MediaWiki\Rest\SimpleHandler;
13use MediaWiki\Title\TitleFormatter;
14use Wikimedia\Message\MessageValue;
15
16/**
17 * Handler class for Core REST API Page Source endpoint with the following routes:
18 * - /page/{title}
19 * - /page/{title}/bare
20 */
21class PageSourceHandler extends SimpleHandler {
22
23    private TitleFormatter $titleFormatter;
24    private PageRestHelperFactory $helperFactory;
25    private PageContentHelper $contentHelper;
26
27    public function __construct(
28        TitleFormatter $titleFormatter,
29        PageRestHelperFactory $helperFactory
30    ) {
31        $this->titleFormatter = $titleFormatter;
32        $this->contentHelper = $helperFactory->newPageContentHelper();
33        $this->helperFactory = $helperFactory;
34    }
35
36    private function getRedirectHelper(): PageRedirectHelper {
37        return $this->helperFactory->newPageRedirectHelper(
38            $this->getResponseFactory(),
39            $this->getRouter(),
40            $this->getPath(),
41            $this->getRequest()
42        );
43    }
44
45    protected function postValidationSetup() {
46        $this->contentHelper->init( $this->getAuthority(), $this->getValidatedParams() );
47    }
48
49    /**
50     * @param PageReference $page
51     * @return string
52     */
53    private function constructHtmlUrl( PageReference $page ): string {
54        return $this->getRouter()->getRouteUrl(
55            '/v1/page/{title}/html',
56            [ 'title' => $this->titleFormatter->getPrefixedText( $page ) ]
57        );
58    }
59
60    /**
61     * @return Response
62     * @throws LocalizedHttpException
63     */
64    public function run(): Response {
65        $this->contentHelper->checkAccess();
66        $page = $this->contentHelper->getPageIdentity();
67
68        if ( !$page->exists() ) {
69            // We may get here for "known" but non-existing pages, such as
70            // message pages. Since there is no page, we should still return
71            // a 404. See T349677 for discussion.
72            $titleText = $this->contentHelper->getTitleText() ?? '(unknown)';
73            throw new LocalizedHttpException(
74                MessageValue::new( 'rest-nonexistent-title' )
75                    ->plaintextParams( $titleText ),
76                404
77            );
78        }
79
80        $redirectHelper = $this->getRedirectHelper();
81
82        '@phan-var \MediaWiki\Page\ExistingPageRecord $page';
83        $redirectResponse = $redirectHelper->createNormalizationRedirectResponseIfNeeded(
84            $page,
85            $this->contentHelper->getTitleText()
86        );
87
88        if ( $redirectResponse !== null ) {
89            return $redirectResponse;
90        }
91
92        $outputMode = $this->getOutputMode();
93        switch ( $outputMode ) {
94            case 'bare':
95                $body = $this->contentHelper->constructMetadata();
96                $body['html_url'] = $this->constructHtmlUrl( $page );
97                break;
98            case 'source':
99                $content = $this->contentHelper->getContent();
100                $body = $this->contentHelper->constructMetadata();
101                $body['source'] = $content->getText();
102                break;
103            default:
104                throw new LogicException( "Unknown HTML type $outputMode" );
105        }
106
107        // If param redirect=no is present, that means this page can be a redirect
108        // check for a redirectTargetUrl and send it to the body as `redirect_target`
109        '@phan-var \MediaWiki\Page\ExistingPageRecord $page';
110        $redirectTargetUrl = $redirectHelper->getWikiRedirectTargetUrl( $page );
111
112        if ( $redirectTargetUrl ) {
113            $body['redirect_target'] = $redirectTargetUrl;
114        }
115
116        $response = $this->getResponseFactory()->createJson( $body );
117        $this->contentHelper->setCacheControl( $response );
118
119        return $response;
120    }
121
122    /**
123     * Returns an ETag representing a page's source. The ETag assumes a page's source has changed
124     * if the latest revision of a page has been made private, un-readable for another reason,
125     * or a newer revision exists.
126     * @return string|null
127     */
128    protected function getETag(): ?string {
129        return $this->contentHelper->getETag();
130    }
131
132    /**
133     * @return string|null
134     */
135    protected function getLastModified(): ?string {
136        return $this->contentHelper->getLastModified();
137    }
138
139    private function getOutputMode(): string {
140        return $this->getConfig()['format'];
141    }
142
143    public function needsWriteAccess(): bool {
144        return false;
145    }
146
147    public function getParamSettings(): array {
148        return $this->contentHelper->getParamSettings();
149    }
150
151    /**
152     * @return bool
153     */
154    protected function hasRepresentation() {
155        return $this->contentHelper->hasContent();
156    }
157}