Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiJsonSchema
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 4
420
0.00% covered (danger)
0.00%
0 / 1
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 markCacheable
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
306
1<?php
2/**
3 * API module for retrieving JSON Schema.
4 *
5 * @file
6 * @ingroup EventLogging
7 * @ingroup Extensions
8 *
9 * @author Ori Livneh <ori@wikimedia.org>
10 */
11
12namespace MediaWiki\Extension\EventLogging;
13
14use ApiBase;
15use ApiResult;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Revision\RevisionRecord;
18use MediaWiki\Revision\SlotRecord;
19use MediaWiki\Title\Title;
20use Wikimedia\ParamValidator\ParamValidator;
21
22/**
23 * API module for retrieving JSON Schema.
24 * This avoids API result paths and returns HTTP error codes in order to
25 * act like a request for the raw page content.
26 * @ingroup API
27 */
28class ApiJsonSchema extends ApiBase {
29
30    public function getAllowedParams() {
31        return [
32            'revid' => [
33                ParamValidator::PARAM_TYPE => 'integer',
34            ],
35            'title' => [
36                ParamValidator::PARAM_TYPE => 'string',
37            ],
38        ];
39    }
40
41    /**
42     * @see ApiBase::getExamplesMessages()
43     * @return array
44     */
45    protected function getExamplesMessages() {
46        return [
47            'action=jsonschema&revid=1234'
48                => 'apihelp-jsonschema-example-1',
49            'action=jsonschema&title=Test'
50                => 'apihelp-jsonschema-example-2',
51        ];
52    }
53
54    /**
55     * Set headers on the pending HTTP response.
56     * @param RevisionRecord $revRecord
57     */
58    private function markCacheable( RevisionRecord $revRecord ) {
59        $main = $this->getMain();
60        $main->setCacheMode( 'public' );
61        $main->setCacheMaxAge( 300 );
62
63        $lastModified = wfTimestamp( TS_RFC2822, $revRecord->getTimestamp() );
64        $main->getRequest()->response()->header( "Last-Modified: $lastModified" );
65    }
66
67    public function execute() {
68        $params = $this->extractRequestParams();
69
70        if ( !isset( $params['revid'] ) && !isset( $params['title'] ) ) {
71            $this->dieWithError(
72                [ 'apierror-missingparam-at-least-one-of', 'revid', 'title' ],
73                null, null, 400
74            );
75        }
76
77        $revLookup = MediaWikiServices::getInstance()->getRevisionLookup();
78        // If we are given revid, then look up Revision and
79        // verify that $params['title'] (if given) matches.
80        if ( isset( $params['revid'] ) ) {
81            $revRecord = $revLookup->getRevisionById( $params['revid'] );
82            if ( !$revRecord ) {
83                $this->dieWithError(
84                    [ 'apierror-nosuchrevid', $params['revid'] ], null, null, 400
85                );
86            }
87            $title = Title::newFromLinkTarget( $revRecord->getPageAsLinkTarget() );
88            if ( !$title || !$title->inNamespace( NS_SCHEMA ) ) {
89                $this->dieWithError(
90                    [ 'apierror-invalidtitle', wfEscapeWikiText( $title ?: '' ) ], null, null, 400
91                );
92            }
93
94            // If we use the revid param for lookup; the 'title' parameter is
95            // optional. If present, it is used to assert that the specified
96            // revision ID is indeed a revision of a page with the specified
97            // title. (Bug 46174)
98            if (
99                $params['title'] &&
100                !$title->equals( Title::newFromText( $params['title'], NS_SCHEMA ) )
101            ) {
102                $this->dieWithError(
103                    [ 'apierror-revwrongpage', $params['revid'], wfEscapeWikiText( $params['title'] ) ],
104                    null, null, 400
105                );
106            }
107
108        // Else use $params['title'] and get the latest revision
109        } else {
110            $title = Title::newFromText( $params['title'], NS_SCHEMA );
111            if ( !$title || !$title->exists() || !$title->inNamespace( NS_SCHEMA ) ) {
112                $this->dieWithError(
113                    [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ], null, null, 400
114                );
115            }
116
117            $revRecord = $revLookup->getRevisionById( $title->getLatestRevID() );
118        }
119
120        /** @var JsonSchemaContent $content */
121        $content = $revRecord->getContent( SlotRecord::MAIN );
122        if ( !$content ) {
123            $this->dieWithError(
124                [ 'apierror-nosuchrevid', $revRecord->getId() ],
125                null, null, 400
126            );
127        }
128
129        // @phan-suppress-next-line PhanTypeMismatchArgumentNullable T240141
130        $this->markCacheable( $revRecord );
131        '@phan-var JsonSchemaContent $content';
132        $schema = $content->getJsonData();
133
134        $result = $this->getResult();
135        $result->addValue( null, 'title', $title->getText() );
136        foreach ( $schema as $k => &$v ) {
137            if ( $k === 'properties' ) {
138                foreach ( $v as &$properties ) {
139                    $properties[ApiResult::META_BC_BOOLS] = [ 'required' ];
140                }
141            }
142            $result->addValue( null, $k, $v );
143        }
144    }
145}