Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
JCContentHandler
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 9
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 serializeContent
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 merge3
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 getSlotDiffRendererWithOptions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 unserializeContent
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getContentClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeEmptyContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preSaveTransform
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 fillParserOutput
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace JsonConfig;
4
5use Content;
6use FormatJson;
7use IContextSource;
8use MediaWiki\Content\Renderer\ContentParseParams;
9use MediaWiki\Content\Transform\PreSaveTransformParams;
10use MediaWiki\Parser\ParserOutput;
11use TextContentHandler;
12
13/**
14 * JSON Json Config content handler
15 *
16 * @file
17 * @ingroup Extensions
18 * @ingroup JsonConfig
19 *
20 * @author Yuri Astrakhan <yurik@wikimedia.org>
21 */
22class JCContentHandler extends TextContentHandler {
23
24    /**
25     * Internal format to force pretty-printed json serialization
26     */
27    public const CONTENT_FORMAT_JSON_PRETTY = 'application/json+pretty';
28
29    /**
30     * @param string $modelId
31     */
32    public function __construct( $modelId ) {
33        parent::__construct( $modelId, [ CONTENT_FORMAT_JSON, self::CONTENT_FORMAT_JSON_PRETTY ] );
34    }
35
36    /**
37     * Returns the content's text as-is.
38     *
39     * @param \Content|JCContent $content This is actually a Content object
40     * @param string|null $format
41     * @return mixed
42     */
43    public function serializeContent( \Content $content, $format = null ) {
44        $this->checkFormat( $format );
45        $status = $content->getStatus();
46        if ( $status->isGood() ) {
47            $data = $content->getData(); // There are no errors, normalize data
48        } elseif ( $status->isOK() ) {
49            $data = $content->getRawData(); // JSON is valid, but the data has errors
50        } else {
51            return $content->getNativeData(); // Invalid JSON - can't do anything with it
52        }
53
54        return FormatJson::encode( $data, $format === self::CONTENT_FORMAT_JSON_PRETTY,
55            FormatJson::ALL_OK );
56    }
57
58    /**
59     * @param \Content|JCContent $oldContent
60     * @param \Content|JCContent $myContent
61     * @param \Content|JCContent $yourContent
62     * @return bool|JCContent
63     */
64    public function merge3( \Content $oldContent, \Content $myContent, \Content $yourContent ) {
65        // Almost identical clone of the parent's merge3, except that we use pretty-printed merge,
66        // thus allowing much more lenient line-based merging.
67
68        $this->checkModelID( $oldContent->getModel() );
69        $this->checkModelID( $myContent->getModel() );
70        $this->checkModelID( $yourContent->getModel() );
71
72        $format = self::CONTENT_FORMAT_JSON_PRETTY;
73
74        $old = $this->serializeContent( $oldContent, $format );
75        $mine = $this->serializeContent( $myContent, $format );
76        $yours = $this->serializeContent( $yourContent, $format );
77
78        $ok = wfMerge( $old, $mine, $yours, $result );
79
80        if ( !$ok ) {
81            return false;
82        }
83
84        if ( !$result ) {
85            return $this->makeEmptyContent();
86        }
87
88        return $this->unserializeContent( $result, $format );
89    }
90
91    protected function getSlotDiffRendererWithOptions( IContextSource $context, $options = [] ) {
92        return new JCSlotDiffRenderer( $this->createTextSlotDiffRenderer( $options ) );
93    }
94
95    /**
96     * Unserializes a JsonSchemaContent object.
97     *
98     * @param string|null $text Serialized form of the content
99     * @param null|string $format The format used for serialization
100     * @param bool $isSaving Perform extra validation
101     * @return JCContent the JsonSchemaContent object wrapping $text
102     */
103    public function unserializeContent( $text, $format = null, $isSaving = true ) {
104        $this->checkFormat( $format );
105        $modelId = $this->getModelID();
106        $class = JCSingleton::getContentClass( $modelId );
107        return new $class( $text, $modelId, $isSaving );
108    }
109
110    /** @inheritDoc */
111    protected function getContentClass() {
112        return JCSingleton::getContentClass( $this->getModelID() );
113    }
114
115    /**
116     * Creates an empty JsonSchemaContent object.
117     *
118     * @return JCContent
119     */
120    public function makeEmptyContent() {
121        // Each model could have its own default JSON value
122        // null notifies that default should be used
123        return $this->unserializeContent( null );
124    }
125
126    /**
127     * @inheritDoc
128     */
129    public function preSaveTransform(
130        Content $content,
131        PreSaveTransformParams $pstParams
132    ): Content {
133        '@phan-var JCContent $content';
134
135        $contentClass = $this->getContentClass();
136        if ( !$content->isValidJson() ) {
137            return $content; // Invalid JSON - can't do anything with it
138        }
139        $formatted = FormatJson::encode( $content->getData(), false, FormatJson::ALL_OK );
140        if ( $content->getText() !== $formatted ) {
141            return new $contentClass( $formatted, $content->getModel(), $content->thorough() );
142        }
143        return $content;
144    }
145
146    /**
147     * @inheritDoc
148     */
149    protected function fillParserOutput(
150        Content $content,
151        ContentParseParams $cpoParams,
152        ParserOutput &$output
153    ) {
154        '@phan-var JCContent $content';
155        $page = $cpoParams->getPage();
156        $generateHtml = $cpoParams->getGenerateHtml();
157        $revId = $cpoParams->getRevId();
158        $parserOptions = $cpoParams->getParserOptions();
159        if ( !$generateHtml ) {
160            return;
161        }
162
163        $status = $content->getStatus();
164        if ( !$status->isGood() ) {
165            // Use user's language, and split parser cache.  This should not have a big
166            // impact because data namespace is rarely viewed, but viewing it localized
167            // will be valuable
168            $lang = $parserOptions->getUserLangObj();
169            $html = $status->getHTML( false, false, $lang );
170        } else {
171            $html = '';
172        }
173
174        if ( $status->isOK() ) {
175            $html .= $content
176                ->getView( $content->getModel() )
177                ->valueToHtml( $content, $page, $revId, $parserOptions, $generateHtml, $output );
178        }
179
180        $output->setText( $html );
181    }
182}