Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MobileFrontendEditorHooks
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 5
380
0.00% covered (danger)
0.00%
0 / 1
 getContentLanguageMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getResourceLoaderMFConfigVars
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 onMakeGlobalVariablesScript
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 onCustomEditor
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
42
 isSupportedEditRequest
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2
3use MediaWiki\Config\Config;
4use MediaWiki\Context\IContextSource;
5use MediaWiki\Hook\CustomEditorHook;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\Output\Hook\MakeGlobalVariablesScriptHook;
8use MediaWiki\Output\OutputPage;
9use MediaWiki\Page\Article;
10use MediaWiki\Registration\ExtensionRegistry;
11use MediaWiki\ResourceLoader\Context;
12use MediaWiki\User\User;
13
14class MobileFrontendEditorHooks implements
15    CustomEditorHook,
16    MakeGlobalVariablesScriptHook
17{
18
19    /**
20     * Return messages in content language, for use in a ResourceLoader module.
21     *
22     * @param Context $context
23     * @param Config $config
24     * @param array $messagesKeys
25     * @return array
26     */
27    public static function getContentLanguageMessages(
28        Context $context, Config $config, array $messagesKeys = []
29    ): array {
30        return array_combine(
31            $messagesKeys,
32            array_map( static function ( $key ) {
33                return wfMessage( $key )->inContentLanguage()->text();
34            }, $messagesKeys )
35        );
36    }
37
38    /**
39     * Generate config for usage inside MobileFrontend
40     * This should be used for variables which:
41     *  - vary with the html
42     *  - variables that should work cross skin including anonymous users
43     *  - used for both, stable and beta mode (don't use
44     *    MobileContext::isBetaGroupMember in this function - T127860)
45     *
46     * @return array
47     */
48    public static function getResourceLoaderMFConfigVars() {
49        $config = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Config' );
50
51        return [
52            'wgMFDefaultEditor' => $config->get( 'MFDefaultEditor' ),
53            'wgMFFallbackEditor' => $config->get( 'MFFallbackEditor' ),
54            'wgMFEnableVEWikitextEditor' => $config->get( 'MFEnableVEWikitextEditor' ),
55        ];
56    }
57
58    /**
59     * Handler for MakeGlobalVariablesScript hook.
60     * For values that depend on the current page, user or request state.
61     *
62     * @see https://www.mediawiki.org/wiki/Manual:Hooks/MakeGlobalVariablesScript
63     * @param array &$vars Variables to be added into the output
64     * @param OutputPage $out OutputPage instance calling the hook
65     */
66    public function onMakeGlobalVariablesScript( &$vars, $out ): void {
67        /** @var MobileContext $mobileContext */
68        $mobileContext = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' );
69        $config = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Config' );
70
71        if ( $mobileContext->shouldDisplayMobileView() ) {
72            // mobile.init
73            $vars['wgMFIsSupportedEditRequest'] = self::isSupportedEditRequest( $out->getContext() );
74            $vars['wgMFScriptPath'] = $config->get( 'MFScriptPath' );
75        }
76    }
77
78    /**
79     * Decide whether to bother showing the wikitext editor at all.
80     * If not, we expect the editor initialisation JS to activate.
81     *
82     * @param Article $article The article being viewed.
83     * @param User $user The user-specific settings.
84     * @return bool Whether to show the wikitext editor or not.
85     */
86    public function onCustomEditor( $article, $user ) {
87        $req = $article->getContext()->getRequest();
88        $title = $article->getTitle();
89        if (
90            !$req->getVal( 'mfnoscript' ) &&
91            self::isSupportedEditRequest( $article->getContext() )
92        ) {
93            $params = $req->getValues();
94            $params['mfnoscript'] = '1';
95            $url = wfScript() . '?' . wfArrayToCgi( $params );
96            $escapedUrl = htmlspecialchars( $url );
97
98            $out = $article->getContext()->getOutput();
99            $titleMsg = $title->exists() ? 'editing' : 'creating';
100            $out->setPageTitleMsg( wfMessage( $titleMsg, $title->getPrefixedText() ) );
101
102            $msgParams = [];
103            if ( $title->inNamespace( NS_FILE ) && !$title->exists() ) {
104                // Is a new file page (enable upload image only) T60311
105                $msg = 'mobile-frontend-editor-uploadenable';
106            } else {
107                $msg = 'mobile-frontend-editor-toload';
108                $urlUtils = MediaWikiServices::getInstance()->getUrlUtils();
109                $msgParams[] = $urlUtils->expand( $url, PROTO_CURRENT );
110            }
111            // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Only null for invalid URL, shouldn't happen
112            $out->showPendingTakeover( $url, $msg, ...$msgParams );
113
114            $out->setRevisionId( $req->getInt( 'oldid', $article->getRevIdFetched() ) );
115            return false;
116        }
117        return true;
118    }
119
120    /**
121     * Whether the custom editor override should occur
122     *
123     * @param IContextSource $context
124     * @return bool Whether the frontend JS should try to display an editor
125     */
126    protected static function isSupportedEditRequest( IContextSource $context ) {
127        /** @var MobileContext $mobileContext */
128        $mobileContext = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' );
129        if ( !$mobileContext->shouldDisplayMobileView() ) {
130            return false;
131        }
132
133        $extensionRegistry = ExtensionRegistry::getInstance();
134        $editorAvailableSkins = $extensionRegistry->getAttribute( 'MobileFrontendEditorAvailableSkins' );
135        if ( !in_array( $context->getSkin()->getSkinName(), $editorAvailableSkins ) ) {
136            // Mobile editor commonly doesn't work well with other skins than Minerva (it looks horribly
137            // broken without some styles that are only defined by Minerva). So we only enable it for the
138            // skin that wants it.
139            return false;
140        }
141
142        $req = $context->getRequest();
143        $title = $context->getTitle();
144
145        // Various things fall back to WikiEditor
146        if ( $req->getRawVal( 'action' ) === 'submit' ) {
147            // Don't try to take over if the form has already been submitted
148            return false;
149        }
150        if ( $title->inNamespace( NS_SPECIAL ) ) {
151            return false;
152        }
153        if ( $title->getContentModel() !== 'wikitext' ) {
154            // Only load the wikitext editor on wikitext. Otherwise we'll rely on the fallback behaviour
155            // (You can test this on MediaWiki:Common.css) ?action=edit url (T173800)
156            return false;
157        }
158        if ( $req->getCheck( 'undo' ) || $req->getCheck( 'undoafter' ) ) {
159            // Undo needs to show a diff above the editor
160            return false;
161        }
162        if ( $req->getRawVal( 'section' ) === 'new' ) {
163            // New sections need a title field
164            return false;
165        }
166        return true;
167    }
168
169}