Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
MobileSpecialPage
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 12
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 executeWhenAvailable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 setHeaders
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 renderUnavailableBanner
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 addModules
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 isListed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 showPageNotFound
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getDesktopUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserOptionsLookup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserGroupManager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserFactory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\CommentFormatter\CommentFormatter;
4use MediaWiki\Config\Config;
5use MediaWiki\Html\Html;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\SpecialPage\SpecialPage;
8use MediaWiki\User\Options\UserOptionsLookup;
9use MediaWiki\User\UserFactory;
10use MediaWiki\User\UserGroupManager;
11use MobileFrontend\Hooks\HookRunner;
12
13/**
14 * Basic mobile implementation of SpecialPage to use in specific mobile special pages
15 */
16class MobileSpecialPage extends SpecialPage {
17    /** @var bool If true, the page will also be available on desktop */
18    protected $hasDesktopVersion = false;
19    /** @var bool Whether this special page should appear on Special:SpecialPages */
20    protected $listed = false;
21    /** @var Config MobileFrontend's config object */
22    protected $config = null;
23    /** @var string a message key for the error message heading that should be shown on a 404 */
24    protected $errorNotFoundTitleMsg = 'mobile-frontend-generic-404-title';
25    /** @var string a message key for the error message description that should be shown on a 404 */
26    protected $errorNotFoundDescriptionMsg = 'mobile-frontend-generic-404-desc';
27    /** @var MobileContext */
28    protected $mobileContext;
29    /** @var UserOptionsLookup */
30    protected $userOptionsLookup;
31    /** @var UserGroupManager */
32    protected $userGroupManager;
33    /** @var UserFactory */
34    protected $userFactory;
35    /** @var CommentFormatter */
36    protected $commentFormatter;
37
38    /**
39     * @param string $page
40     */
41    public function __construct( $page ) {
42        parent::__construct( $page );
43
44        $services = MediaWikiServices::getInstance();
45        $this->config = $services->getService( 'MobileFrontend.Config' );
46        $this->mobileContext = $services->getService( 'MobileFrontend.Context' );
47        $this->userOptionsLookup = $services->getUserOptionsLookup();
48        $this->userGroupManager = $services->getUserGroupManager();
49        $this->userFactory = $services->getUserFactory();
50        $this->commentFormatter = $services->getCommentFormatter();
51    }
52
53    /**
54     * Executes the page when available in the current $mode
55     * @param string|null $subPage parameter as subpage of specialpage
56     */
57    public function executeWhenAvailable( $subPage ) {
58    }
59
60    /**
61     * Checks the availability of the special page in actual mode and display the page, if available
62     * @param string|null $subPage parameter submitted as "subpage"
63     */
64    public function execute( $subPage ) {
65        $out = $this->getOutput();
66        $out->addBodyClasses( 'mw-mf-special-page' );
67        $out->setProperty( 'desktopUrl', $this->getDesktopUrl( $subPage ) );
68        if ( !$this->mobileContext->shouldDisplayMobileView() &&
69             !$this->hasDesktopVersion ) {
70            # We are not going to return any real content
71            $out->setStatusCode( 404 );
72            $this->renderUnavailableBanner( $this->msg( 'mobile-frontend-requires-mobile' )->parse() );
73        } else {
74            $this->executeWhenAvailable( $subPage );
75        }
76    }
77
78    /**
79     * Add modules to headers
80     */
81    public function setHeaders() {
82        parent::setHeaders();
83        $this->addModules();
84    }
85
86    /**
87     * Renders a banner telling the user the page is unavailable
88     *
89     * @param string $msg Message to display
90     */
91    protected function renderUnavailableBanner( $msg ) {
92        $out = $this->getOutput();
93        $out->setPageTitleMsg( $this->msg( 'mobile-frontend-requires-title' ) );
94        $out->addHTML( Html::warningBox( $msg ) );
95    }
96
97    /**
98     * Add mobile special page specific modules (styles and scripts)
99     */
100    protected function addModules() {
101        $out = $this->getOutput();
102        $rl = $out->getResourceLoader();
103        $name = $this->getName();
104        $id = strtolower( $name );
105        // FIXME: These modules should not exist in MobileFrontend, please see:
106        //  * T109277: [EPIC]: Use core watchlist code for mobile experience
107        //  * T91201 [EPIC] Accessibility settings/preferences
108        // Possible values:
109        // * mobile.special.watchlist.scripts
110        // * mobile.special.watchlist.styles
111        // * mobile.special.mobileoptions.styles
112        // * mobile.special.mobileoptions.scripts
113        $specialStyleModuleName = 'mobile.special.' . $id . '.styles';
114        $specialScriptModuleName = 'mobile.special.' . $id . '.scripts';
115
116        if ( $rl->isModuleRegistered( $specialStyleModuleName ) ) {
117            $out->addModuleStyles( [
118                // FIXME: mobile.special.styles should be replaced with mediawiki.special module
119                'mobile.special.styles',
120                $specialStyleModuleName
121            ] );
122        }
123
124        if ( $rl->isModuleRegistered( $specialScriptModuleName ) ) {
125            $out->addModules( $specialScriptModuleName );
126        }
127        $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
128        $hookRunner->onMobileSpecialPageStyles( $id, $out );
129    }
130
131    /**
132     * Returns if this page is listed on Special:SpecialPages
133     * @return bool
134     */
135    public function isListed() {
136        return $this->listed;
137    }
138
139    /**
140     * Render mobile specific error page, when special page can not be found
141     */
142    protected function showPageNotFound() {
143        $this->getOutput()->setStatusCode( 404 );
144        $this->getOutput()->addHTML(
145            MobileUI::contentElement(
146                Html::errorBox(
147                    $this->msg( $this->errorNotFoundDescriptionMsg )->text(),
148                    $this->msg( $this->errorNotFoundTitleMsg )->text()
149                )
150            )
151        );
152    }
153
154    /**
155     * When overridden in a descendant class, returns desktop URL for this special page
156     * @param string|null $subPage Subpage passed in URL
157     * @return string|null Desktop URL for this special page or null if a standard one should be used
158     */
159    public function getDesktopUrl( $subPage ) {
160        return null;
161    }
162
163    /**
164     * Get a user options lookup object.
165     * @return UserOptionsLookup
166     */
167    protected function getUserOptionsLookup(): UserOptionsLookup {
168        return $this->userOptionsLookup;
169    }
170
171    /**
172     * Get a user group manager object.
173     * @return UserGroupManager
174     */
175    protected function getUserGroupManager(): UserGroupManager {
176        return $this->userGroupManager;
177    }
178
179    /**
180     * Get a user factory object for creating UserIdentify object.
181     * @return UserFactory
182     */
183    protected function getUserFactory(): UserFactory {
184        return $this->userFactory;
185    }
186}