Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
JsonSchemaHooks
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 6
420
0.00% covered (danger)
0.00%
0 / 1
 isSchemaNamespaceEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onApiMain__moduleManager
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 onCodeEditorGetPageLanguage
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 onEditFilterMergedContent
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 onMovePageIsValidMove
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Hooks for managing JSON Schema namespace and content model.
4 *
5 * @file
6 * @ingroup Extensions
7 * @ingroup EventLogging
8 *
9 * @author Ori Livneh <ori@wikimedia.org>
10 */
11
12// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
13
14namespace MediaWiki\Extension\EventLogging;
15
16use MediaWiki\Api\ApiModuleManager;
17use MediaWiki\Api\Hook\ApiMain__moduleManagerHook;
18use MediaWiki\Content\Content;
19use MediaWiki\Context\IContextSource;
20use MediaWiki\EditPage\EditPage;
21use MediaWiki\Extension\EventLogging\Libs\JsonSchemaValidation\JsonSchemaException;
22use MediaWiki\Hook\EditFilterMergedContentHook;
23use MediaWiki\Hook\MovePageIsValidMoveHook;
24use MediaWiki\Output\Hook\BeforePageDisplayHook;
25use MediaWiki\Output\OutputPage;
26use MediaWiki\Status\Status;
27use MediaWiki\Title\Title;
28use MediaWiki\User\User;
29use Skin;
30
31class JsonSchemaHooks implements
32    BeforePageDisplayHook,
33    EditFilterMergedContentHook,
34    MovePageIsValidMoveHook,
35    ApiMain__moduleManagerHook
36{
37
38    /**
39     * Convenience function to determine whether the
40     * Schema namespace is enabled
41     *
42     * @return bool
43     */
44    public static function isSchemaNamespaceEnabled() {
45        global $wgEventLoggingDBname, $wgDBname;
46
47        return $wgEventLoggingDBname === $wgDBname;
48    }
49
50    /**
51     * ApiMain::moduleManager hook to register jsonschema
52     * API module only if the Schema namespace is enabled
53     *
54     * @param ApiModuleManager $moduleManager
55     */
56    public function onApiMain__moduleManager( $moduleManager ): void {
57        if ( self::isSchemaNamespaceEnabled() ) {
58            $moduleManager->addModule(
59                'jsonschema',
60                'action',
61                ApiJsonSchema::class
62            );
63        }
64    }
65
66    /**
67     * Declares JSON as the code editor language for Schema: pages.
68     * This hook only runs if the CodeEditor extension is enabled.
69     *
70     * @param Title $title
71     * @param string &$lang Page language.
72     */
73    public static function onCodeEditorGetPageLanguage( $title, &$lang ): void {
74        if ( self::isSchemaNamespaceEnabled()
75            && $title->inNamespace( NS_SCHEMA )
76        ) {
77            $lang = 'json';
78        }
79    }
80
81    /**
82     * Validates that the revised contents are valid JSON.
83     * If not valid, rejects edit with error message.
84     *
85     * @param IContextSource $context
86     * @param Content $content
87     * @param Status $status
88     * @param string $summary
89     * @param User $user
90     * @param bool $minoredit
91     * @return bool
92     */
93    public function onEditFilterMergedContent(
94        IContextSource $context,
95        Content $content,
96        Status $status,
97        $summary,
98        User $user,
99        $minoredit
100    ): bool {
101        $title = $context->getTitle();
102
103        if ( !self::isSchemaNamespaceEnabled()
104            || !$title->inNamespace( NS_SCHEMA )
105        ) {
106            return true;
107        }
108
109        if ( !preg_match( '/^[a-zA-Z0-9_-]{1,63}$/', $title->getText() ) ) {
110            $status->fatal( 'badtitle' );
111            // @todo Remove this line after this extension do not support mediawiki version 1.36 and before
112            $status->value = EditPage::AS_HOOK_ERROR_EXPECTED;
113            return false;
114        }
115
116        if ( !$content instanceof JsonSchemaContent ) {
117            return true;
118        }
119
120        try {
121            $content->validate();
122            return true;
123        } catch ( JsonSchemaException $e ) {
124            $status->fatal( $context->msg( $e->getCode(), $e->args ) );
125            // @todo Remove this line after this extension do not support mediawiki version 1.36 and before
126            $status->value = EditPage::AS_HOOK_ERROR_EXPECTED;
127            return false;
128        }
129    }
130
131    /**
132     * Add the revision id as the subtitle on NS_SCHEMA pages.
133     *
134     * @param OutputPage $out
135     * @param Skin $skin
136     */
137    public function onBeforePageDisplay( $out, $skin ): void {
138        $title = $out->getTitle();
139        $revId = $out->getRevisionId();
140
141        if ( self::isSchemaNamespaceEnabled()
142            && $title->inNamespace( NS_SCHEMA )
143            && $revId !== null
144        ) {
145            $out->addSubtitle( $out->msg( 'eventlogging-revision-id' )
146                // We use 'rawParams' rather than 'numParams' to make it
147                // easy to copy/paste the value into code.
148                ->rawParams( $revId )
149                ->escaped() );
150        }
151    }
152
153    /**
154     * Prohibit moving (renaming) Schema pages, as doing so violates
155     * immutability guarantees.
156     *
157     * @param Title $currentTitle
158     * @param Title $newTitle
159     * @param Status $status
160     * @return bool
161     */
162    public function onMovePageIsValidMove(
163        $currentTitle, $newTitle, $status
164    ) {
165        if ( !self::isSchemaNamespaceEnabled() ) {
166            // Namespace isn't even enabled
167            return true;
168        } elseif ( $currentTitle->inNamespace( NS_SCHEMA ) ) {
169            $status->fatal( 'eventlogging-error-move-source' );
170            return false;
171        } elseif ( $newTitle->inNamespace( NS_SCHEMA ) ) {
172            $status->fatal( 'eventlogging-error-move-destination' );
173            return false;
174        }
175        return true;
176    }
177}