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