Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.21% |
61 / 78 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
78.21% |
61 / 78 |
|
37.50% |
3 / 8 |
44.27 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
shouldLoadCodeMirror | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
12 | |||
conflictingGadgetsEnabled | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
42 | |||
onEditPage__showEditForm_initial | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
5.01 | |||
onEditPage__showReadOnlyForm_initial | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
shouldUseV6 | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
onResourceLoaderGetConfigVars | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onGetPreferences | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CodeMirror; |
4 | |
5 | use ExtensionRegistry; |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Config\Config; |
8 | use MediaWiki\EditPage\EditPage; |
9 | use MediaWiki\Extension\Gadgets\GadgetRepo; |
10 | use MediaWiki\Hook\EditPage__showEditForm_initialHook; |
11 | use MediaWiki\Hook\EditPage__showReadOnlyForm_initialHook; |
12 | use MediaWiki\Output\OutputPage; |
13 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
14 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook; |
15 | use MediaWiki\User\Options\UserOptionsLookup; |
16 | use MediaWiki\User\User; |
17 | |
18 | /** |
19 | * @phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName |
20 | */ |
21 | class Hooks implements |
22 | EditPage__showEditForm_initialHook, |
23 | EditPage__showReadOnlyForm_initialHook, |
24 | ResourceLoaderGetConfigVarsHook, |
25 | GetPreferencesHook |
26 | { |
27 | |
28 | private UserOptionsLookup $userOptionsLookup; |
29 | private array $conflictingGadgets; |
30 | private bool $useV6; |
31 | private bool $isSupportedRtlWiki; |
32 | private ?GadgetRepo $gadgetRepo; |
33 | |
34 | /** |
35 | * @param UserOptionsLookup $userOptionsLookup |
36 | * @param Config $config |
37 | * @param GadgetRepo|null $gadgetRepo |
38 | */ |
39 | public function __construct( |
40 | UserOptionsLookup $userOptionsLookup, |
41 | Config $config, |
42 | ?GadgetRepo $gadgetRepo |
43 | ) { |
44 | $this->userOptionsLookup = $userOptionsLookup; |
45 | $this->useV6 = $config->get( 'CodeMirrorV6' ); |
46 | $this->conflictingGadgets = $config->get( 'CodeMirrorConflictingGadgets' ); |
47 | $this->isSupportedRtlWiki = $config->get( 'CodeMirrorRTL' ); |
48 | $this->gadgetRepo = $gadgetRepo; |
49 | } |
50 | |
51 | /** |
52 | * Checks if CodeMirror for textarea wikitext editor should be loaded on this page or not. |
53 | * |
54 | * @param OutputPage $out |
55 | * @param ExtensionRegistry|null $extensionRegistry Overridden in tests. |
56 | * @return bool |
57 | */ |
58 | public function shouldLoadCodeMirror( OutputPage $out, ?ExtensionRegistry $extensionRegistry = null ): bool { |
59 | // Disable CodeMirror when CodeEditor is active on this page |
60 | // Depends on ext.codeEditor being added by \MediaWiki\EditPage\EditPage::showEditForm:initial |
61 | if ( in_array( 'ext.codeEditor', $out->getModules(), true ) ) { |
62 | return false; |
63 | } |
64 | |
65 | $shouldUseV6 = $this->shouldUseV6( $out ); |
66 | $useCodeMirror = $this->userOptionsLookup->getBoolOption( $out->getUser(), 'usecodemirror' ); |
67 | $useWikiEditor = $this->userOptionsLookup->getBoolOption( $out->getUser(), 'usebetatoolbar' ); |
68 | // Disable CodeMirror 5 when the WikiEditor toolbar is not enabled in preferences. |
69 | if ( !$shouldUseV6 && !$useWikiEditor ) { |
70 | return false; |
71 | } |
72 | // In CodeMirror 6, either WikiEditor or the 'usecodemirror' preference must be enabled. |
73 | if ( $shouldUseV6 && !$useWikiEditor && !$useCodeMirror ) { |
74 | return false; |
75 | } |
76 | |
77 | $extensionRegistry = $extensionRegistry ?: ExtensionRegistry::getInstance(); |
78 | $contentModels = $extensionRegistry->getAttribute( 'CodeMirrorContentModels' ); |
79 | $isRTL = $out->getTitle()->getPageLanguage()->isRTL(); |
80 | // Disable CodeMirror if we're on an edit page with a conflicting gadget. See T178348. |
81 | return !$this->conflictingGadgetsEnabled( $extensionRegistry, $out->getUser() ) && |
82 | // CodeMirror 5 on textarea wikitext editors doesn't support RTL (T170001) |
83 | ( !$isRTL || ( $this->shouldUseV6( $out ) && $this->isSupportedRtlWiki ) ) && |
84 | // Limit to supported content models that use wikitext. |
85 | // See https://www.mediawiki.org/wiki/Content_handlers#Extension_content_handlers |
86 | in_array( $out->getTitle()->getContentModel(), $contentModels ); |
87 | } |
88 | |
89 | /** |
90 | * @param ExtensionRegistry $extensionRegistry |
91 | * @param User $user |
92 | * @return bool |
93 | */ |
94 | private function conflictingGadgetsEnabled( ExtensionRegistry $extensionRegistry, User $user ): bool { |
95 | if ( !$extensionRegistry->isLoaded( 'Gadgets' ) || !$this->gadgetRepo ) { |
96 | return false; |
97 | } |
98 | $conflictingGadgets = array_intersect( $this->conflictingGadgets, $this->gadgetRepo->getGadgetIds() ); |
99 | foreach ( $conflictingGadgets as $conflictingGadget ) { |
100 | try { |
101 | if ( $this->gadgetRepo->getGadget( $conflictingGadget )->isEnabled( $user ) ) { |
102 | return true; |
103 | } |
104 | } catch ( InvalidArgumentException $e ) { |
105 | // Safeguard for an invalid gadget ID; treat as gadget not enabled. |
106 | continue; |
107 | } |
108 | } |
109 | return false; |
110 | } |
111 | |
112 | /** |
113 | * Load CodeMirror if necessary. |
114 | * |
115 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/EditPage::showEditForm:initial |
116 | * |
117 | * @param EditPage $editor |
118 | * @param OutputPage $out |
119 | */ |
120 | public function onEditPage__showEditForm_initial( $editor, $out ): void { |
121 | if ( !$this->shouldLoadCodeMirror( $out ) ) { |
122 | return; |
123 | } |
124 | |
125 | $useCodeMirror = $this->userOptionsLookup->getBoolOption( $out->getUser(), 'usecodemirror' ); |
126 | $useWikiEditor = $this->userOptionsLookup->getBoolOption( $out->getUser(), 'usebetatoolbar' ); |
127 | |
128 | if ( $this->shouldUseV6( $out ) ) { |
129 | $out->addModules( $useWikiEditor ? |
130 | 'ext.CodeMirror.v6.WikiEditor.init' : |
131 | 'ext.CodeMirror.v6.init' |
132 | ); |
133 | } else { |
134 | $out->addModules( 'ext.CodeMirror.WikiEditor' ); |
135 | |
136 | if ( $useCodeMirror ) { |
137 | // These modules are predelivered for performance when needed |
138 | // keep these modules in sync with ext.CodeMirror.js |
139 | $out->addModules( [ 'ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki' ] ); |
140 | } |
141 | } |
142 | } |
143 | |
144 | /** |
145 | * Load CodeMirror 6 on read-only pages. |
146 | * |
147 | * @param EditPage $editor |
148 | * @param OutputPage $out |
149 | */ |
150 | public function onEditPage__showReadOnlyForm_initial( $editor, $out ): void { |
151 | if ( $this->shouldUseV6( $out ) && $this->shouldLoadCodeMirror( $out ) ) { |
152 | $useWikiEditor = $this->userOptionsLookup->getBoolOption( $out->getUser(), 'usebetatoolbar' ); |
153 | $out->addModules( $useWikiEditor ? |
154 | 'ext.CodeMirror.v6.WikiEditor.init' : |
155 | 'ext.CodeMirror.v6.init' |
156 | ); |
157 | } |
158 | } |
159 | |
160 | /** |
161 | * @param OutputPage $out |
162 | * @return bool |
163 | * @todo Remove check for cm6enable flag after migration is complete |
164 | */ |
165 | private function shouldUseV6( OutputPage $out ): bool { |
166 | return $this->useV6 || $out->getRequest()->getRawVal( 'cm6enable' ); |
167 | } |
168 | |
169 | /** |
170 | * Hook handler for enabling bracket matching. |
171 | * |
172 | * TODO: Remove after migration to CodeMirror 6 is complete. |
173 | * |
174 | * @param array &$vars Array of variables to be added into the output of the startup module |
175 | * @param string $skin |
176 | * @param Config $config |
177 | * @return void This hook must not abort, it must return no value |
178 | */ |
179 | public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void { |
180 | $vars['wgCodeMirrorLineNumberingNamespaces'] = $config->get( 'CodeMirrorLineNumberingNamespaces' ); |
181 | } |
182 | |
183 | /** |
184 | * GetPreferences hook handler |
185 | * |
186 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences |
187 | * |
188 | * @param User $user |
189 | * @param array &$defaultPreferences |
190 | * @return bool|void True or no return value to continue or false to abort |
191 | */ |
192 | public function onGetPreferences( $user, &$defaultPreferences ) { |
193 | if ( !$this->useV6 ) { |
194 | $defaultPreferences['usecodemirror'] = [ |
195 | 'type' => 'api', |
196 | ]; |
197 | |
198 | // The following messages are generated upstream by the 'section' value |
199 | // * prefs-accessibility |
200 | $defaultPreferences['usecodemirror-colorblind'] = [ |
201 | 'type' => 'toggle', |
202 | 'label-message' => 'codemirror-prefs-colorblind', |
203 | 'help-message' => 'codemirror-prefs-colorblind-help', |
204 | 'section' => 'editing/accessibility', |
205 | ]; |
206 | return; |
207 | } |
208 | |
209 | // Show message with a link to the Help page under "Syntax highlighting". |
210 | // The following messages are generated upstream by the 'section' value: |
211 | // * prefs-syntax-highlighting |
212 | $defaultPreferences['usecodemirror-summary'] = [ |
213 | 'type' => 'info', |
214 | 'default' => wfMessage( 'codemirror-prefs-summary' )->parse(), |
215 | 'raw' => true, |
216 | 'section' => 'editing/syntax-highlighting' |
217 | ]; |
218 | |
219 | // CodeMirror is disabled by default for all users. It can enabled for everyone |
220 | // by default by adding '$wgDefaultUserOptions['usecodemirror'] = 1;' into LocalSettings.php |
221 | $defaultPreferences['usecodemirror'] = [ |
222 | 'type' => 'toggle', |
223 | 'label-message' => 'codemirror-prefs-enable', |
224 | 'section' => 'editing/syntax-highlighting', |
225 | ]; |
226 | |
227 | $defaultPreferences['usecodemirror-colorblind'] = [ |
228 | 'type' => 'toggle', |
229 | 'label-message' => 'codemirror-prefs-colorblind', |
230 | 'section' => 'editing/syntax-highlighting', |
231 | 'disable-if' => [ '!==', 'usecodemirror', '1' ] |
232 | ]; |
233 | } |
234 | } |