Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
61.36% |
27 / 44 |
|
25.00% |
2 / 8 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
61.36% |
27 / 44 |
|
25.00% |
2 / 8 |
57.22 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
shouldLoadCodeMirror | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
7.04 | |||
conflictingGadgetsEnabled | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
onEditPage__showEditForm_initial | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
onEditPage__showReadOnlyForm_initial | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
shouldUseV6 | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
onResourceLoaderGetConfigVars | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onGetPreferences | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 |
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 | |
32 | /** |
33 | * @param UserOptionsLookup $userOptionsLookup |
34 | * @param Config $config |
35 | */ |
36 | public function __construct( |
37 | UserOptionsLookup $userOptionsLookup, |
38 | Config $config |
39 | ) { |
40 | $this->userOptionsLookup = $userOptionsLookup; |
41 | $this->useV6 = $config->get( 'CodeMirrorV6' ); |
42 | $this->conflictingGadgets = $config->get( 'CodeMirrorConflictingGadgets' ); |
43 | } |
44 | |
45 | /** |
46 | * Checks if CodeMirror for textarea wikitext editor should be loaded on this page or not. |
47 | * |
48 | * @param OutputPage $out |
49 | * @param ExtensionRegistry|null $extensionRegistry Overridden in tests. |
50 | * @return bool |
51 | */ |
52 | public function shouldLoadCodeMirror( OutputPage $out, ?ExtensionRegistry $extensionRegistry = null ): bool { |
53 | // Disable CodeMirror when CodeEditor is active on this page |
54 | // Depends on ext.codeEditor being added by \MediaWiki\EditPage\EditPage::showEditForm:initial |
55 | if ( in_array( 'ext.codeEditor', $out->getModules(), true ) ) { |
56 | return false; |
57 | } |
58 | // Disable CodeMirror when the WikiEditor toolbar is not enabled in preferences |
59 | if ( !$this->userOptionsLookup->getOption( $out->getUser(), 'usebetatoolbar' ) ) { |
60 | return false; |
61 | } |
62 | $extensionRegistry = $extensionRegistry ?: ExtensionRegistry::getInstance(); |
63 | $contentModels = $extensionRegistry->getAttribute( 'CodeMirrorContentModels' ); |
64 | $isRTL = $out->getTitle()->getPageLanguage()->isRTL(); |
65 | // Disable CodeMirror if we're on an edit page with a conflicting gadget. See T178348. |
66 | return !$this->conflictingGadgetsEnabled( $extensionRegistry, $out->getUser() ) && |
67 | // CodeMirror 5 on textarea wikitext editors doesn't support RTL (T170001) |
68 | ( !$isRTL || $this->shouldUseV6( $out ) ) && |
69 | // Limit to supported content models that use wikitext. |
70 | // See https://www.mediawiki.org/wiki/Content_handlers#Extension_content_handlers |
71 | in_array( $out->getTitle()->getContentModel(), $contentModels ); |
72 | } |
73 | |
74 | /** |
75 | * @param ExtensionRegistry $extensionRegistry |
76 | * @param User $user |
77 | * @return bool |
78 | */ |
79 | private function conflictingGadgetsEnabled( ExtensionRegistry $extensionRegistry, User $user ): bool { |
80 | if ( !$extensionRegistry->isLoaded( 'Gadgets' ) ) { |
81 | return false; |
82 | } |
83 | // @phan-suppress-next-line PhanUndeclaredClassMethod Code path won't be followed if class doesn't exist. |
84 | $gadgetRepo = GadgetRepo::singleton(); |
85 | $conflictingGadgets = array_intersect( $this->conflictingGadgets, $gadgetRepo->getGadgetIds() ); |
86 | foreach ( $conflictingGadgets as $conflictingGadget ) { |
87 | try { |
88 | if ( $gadgetRepo->getGadget( $conflictingGadget )->isEnabled( $user ) ) { |
89 | return true; |
90 | } |
91 | } catch ( InvalidArgumentException $e ) { |
92 | // Safeguard for an invalid gadget ID; treat as gadget not enabled. |
93 | continue; |
94 | } |
95 | } |
96 | return false; |
97 | } |
98 | |
99 | /** |
100 | * Load CodeMirror if necessary. |
101 | * |
102 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/EditPage::showEditForm:initial |
103 | * |
104 | * @param EditPage $editor |
105 | * @param OutputPage $out |
106 | */ |
107 | public function onEditPage__showEditForm_initial( $editor, $out ): void { |
108 | if ( !$this->shouldLoadCodeMirror( $out ) ) { |
109 | return; |
110 | } |
111 | |
112 | if ( $this->shouldUseV6( $out ) ) { |
113 | $out->addModules( 'ext.CodeMirror.v6.WikiEditor' ); |
114 | } else { |
115 | $out->addModules( 'ext.CodeMirror.WikiEditor' ); |
116 | |
117 | if ( $this->userOptionsLookup->getOption( $out->getUser(), 'usecodemirror' ) ) { |
118 | // These modules are predelivered for performance when needed |
119 | // keep these modules in sync with ext.CodeMirror.js |
120 | $out->addModules( [ 'ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki' ] ); |
121 | } |
122 | } |
123 | } |
124 | |
125 | /** |
126 | * Load CodeMirror 6 on read-only pages. |
127 | * |
128 | * @param EditPage $editor |
129 | * @param OutputPage $out |
130 | */ |
131 | public function onEditPage__showReadOnlyForm_initial( $editor, $out ): void { |
132 | if ( $this->shouldUseV6( $out ) && $this->shouldLoadCodeMirror( $out ) ) { |
133 | $out->addModules( 'ext.CodeMirror.v6.WikiEditor' ); |
134 | } |
135 | } |
136 | |
137 | /** |
138 | * @param OutputPage $out |
139 | * @return bool |
140 | * @todo Remove check for cm6enable flag after migration is complete |
141 | */ |
142 | private function shouldUseV6( OutputPage $out ): bool { |
143 | return $this->useV6 || $out->getRequest()->getRawVal( 'cm6enable' ); |
144 | } |
145 | |
146 | /** |
147 | * Hook handler for enabling bracket matching. |
148 | * |
149 | * TODO: Remove after migration to CodeMirror 6 is complete. |
150 | * |
151 | * @param array &$vars Array of variables to be added into the output of the startup module |
152 | * @param string $skin |
153 | * @param Config $config |
154 | * @return void This hook must not abort, it must return no value |
155 | */ |
156 | public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void { |
157 | $vars['wgCodeMirrorLineNumberingNamespaces'] = $config->get( 'CodeMirrorLineNumberingNamespaces' ); |
158 | } |
159 | |
160 | /** |
161 | * GetPreferences hook handler |
162 | * |
163 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences |
164 | * |
165 | * @param User $user |
166 | * @param array &$defaultPreferences |
167 | * @return bool|void True or no return value to continue or false to abort |
168 | */ |
169 | public function onGetPreferences( $user, &$defaultPreferences ) { |
170 | // CodeMirror is disabled by default for all users. It can enabled for everyone |
171 | // by default by adding '$wgDefaultUserOptions['usecodemirror'] = 1;' into LocalSettings.php |
172 | $defaultPreferences['usecodemirror'] = [ |
173 | 'type' => 'api', |
174 | ]; |
175 | |
176 | // The following messages are generated upstream by the 'section' value |
177 | // * prefs-accessibility |
178 | $defaultPreferences['usecodemirror-colorblind'] = [ |
179 | 'type' => 'toggle', |
180 | 'label-message' => 'codemirror-prefs-colorblind', |
181 | 'help-message' => 'codemirror-prefs-colorblind-help', |
182 | 'section' => 'editing/accessibility', |
183 | ]; |
184 | } |
185 | } |