Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
48.33% covered (danger)
48.33%
29 / 60
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
TransformHandler
48.33% covered (danger)
48.33%
29 / 60
0.00% covered (danger)
0.00%
0 / 4
68.79
0.00% covered (danger)
0.00%
0 / 1
 getParamSettings
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 checkPreconditions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRequestAttributes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 execute
72.50% covered (warning)
72.50%
29 / 40
0.00% covered (danger)
0.00%
0 / 1
19.68
1<?php
2
3/**
4 * Copyright (C) 2011-2020 Wikimedia Foundation and others.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21namespace MediaWiki\Rest\Handler;
22
23use MediaWiki\Rest\Handler\Helper\ParsoidFormatHelper;
24use MediaWiki\Rest\HttpException;
25use MediaWiki\Rest\LocalizedHttpException;
26use MediaWiki\Rest\Response;
27use Wikimedia\Message\MessageValue;
28use Wikimedia\ParamValidator\ParamValidator;
29
30/**
31 * Handler for transforming content given in the request.
32 * - /v1/transform/{from}/to/{format}
33 * - /v1/transform/{from}/to/{format}/{title}
34 * - /v1/transform/{from}/to/{format}/{title}/{revision}
35 *
36 * @see https://www.mediawiki.org/wiki/Parsoid/API#POST
37 */
38class TransformHandler extends ParsoidHandler {
39
40    /** @inheritDoc */
41    public function getParamSettings() {
42        return [
43            'from' => [ self::PARAM_SOURCE => 'path',
44                ParamValidator::PARAM_TYPE => 'string',
45                ParamValidator::PARAM_REQUIRED => true, ],
46            'format' => [ self::PARAM_SOURCE => 'path',
47                ParamValidator::PARAM_TYPE => 'string',
48                ParamValidator::PARAM_REQUIRED => true, ],
49            'title' => [ self::PARAM_SOURCE => 'path',
50                ParamValidator::PARAM_TYPE => 'string',
51                ParamValidator::PARAM_REQUIRED => false, ],
52            'revision' => [ self::PARAM_SOURCE => 'path',
53                ParamValidator::PARAM_TYPE => 'string',
54                ParamValidator::PARAM_REQUIRED => false, ], ];
55    }
56
57    public function checkPreconditions() {
58        // NOTE: disable all precondition checks.
59        // If-(not)-Modified-Since is not supported by the /transform/ handler.
60        // If-None-Match is not supported by the /transform/ handler.
61        // If-Match for wt2html is handled in getRequestAttributes.
62    }
63
64    protected function &getRequestAttributes(): array {
65        $attribs =& parent::getRequestAttributes();
66
67        $request = $this->getRequest();
68
69        // NOTE: If there is more than one ETag, this will break.
70        //       We don't have a good way to test multiple ETag to see if one of them is a working stash key.
71        $ifMatch = $request->getHeaderLine( 'If-Match' );
72
73        if ( $ifMatch ) {
74            $attribs['opts']['original']['etag'] = $ifMatch;
75        }
76
77        return $attribs;
78    }
79
80    /**
81     * Transform content given in the request from or to wikitext.
82     *
83     * @return Response
84     * @throws HttpException
85     */
86    public function execute(): Response {
87        $request = $this->getRequest();
88        $from = $request->getPathParam( 'from' );
89        $format = $request->getPathParam( 'format' );
90
91        // XXX: Fallback to the default valid transforms in case the request is
92        //      coming from a legacy client (restbase) that supports everything
93        //      in the default valid transforms.
94        $validTransformations = $this->getConfig()['transformations'] ?? ParsoidFormatHelper::VALID_TRANSFORM;
95
96        if ( !isset( $validTransformations[$from] ) || !in_array( $format,
97                $validTransformations[$from],
98                true ) ) {
99            throw new LocalizedHttpException( new MessageValue( "rest-invalid-transform", [ $from, $format ] ), 404 );
100        }
101        $attribs = &$this->getRequestAttributes();
102        if ( !$this->acceptable( $attribs ) ) { // mutates $attribs
103            throw new LocalizedHttpException( new MessageValue( "rest-unsupported-target-format" ), 406 );
104        }
105        if ( $from === ParsoidFormatHelper::FORMAT_WIKITEXT ) {
106            // Accept wikitext as a string or object{body,headers}
107            $wikitext = $attribs['opts']['wikitext'] ?? null;
108            if ( is_array( $wikitext ) ) {
109                $wikitext = $wikitext['body'];
110                // We've been given a pagelanguage for this page.
111                if ( isset( $attribs['opts']['wikitext']['headers']['content-language'] ) ) {
112                    $attribs['pagelanguage'] = $attribs['opts']['wikitext']['headers']['content-language'];
113                }
114            }
115            // We've been given source for this page
116            if ( $wikitext === null && isset( $attribs['opts']['original']['wikitext'] ) ) {
117                $wikitext = $attribs['opts']['original']['wikitext']['body'];
118                // We've been given a pagelanguage for this page.
119                if ( isset( $attribs['opts']['original']['wikitext']['headers']['content-language'] ) ) {
120                    $attribs['pagelanguage'] = $attribs['opts']['original']['wikitext']['headers']['content-language'];
121                }
122            }
123            // Abort if no wikitext or title.
124            if ( $wikitext === null && empty( $attribs['pageName'] ) ) {
125                throw new LocalizedHttpException( new MessageValue( "rest-transform-missing-title" ), 400 );
126            }
127            $pageConfig = $this->tryToCreatePageConfig( $attribs, $wikitext );
128
129            return $this->wt2html( $pageConfig,
130                $attribs,
131                $wikitext );
132        } elseif ( $format === ParsoidFormatHelper::FORMAT_WIKITEXT ) {
133            $html = $attribs['opts']['html'] ?? null;
134            // Accept html as a string or object{body,headers}
135            if ( is_array( $html ) ) {
136                $html = $html['body'];
137            }
138            if ( $html === null ) {
139                throw new LocalizedHttpException( new MessageValue( "rest-transform-missing-html" ), 400 );
140            }
141
142            // TODO: use ETag from If-Match header, for compat!
143
144            $page = $this->tryToCreatePageIdentity( $attribs );
145
146            return $this->html2wt(
147                $page,
148                $attribs,
149                $html
150            );
151        } else {
152            return $this->pb2pb( $attribs );
153        }
154    }
155}