Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 186 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
| SpecialMobileOptions | |
0.00% |
0 / 186 |
|
0.00% |
0 / 10 |
650 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
| doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| setJsConfigVars | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| execute | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
6 | |||
| buildAMCToggle | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
2 | |||
| buildMobileUserPreferences | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
2 | |||
| contentElement | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| addSettingsForm | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
42 | |||
| getRedirectUrl | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| submitSettingsForm | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
72 | |||
| 1 | <?php |
| 2 | |
| 3 | use MediaWiki\Config\Config; |
| 4 | use MediaWiki\Deferred\DeferredUpdates; |
| 5 | use MediaWiki\Html\Html; |
| 6 | use MediaWiki\MediaWikiServices; |
| 7 | use MediaWiki\Request\WebRequest; |
| 8 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
| 9 | use MediaWiki\Title\Title; |
| 10 | use MediaWiki\User\Options\UserOptionsManager; |
| 11 | use MobileFrontend\Amc\Manager; |
| 12 | use MobileFrontend\Amc\UserMode; |
| 13 | use MobileFrontend\Features\FeaturesManager; |
| 14 | use Wikimedia\Rdbms\ReadOnlyMode; |
| 15 | |
| 16 | /** |
| 17 | * Adds a special page with mobile specific preferences |
| 18 | */ |
| 19 | class SpecialMobileOptions extends UnlistedSpecialPage { |
| 20 | /** @var bool Whether this special page has a desktop version or not */ |
| 21 | protected $hasDesktopVersion = true; |
| 22 | |
| 23 | /** |
| 24 | * Advanced Mobile Contributions mode |
| 25 | */ |
| 26 | private Manager $amc; |
| 27 | private FeaturesManager $featuresManager; |
| 28 | private UserMode $userMode; |
| 29 | private MobileContext $mobileContext; |
| 30 | |
| 31 | public function __construct( |
| 32 | private readonly UserOptionsManager $userOptionsManager, |
| 33 | private readonly ReadOnlyMode $readOnlyMode, |
| 34 | private readonly Config $config, |
| 35 | ) { |
| 36 | parent::__construct( 'MobileOptions' ); |
| 37 | $services = MediaWikiServices::getInstance(); |
| 38 | $this->amc = $services->getService( 'MobileFrontend.AMC.Manager' ); |
| 39 | $this->featuresManager = $services->getService( 'MobileFrontend.FeaturesManager' ); |
| 40 | $this->userMode = $services->getService( 'MobileFrontend.AMC.UserMode' ); |
| 41 | $this->mobileContext = $services->getService( 'MobileFrontend.Context' ); |
| 42 | } |
| 43 | |
| 44 | /** |
| 45 | * @return bool |
| 46 | */ |
| 47 | public function doesWrites() { |
| 48 | return true; |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * Set the required config for the page. |
| 53 | */ |
| 54 | public function setJsConfigVars() { |
| 55 | $this->getOutput()->addJsConfigVars( [ |
| 56 | 'wgMFEnableFontChanger' => $this->featuresManager->isFeatureAvailableForCurrentUser( |
| 57 | 'MFEnableFontChanger' |
| 58 | ), |
| 59 | ] ); |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Render the special page |
| 64 | * @param string|null $par Parameter submitted as subpage |
| 65 | */ |
| 66 | public function execute( $par = '' ) { |
| 67 | parent::execute( $par ); |
| 68 | $out = $this->getOutput(); |
| 69 | |
| 70 | $this->setHeaders(); |
| 71 | $out->addBodyClasses( 'mw-mf-special-page' ); |
| 72 | $out->addModuleStyles( [ |
| 73 | 'mobile.special.styles', |
| 74 | 'mobile.special.codex.styles', |
| 75 | 'mobile.special.mobileoptions.styles', |
| 76 | ] ); |
| 77 | $out->addModules( [ |
| 78 | 'mobile.special.mobileoptions.scripts', |
| 79 | ] ); |
| 80 | $this->setJsConfigVars(); |
| 81 | |
| 82 | $this->mobileContext->setForceMobileView( true ); |
| 83 | |
| 84 | if ( $this->getRequest()->wasPosted() ) { |
| 85 | $this->submitSettingsForm(); |
| 86 | } else { |
| 87 | $this->addSettingsForm(); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | private function buildAMCToggle(): OOUI\FieldLayout { |
| 92 | $amcToggle = new OOUI\CheckboxInputWidget( [ |
| 93 | 'name' => 'enableAMC', |
| 94 | 'infusable' => true, |
| 95 | 'selected' => $this->userMode->isEnabled(), |
| 96 | 'id' => 'enable-amc-toggle', |
| 97 | 'value' => '1', |
| 98 | ] ); |
| 99 | $layout = new OOUI\FieldLayout( |
| 100 | $amcToggle, |
| 101 | [ |
| 102 | 'label' => new OOUI\LabelWidget( [ |
| 103 | 'input' => $amcToggle, |
| 104 | 'label' => new OOUI\HtmlSnippet( |
| 105 | Html::openElement( 'div' ) . |
| 106 | Html::rawElement( 'strong', [], |
| 107 | $this->msg( 'mw-mf-amc-name' )->parse() ) . |
| 108 | Html::rawElement( 'div', [ 'class' => 'option-description' ], |
| 109 | $this->msg( 'mw-mf-amc-description' )->parse() |
| 110 | ) . |
| 111 | Html::closeElement( 'div' ) |
| 112 | ) |
| 113 | ] ), |
| 114 | 'id' => 'amc-field', |
| 115 | ] |
| 116 | ); |
| 117 | // placing links inside a label reduces usability and accessibility so |
| 118 | // append links to $layout and outside of label instead |
| 119 | // https://www.w3.org/TR/html52/sec-forms.html#example-42c5e0c5 |
| 120 | $layout->appendContent( new OOUI\HtmlSnippet( |
| 121 | Html::openElement( 'ul', [ 'class' => 'hlist option-links' ] ) . |
| 122 | Html::openElement( 'li' ) . |
| 123 | Html::rawElement( |
| 124 | 'a', |
| 125 | // phpcs:ignore Generic.Files.LineLength.TooLong |
| 126 | [ 'href' => 'https://www.mediawiki.org/wiki/Special:MyLanguage/Reading/Web/Advanced_mobile_contributions' ], |
| 127 | $this->msg( 'mobile-frontend-mobile-option-amc-learn-more' )->parse() |
| 128 | ) . |
| 129 | Html::closeElement( 'li' ) . |
| 130 | Html::openElement( 'li' ) . |
| 131 | Html::rawElement( |
| 132 | 'a', |
| 133 | // phpcs:ignore Generic.Files.LineLength.TooLong |
| 134 | [ 'href' => 'https://www.mediawiki.org/wiki/Special:MyLanguage/Talk:Reading/Web/Advanced_mobile_contributions' ], |
| 135 | $this->msg( 'mobile-frontend-mobile-option-amc-send-feedback' )->parse() |
| 136 | ) . |
| 137 | Html::closeElement( 'li' ) . |
| 138 | Html::closeElement( 'ul' ) |
| 139 | ) ); |
| 140 | return $layout; |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Builds mobile user preferences field. |
| 145 | * @return \OOUI\FieldLayout |
| 146 | */ |
| 147 | private function buildMobileUserPreferences() { |
| 148 | $spacer = new OOUI\LabelWidget( [ |
| 149 | 'name' => 'mobile_preference_spacer', |
| 150 | ] ); |
| 151 | $userPreferences = new OOUI\FieldLayout( |
| 152 | $spacer, |
| 153 | [ |
| 154 | 'label' => new OOUI\LabelWidget( [ |
| 155 | 'input' => $spacer, |
| 156 | 'label' => new OOUI\HtmlSnippet( |
| 157 | Html::openElement( 'div' ) . |
| 158 | Html::rawElement( 'strong', [], |
| 159 | $this->msg( 'mobile-frontend-user-pref-option' )->parse() ) . |
| 160 | Html::rawElement( 'div', [ 'class' => 'option-description' ], |
| 161 | $this->msg( 'mobile-frontend-user-pref-description' )->parse() |
| 162 | ) . |
| 163 | Html::closeElement( 'div' ) |
| 164 | ) |
| 165 | ] ), |
| 166 | 'id' => 'mobile-user-pref', |
| 167 | ] |
| 168 | ); |
| 169 | $userPreferences->appendContent( new OOUI\HtmlSnippet( |
| 170 | Html::openElement( 'ul', [ 'class' => 'hlist option-links' ] ) . |
| 171 | Html::openElement( 'li' ) . |
| 172 | Html::rawElement( |
| 173 | 'a', |
| 174 | [ 'href' => Title::newFromText( 'Special:Preferences' )->getLocalURL() ], |
| 175 | $this->msg( 'mobile-frontend-user-pref-link' )->parse() |
| 176 | ) . |
| 177 | Html::closeElement( 'li' ) . |
| 178 | Html::closeElement( 'ul' ) |
| 179 | ) ); |
| 180 | return $userPreferences; |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Mark some html as being content |
| 185 | * @param string $html HTML content |
| 186 | * @return string of html |
| 187 | */ |
| 188 | private static function contentElement( $html ) { |
| 189 | return Html::rawElement( 'div', [ |
| 190 | 'class' => 'content' |
| 191 | ], $html ); |
| 192 | } |
| 193 | |
| 194 | /** |
| 195 | * Render the settings form (with actual set settings) and add it to the |
| 196 | * output as well as any supporting modules. |
| 197 | */ |
| 198 | private function addSettingsForm() { |
| 199 | $out = $this->getOutput(); |
| 200 | $user = $this->getUser(); |
| 201 | $isTemp = $user->isTemp(); |
| 202 | |
| 203 | $out->setPageTitleMsg( $this->msg( 'mobile-frontend-main-menu-settings-heading' ) ); |
| 204 | $out->enableOOUI(); |
| 205 | |
| 206 | if ( $this->getRequest()->getCheck( 'success' ) ) { |
| 207 | $out->wrapWikiMsg( |
| 208 | self::contentElement( |
| 209 | Html::successBox( |
| 210 | $this->msg( 'savedprefs' )->parse(), |
| 211 | 'mw-mf-mobileoptions-message' |
| 212 | ) |
| 213 | ) |
| 214 | ); |
| 215 | } |
| 216 | |
| 217 | $fields = []; |
| 218 | $form = new OOUI\FormLayout( [ |
| 219 | 'method' => 'POST', |
| 220 | 'id' => 'mobile-options', |
| 221 | 'action' => $this->getPageTitle()->getLocalURL(), |
| 222 | ] ); |
| 223 | $form->addClasses( [ 'mw-mf-settings' ] ); |
| 224 | |
| 225 | if ( $this->amc->isAvailable() && !$isTemp ) { |
| 226 | $fields[] = $this->buildAMCToggle(); |
| 227 | } |
| 228 | |
| 229 | $fields[] = new OOUI\ButtonInputWidget( [ |
| 230 | 'id' => 'mw-mf-settings-save', |
| 231 | 'infusable' => true, |
| 232 | 'value' => $this->msg( 'mobile-frontend-save-settings' )->text(), |
| 233 | 'label' => $this->msg( 'mobile-frontend-save-settings' )->text(), |
| 234 | 'flags' => [ 'primary', 'progressive' ], |
| 235 | 'type' => 'submit', |
| 236 | ] ); |
| 237 | |
| 238 | if ( $user->isRegistered() && !$isTemp ) { |
| 239 | $fields[] = new OOUI\HiddenInputWidget( [ 'name' => 'token', |
| 240 | 'value' => $user->getEditToken() ] ); |
| 241 | // Special:Preferences link (https://phabricator.wikimedia.org/T327506) |
| 242 | $fields[] = $this->buildMobileUserPreferences(); |
| 243 | } |
| 244 | |
| 245 | $form->appendContent( |
| 246 | ...$fields |
| 247 | ); |
| 248 | $out->addHTML( $form ); |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * @param WebRequest $request |
| 253 | * @return string url to redirect to |
| 254 | */ |
| 255 | private function getRedirectUrl( WebRequest $request ) { |
| 256 | $returnTo = $request->getText( 'returnto' ); |
| 257 | if ( $returnTo !== '' ) { |
| 258 | $title = Title::newFromText( $returnTo ); |
| 259 | |
| 260 | if ( $title !== null ) { |
| 261 | return $title->getFullURL( $request->getText( 'returntoquery' ) ); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | return $this->mobileContext->getMobileUrl( |
| 266 | $this->getPageTitle()->getFullURL( 'success' ) |
| 267 | ); |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Saves the settings submitted by the settings form |
| 272 | */ |
| 273 | private function submitSettingsForm() { |
| 274 | $request = $this->getRequest(); |
| 275 | $user = $this->getUser(); |
| 276 | |
| 277 | if ( $user->isRegistered() && !$user->matchEditToken( $request->getVal( 'token' ) ) ) { |
| 278 | $errorText = __METHOD__ . '(): token mismatch'; |
| 279 | wfDebugLog( 'mobile', $errorText ); |
| 280 | $this->getOutput()->addHTML( |
| 281 | Html::errorBox( |
| 282 | $this->msg( "mobile-frontend-save-error" )->parse() |
| 283 | ) |
| 284 | ); |
| 285 | $this->addSettingsForm(); |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | $enableAMC = $request->getBool( 'enableAMC' ); |
| 290 | |
| 291 | if ( $this->amc->isAvailable() ) { |
| 292 | $this->userMode->setEnabled( $enableAMC ); |
| 293 | } |
| 294 | |
| 295 | DeferredUpdates::addCallableUpdate( function () use ( $enableAMC ) { |
| 296 | if ( $this->readOnlyMode->isReadOnly() ) { |
| 297 | return; |
| 298 | } |
| 299 | |
| 300 | $user = $this->getUser(); |
| 301 | if ( !$user->isNamed() ) { |
| 302 | // The user is anon, temp user or could not be loaded from the database. |
| 303 | return; |
| 304 | } |
| 305 | |
| 306 | if ( $this->amc->isAvailable() ) { |
| 307 | $this->userOptionsManager->setOption( |
| 308 | $user, |
| 309 | UserMode::USER_OPTION_MODE_AMC, |
| 310 | $enableAMC ? UserMode::OPTION_ENABLED : UserMode::OPTION_DISABLED |
| 311 | ); |
| 312 | } |
| 313 | $this->userOptionsManager->saveOptions( $user ); |
| 314 | }, DeferredUpdates::PRESEND ); |
| 315 | |
| 316 | $this->getOutput()->redirect( $this->getRedirectUrl( $request ) ); |
| 317 | } |
| 318 | } |