Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 64 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
BaseModule | |
0.00% |
0 / 64 |
|
0.00% |
0 / 14 |
420 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
setPageURL | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageURL | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
shouldWrapModuleWithLink | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getJsData | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
renderMobileDetailsForOverlay | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getGrowthWikiConfig | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getModuleStyles | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getState | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getActionData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
buildModuleWrapper | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
20 | |||
outputDependencies | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getModuleRoute | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUserVariant | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\HomepageModules; |
4 | |
5 | use GrowthExperiments\DashboardModule\DashboardModule; |
6 | use GrowthExperiments\ExperimentUserManager; |
7 | use IContextSource; |
8 | use MediaWiki\Config\Config; |
9 | use MediaWiki\Html\Html; |
10 | |
11 | /** |
12 | * BaseModule is a base class for homepage modules. |
13 | * It provides utilities and a default structure (header, subheader, body, footer). |
14 | * |
15 | * @package GrowthExperiments\HomepageModules |
16 | */ |
17 | abstract class BaseModule extends DashboardModule { |
18 | |
19 | protected const BASE_CSS_CLASS = 'growthexperiments-homepage-module'; |
20 | protected const MODULE_STATE_COMPLETE = 'complete'; |
21 | protected const MODULE_STATE_INCOMPLETE = 'incomplete'; |
22 | protected const MODULE_STATE_ACTIVATED = 'activated'; |
23 | protected const MODULE_STATE_UNACTIVATED = 'unactivated'; |
24 | protected const MODULE_STATE_NOEMAIL = 'noemail'; |
25 | protected const MODULE_STATE_UNCONFIRMED = 'unconfirmed'; |
26 | protected const MODULE_STATE_CONFIRMED = 'confirmed'; |
27 | protected const MODULE_STATE_NOTRENDERED = 'notrendered'; |
28 | |
29 | /** |
30 | * @var ExperimentUserManager |
31 | */ |
32 | private $experimentUserManager; |
33 | |
34 | /** @var Config */ |
35 | private $wikiConfig; |
36 | |
37 | /** |
38 | * @var bool |
39 | */ |
40 | private $shouldWrapModuleWithLink; |
41 | |
42 | /** |
43 | * @var string |
44 | */ |
45 | private $pageURL = null; |
46 | |
47 | /** |
48 | * @param string $name Name of the module |
49 | * @param IContextSource $ctx |
50 | * @param Config $wikiConfig |
51 | * @param ExperimentUserManager $experimentUserManager |
52 | * @param bool $shouldWrapModuleWithLink |
53 | */ |
54 | public function __construct( |
55 | $name, |
56 | IContextSource $ctx, |
57 | Config $wikiConfig, |
58 | ExperimentUserManager $experimentUserManager, |
59 | bool $shouldWrapModuleWithLink = true |
60 | ) { |
61 | parent::__construct( $name, $ctx ); |
62 | |
63 | $this->wikiConfig = $wikiConfig; |
64 | $this->experimentUserManager = $experimentUserManager; |
65 | $this->shouldWrapModuleWithLink = $shouldWrapModuleWithLink; |
66 | } |
67 | |
68 | /** |
69 | * Sets the page base URL where the module is being rendered. |
70 | * Can be later used for generating links from inside the module. |
71 | * @param string $url |
72 | */ |
73 | public function setPageURL( string $url ): void { |
74 | $this->pageURL = $url; |
75 | } |
76 | |
77 | /** |
78 | * Gets the base page URL where the module is being rendered. |
79 | * @return string|null |
80 | */ |
81 | public function getPageURL(): ?string { |
82 | return $this->pageURL; |
83 | } |
84 | |
85 | /** |
86 | * Gets whether the module will be wrapped in a link to its |
87 | * full screen view or not |
88 | * @return bool |
89 | */ |
90 | public function shouldWrapModuleWithLink(): bool { |
91 | return $this->shouldWrapModuleWithLink; |
92 | } |
93 | |
94 | /** |
95 | * Get an array of data needed by the Javascript code related to this module. |
96 | * The data will be available in the 'homepagemodules' JS configuration field, keyed by module name. |
97 | * Keys currently in use: |
98 | * - html: module HTML |
99 | * - overlay: mobile overlay HTML |
100 | * - rlModules: ResourceLoader modules this module depends on |
101 | * - heading: module header text |
102 | * 'html' is only present when the module supports dynamic loading, 'overlay' and 'heading' |
103 | * in mobile summary/overlay mode, and 'rlModules' in both cases. |
104 | * |
105 | * @param string $mode One of RENDER_DESKTOP, RENDER_MOBILE_SUMMARY, RENDER_MOBILE_DETAILS |
106 | * @return array |
107 | */ |
108 | public function getJsData( $mode ) { |
109 | if ( !$this->supports( $mode ) ) { |
110 | return []; |
111 | } |
112 | |
113 | $data = []; |
114 | if ( $this->canRender() |
115 | && $mode == self::RENDER_MOBILE_SUMMARY |
116 | ) { |
117 | $this->setMode( self::RENDER_MOBILE_DETAILS_OVERLAY ); |
118 | $data = [ |
119 | 'overlay' => $this->renderMobileDetailsForOverlay(), |
120 | 'rlModules' => $this->getModules(), |
121 | 'heading' => $this->getHeaderText(), |
122 | ]; |
123 | } |
124 | $this->setMode( $mode ); |
125 | $data[ 'renderMode' ] = $mode; |
126 | return $data; |
127 | } |
128 | |
129 | /** |
130 | * @return string HTML rendering for overlay. Same as mobile details but without header. |
131 | */ |
132 | protected function renderMobileDetailsForOverlay() { |
133 | return $this->buildModuleWrapper( |
134 | $this->buildSection( 'subheader', $this->getSubheader(), $this->getSubheaderTag() ), |
135 | $this->buildSection( 'body', $this->getBody() ), |
136 | $this->buildSection( 'footer', $this->getFooter() ) |
137 | ); |
138 | } |
139 | |
140 | /** |
141 | * @return Config |
142 | */ |
143 | final protected function getGrowthWikiConfig(): Config { |
144 | return $this->wikiConfig; |
145 | } |
146 | |
147 | /** |
148 | * @inheritDoc |
149 | */ |
150 | protected function getModuleStyles() { |
151 | return [ 'oojs-ui.styles.icons-movement' ]; |
152 | } |
153 | |
154 | /** |
155 | * Override this function to provide the state of this module. It will |
156 | * be included in 'state' for all HomepageModule events. |
157 | * |
158 | * @return string |
159 | */ |
160 | public function getState() { |
161 | return ''; |
162 | } |
163 | |
164 | /** |
165 | * Override this function to provide the action data of this module. It will |
166 | * be included in 'action_data' for HomepageModule events. |
167 | * |
168 | * @return array |
169 | */ |
170 | protected function getActionData() { |
171 | return []; |
172 | } |
173 | |
174 | /** |
175 | * @inheritDoc |
176 | */ |
177 | protected function buildModuleWrapper( ...$sections ) { |
178 | $moduleContent = Html::rawElement( |
179 | 'div', |
180 | [ |
181 | 'class' => array_merge( [ |
182 | self::BASE_CSS_CLASS, |
183 | self::BASE_CSS_CLASS . '-' . $this->name, |
184 | self::BASE_CSS_CLASS . '-' . $this->getMode(), |
185 | self::BASE_CSS_CLASS . '-user-variant-' . $this->getUserVariant() |
186 | ], $this->getCssClasses() ), |
187 | 'data-module-name' => $this->name, |
188 | 'data-mode' => $this->getMode(), |
189 | ], |
190 | implode( "\n", $sections ) |
191 | ); |
192 | |
193 | if ( |
194 | $this->getMode() === self::RENDER_MOBILE_SUMMARY && |
195 | $this->supports( self::RENDER_MOBILE_DETAILS ) && |
196 | $this->shouldWrapModuleWithLink() |
197 | ) { |
198 | return Html::rawElement( 'a', [ |
199 | 'href' => $this->getPageURL() . '/' . $this->getName(), |
200 | 'data-overlay-route' => $this->getModuleRoute() |
201 | ], $moduleContent ); |
202 | } |
203 | |
204 | return $moduleContent; |
205 | } |
206 | |
207 | protected function outputDependencies() { |
208 | parent::outputDependencies(); |
209 | |
210 | $out = $this->getContext()->getOutput(); |
211 | $out->addModuleStyles( [ |
212 | 'ext.growthExperiments.Homepage.styles', |
213 | 'ext.growthExperiments.icons' |
214 | ] ); |
215 | $out->addJsConfigVars( [ |
216 | 'wgGEHomepageModuleState-' . $this->getName() => $this->getState(), |
217 | 'wgGEHomepageModuleActionData-' . $this->getName() => $this->getActionData(), |
218 | ] ); |
219 | } |
220 | |
221 | /** |
222 | * The component for mw.router to use when routing clicks from mobile |
223 | * summary HTML. If this is an empty string, no routing occurs. |
224 | * |
225 | * @return string |
226 | */ |
227 | protected function getModuleRoute(): string { |
228 | return '#/homepage/' . $this->name; |
229 | } |
230 | |
231 | /** |
232 | * @return string |
233 | */ |
234 | private function getUserVariant(): string { |
235 | return $this->experimentUserManager->getVariant( $this->getContext()->getUser() ); |
236 | } |
237 | |
238 | } |