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 IContextSource;
8use MediaWiki\Config\Config;
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 *
15 * @package GrowthExperiments\HomepageModules
16 */
17abstract 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}