Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
37.23% covered (danger)
37.23%
35 / 94
33.33% covered (danger)
33.33%
4 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntitySchemaContentHandler
37.23% covered (danger)
37.23%
35 / 94
33.33% covered (danger)
33.33%
4 / 12
236.95
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
 getContentClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSlotDiffRendererWithOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getPageViewLanguage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 canBeUsedOn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getActionOverrides
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getActionOverridesEdit
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
30
 getActionOverridesSubmit
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 supportsDirectApiEditing
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUndoContent
79.17% covered (warning)
79.17%
19 / 24
0.00% covered (danger)
0.00%
0 / 1
8.58
 isParserCacheSupported
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fillParserOutput
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare( strict_types = 1 );
4
5namespace EntitySchema\MediaWiki\Content;
6
7use Action;
8use Article;
9use Content;
10use EntitySchema\DataAccess\EntitySchemaEncoder;
11use EntitySchema\MediaWiki\Actions\EntitySchemaEditAction;
12use EntitySchema\MediaWiki\Actions\EntitySchemaSubmitAction;
13use EntitySchema\MediaWiki\Actions\RestoreSubmitAction;
14use EntitySchema\MediaWiki\Actions\RestoreViewAction;
15use EntitySchema\MediaWiki\Actions\UndoSubmitAction;
16use EntitySchema\MediaWiki\Actions\UndoViewAction;
17use EntitySchema\MediaWiki\UndoHandler;
18use EntitySchema\Presentation\InputValidator;
19use EntitySchema\Services\Converter\EntitySchemaConverter;
20use IContextSource;
21use JsonContentHandler;
22use Language;
23use LogicException;
24use MediaWiki\Content\IContentHandlerFactory;
25use MediaWiki\Content\Renderer\ContentParseParams;
26use MediaWiki\MediaWikiServices;
27use MediaWiki\Title\Title;
28use ParserOutput;
29use RequestContext;
30use Wikibase\Repo\WikibaseRepo;
31
32/**
33 * Content handler for the EntitySchema content
34 *
35 * @license GPL-2.0-or-later
36 */
37class EntitySchemaContentHandler extends JsonContentHandler {
38
39    private IContentHandlerFactory $contentHandlerFactory;
40
41    public function __construct(
42        string $modelId,
43        IContentHandlerFactory $contentHandlerFactory
44    ) {
45        // $modelId is typically EntitySchemaContent::CONTENT_MODEL_ID
46        parent::__construct( $modelId );
47        $this->contentHandlerFactory = $contentHandlerFactory;
48    }
49
50    protected function getContentClass(): string {
51        return EntitySchemaContent::class;
52    }
53
54    protected function getSlotDiffRendererWithOptions(
55        IContextSource $context,
56        $options = []
57    ): EntitySchemaSlotDiffRenderer {
58        return new EntitySchemaSlotDiffRenderer(
59            $context,
60            $this->createTextSlotDiffRenderer( $options )
61        );
62    }
63
64    /**
65     * @see ContentHandler::getPageViewLanguage
66     *
67     * This implementation returns the user language, because Schemas get rendered in
68     * the user's language. The PageContentLanguage hook is bypassed.
69     *
70     * @param Title $title (unused) the page to determine the language for.
71     * @param Content|null $content (unused) the page's content
72     *
73     * @return Language The page's language
74     */
75    public function getPageViewLanguage( Title $title, Content $content = null ): Language {
76        $context = RequestContext::getMain();
77        return $context->getLanguage();
78    }
79
80    public function canBeUsedOn( Title $title ): bool {
81        return $title->inNamespace( NS_ENTITYSCHEMA_JSON ) && parent::canBeUsedOn( $title );
82    }
83
84    public function getActionOverrides(): array {
85        return [
86            'edit' => function ( Article $article, IContextSource $context ) {
87                return $this->getActionOverridesEdit( $article, $context );
88            },
89            'submit' => function ( Article $article, IContextSource $context ) {
90                return $this->getActionOverridesSubmit( $article, $context );
91            },
92        ];
93    }
94
95    /**
96     * @param Article $article
97     * @param IContextSource $context
98     * @return Action|callable
99     */
100    private function getActionOverridesEdit(
101        Article $article,
102        IContextSource $context
103    ) {
104        global $wgEditSubmitButtonLabelPublish;
105
106        if ( $article->getPage()->getRevisionRecord() === null ) {
107            return Action::factory( 'view', $article, $context );
108        }
109
110        $req = $context->getRequest();
111
112        if (
113            $req->getCheck( 'undo' )
114            || $req->getCheck( 'undoafter' )
115        ) {
116            return new UndoViewAction(
117                $article,
118                $context,
119                $this->getSlotDiffRendererWithOptions( $context )
120            );
121        }
122
123        if ( $req->getCheck( 'restore' ) ) {
124            return new RestoreViewAction(
125                $article,
126                $context,
127                $this->getSlotDiffRendererWithOptions( $context )
128            );
129        }
130
131        // TODo: check redirect?
132        // !$article->isRedirect()
133        $repoSettings = WikibaseRepo::getSettings();
134        return new EntitySchemaEditAction(
135            $article,
136            $context,
137            new InputValidator(
138                $context,
139                MediaWikiServices::getInstance()->getMainConfig(),
140                MediaWikiServices::getInstance()->getLanguageNameUtils()
141            ),
142            $wgEditSubmitButtonLabelPublish,
143            MediaWikiServices::getInstance()->getUserOptionsLookup(),
144            $repoSettings->getSetting( 'dataRightsUrl' ),
145            $repoSettings->getSetting( 'dataRightsText' ),
146            MediaWikiServices::getInstance()->getTempUserConfig()
147        );
148    }
149
150    /**
151     * @param Article $article
152     * @param IContextSource $context
153     * @return RestoreSubmitAction|UndoSubmitAction|string
154     */
155    private function getActionOverridesSubmit(
156        Article $article,
157        IContextSource $context
158    ) {
159        $req = $context->getRequest();
160
161        if (
162            $req->getCheck( 'undo' )
163            || $req->getCheck( 'undoafter' )
164        ) {
165            return new UndoSubmitAction( $article, $context );
166        }
167
168        if ( $req->getCheck( 'restore' ) ) {
169            return new RestoreSubmitAction( $article, $context );
170        }
171
172        return EntitySchemaSubmitAction::class;
173    }
174
175    public function supportsDirectApiEditing(): bool {
176        return false;
177    }
178
179    /**
180     * Get the Content object that needs to be saved in order to undo all revisions
181     * between $undo and $undoafter. Revisions must belong to the same page,
182     * must exist and must not be deleted.
183     *
184     * @since 1.32 accepts Content objects for all parameters instead of Revision objects.
185     *  Passing Revision objects is deprecated.
186     * @since 1.37 only accepts Content objects
187     *
188     * @param Content $baseContent The current text
189     * @param Content $undoFromContent The content of the revision to undo
190     * @param Content $undoToContent Must be from an earlier revision than $undo
191     * @param bool $undoIsLatest Set true if $undo is from the current revision (since 1.32)
192     *
193     * @return Content|false
194     */
195    public function getUndoContent(
196        Content $baseContent,
197        Content $undoFromContent,
198        Content $undoToContent,
199        $undoIsLatest = false
200    ) {
201        if ( $undoIsLatest ) {
202            return $undoToContent;
203        }
204
205        // Make sure correct subclass
206        if ( !$baseContent instanceof EntitySchemaContent ||
207            !$undoFromContent instanceof EntitySchemaContent ||
208            !$undoToContent instanceof EntitySchemaContent
209        ) {
210            return false;
211        }
212
213        $undoHandler = new UndoHandler();
214        try {
215            $schemaId = $undoHandler->validateContentIds( $undoToContent, $undoFromContent, $baseContent );
216        } catch ( LogicException $e ) {
217            return false;
218        }
219
220        $diffStatus = $undoHandler->getDiffFromContents( $undoFromContent, $undoToContent );
221        if ( !$diffStatus->isOK() ) {
222            return false;
223        }
224
225        $patchStatus = $undoHandler->tryPatching( $diffStatus->getValue(), $baseContent );
226        if ( !$patchStatus->isOK() ) {
227            return false;
228        }
229        $patchedSchema = $patchStatus->getValue()->data;
230
231        return new EntitySchemaContent( EntitySchemaEncoder::getPersistentRepresentation(
232            $schemaId,
233            $patchedSchema['labels'],
234            $patchedSchema['descriptions'],
235            $patchedSchema['aliases'],
236            $patchedSchema['schemaText']
237        ) );
238    }
239
240    /**
241     * Returns true to indicate that the parser cache can be used for Schemas.
242     *
243     * @note The html representation of Schemas depends on the user language, so
244     * EntitySchemaContent::getParserOutput needs to make sure
245     * ParserOutput::recordOption( 'userlang' ) is called to split the cache by user language.
246     *
247     * @see ContentHandler::isParserCacheSupported
248     *
249     * @return bool Always true in this default implementation.
250     */
251    public function isParserCacheSupported(): bool {
252        return true;
253    }
254
255    /**
256     * @inheritDoc
257     */
258    protected function fillParserOutput(
259        Content $content,
260        ContentParseParams $cpoParams,
261        ParserOutput &$parserOutput
262    ): void {
263        '@phan-var EntitySchemaContent $content';
264        $parserOptions = $cpoParams->getParserOptions();
265        $generateHtml = $cpoParams->getGenerateHtml();
266        if ( $generateHtml && $content->isValid() ) {
267            $languageCode = $parserOptions->getUserLang();
268            $renderer = new EntitySchemaSlotViewRenderer( $languageCode );
269            $renderer->fillParserOutput(
270                ( new EntitySchemaConverter() )
271                    ->getFullViewSchemaData( $content->getText(), [ $languageCode ] ),
272                $cpoParams->getPage(),
273                $parserOutput
274            );
275        } else {
276            $parserOutput->setText( '' );
277        }
278    }
279
280}