wikimedia/mediawiki-extensions-Translate

View on GitHub
src/PageTranslation/ManageMessageGroupSubscriptionsSpecialPage.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
declare( strict_types = 1 );

namespace MediaWiki\Extension\Translate\PageTranslation;

use EditWatchlistCheckboxSeriesField;
use EditWatchlistNormalHTMLForm;
use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups;
use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupSubscription;
use MediaWiki\Html\Html;
use MediaWiki\HTMLForm\HTMLForm;
use MediaWiki\HTMLForm\OOUIHTMLForm;
use MediaWiki\Parser\Parser;
use MediaWiki\Parser\ParserOutput;
use MediaWiki\Parser\ParserOutputFlags;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\SpecialPage\UnlistedSpecialPage;
use MediaWiki\Status\Status;
use MessageGroup;
use UserNotLoggedIn;

/**
 * Allows users to manage message group subscriptions.
 *
 * @ingroup SpecialPage
 * @author Eugene Wang'ombe
 * @license GPL-2.0-or-later
 */
class ManageMessageGroupSubscriptionsSpecialPage extends UnlistedSpecialPage {
    private const EDIT_RAW = 'raw';
    private const NS_INVALID = 99999;

    private const WATCHLIST_TAB_PATHS = [
        'Special:ManageMessageGroupSubscriptions',
        'Special:ManageMessageGroupSubscriptions/raw',
    ];

    private MessageGroupSubscription $messageGroupSubscription;

    public function __construct( MessageGroupSubscription $messageGroupSubscription ) {
        parent::__construct( 'ManageMessageGroupSubscriptions' );
        $this->messageGroupSubscription = $messageGroupSubscription;
    }

    public function doesWrites() {
        return true;
    }

    /** @param string|null $mode */
    public function execute( $mode ) {
        $this->setHeaders();

        $user = $this->getUser();
        if ( !$user->isNamed() ) {
            throw new UserNotLoggedIn( 'tpt-manage-message-group-subscriptions-list-anon-text' );
        }

        $out = $this->getOutput();

        $this->checkReadOnly();

        $this->outputHeader();
        $out->addModuleStyles( [
            'mediawiki.interface.helpers.styles',
            'mediawiki.special',
            'ext.translate.special.managemessagegroupsubscriptions.styles'
        ] );
        $out->addModules( [ 'ext.translate.special.managemessagegroupsubscriptions' ] );

        switch ( $mode ) {
            case self::EDIT_RAW:
                $this->executeRawEditForm();
                break;
            default:
                $this->executeNormalEditForm();
                break;
        }
    }

    /** Executes the special page in raw (textbox) mode */
    private function executeRawEditForm(): void {
        $out = $this->getOutput();
        $out->setPageTitleMsg( $this->msg( 'tpt-manage-message-group-subscriptions-edit-raw-title' ) );
        $form = $this->getRawForm();
        if ( $form->show() ) {
            $out->addReturnTo( SpecialPage::getTitleFor( 'ManageMessageGroupSubscriptions' ) );
        }
    }

    /** Executes the special page in normal (checkbox) mode */
    private function executeNormalEditForm(): void {
        $out = $this->getOutput();
        $out->setPageTitleMsg( $this->msg( 'tpt-manage-message-group-subscriptions-edit-normal-title' ) );

        $form = $this->getNormalForm();
        $form->prepareForm();

        $result = $form->tryAuthorizedSubmit();
        if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
            $out->addReturnTo( SpecialPage::getTitleFor( 'ManageMessageGroupSubscriptions' ) );
            return;
        }

        $pout = new ParserOutput;
        $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
        $pout->setRawText( Parser::TOC_PLACEHOLDER );
        $out->addParserOutput( $pout );

        $form->displayForm( $result );
    }

    /** @inheritDoc */
    public function getAssociatedNavigationLinks(): array {
        return self::WATCHLIST_TAB_PATHS;
    }

    /**
     * Return an array of subpages that this special page will accept.
     * @return string[] subpages
     */
    public function getSubpagesForPrefixSearch(): array {
        // ManageMessageGroupSubscriptions uses ManageMessageGroupSubscriptions::getMode, so new types should be added
        // here.
        return [
            self::EDIT_RAW,
        ];
    }

    public function submitRaw( array $data ): bool {
        $retainedLines = trim( $data['Titles'] );
        $retainedGroups = $retainedLines ? array_map( 'trim', explode( "\n", $retainedLines ) ) : [];
        $current = $this->messageGroupSubscription->getUserSubscriptions( $this->getUser() );

        $toSubscribe = array_diff( $retainedGroups, $current );
        $toUnsubscribe = array_diff( $current, $retainedGroups );
        if ( !$toSubscribe && !$toUnsubscribe ) {
            return false;
        }

        $this->messageGroupSubscription->subscribeToGroupsById( $toSubscribe, $this->getUser() );
        $this->messageGroupSubscription->unsubscribeFromGroupsById( $toUnsubscribe, $this->getUser() );

        $this->getUser()->invalidateCache();
        $successMessage = Html::element(
            'p',
            [],
            $this->msg( 'tpt-manage-message-group-subscriptions-raw-done' )->text()
        );

        if ( $toSubscribe ) {
            $successMessage .= Html::element(
                'p',
                [],
                $this->msg( 'tpt-manage-message-group-subscriptions-raw-added' )
                    ->numParams( count( $toSubscribe ) )
                    ->text()
            );
            $successMessage .= $this->showTitles( $toSubscribe );
        }

        if ( $toUnsubscribe ) {
            $successMessage .= Html::element(
                'p',
                [],
                $this->msg( 'tpt-manage-message-group-subscriptions-raw-removed' )
                    ->numParams( count( $toUnsubscribe ) )
                    ->text()
            );
            $successMessage .= $this->showTitles( $toUnsubscribe );
        }

        $this->getOutput()->addHTML( $successMessage );
        return true;
    }

    /** @param string[] $messageGroupsIds */
    private function showTitles( array $messageGroupsIds ): string {
        if ( count( $messageGroupsIds ) >= 100 ) {
            return $this->msg( 'tpt-manage-message-group-subscriptions-too-many' )->parse();
        }

        // Print out the list
        $output = "<ul>\n";
        $linkRenderer = $this->getLinkRenderer();
        foreach ( $messageGroupsIds as $messageGroupId ) {
            if ( $messageGroupId === "" ) {
                continue;
            }

            $messageGroup = MessageGroups::getGroup( $messageGroupId );
            if ( !$messageGroup ) {
                $output .= Html::element( 'li', [], $messageGroupId ) . "\n";
                continue;
            }

            $output .= '<li>' .
                $linkRenderer->makeKnownLink(
                    SpecialPage::getTitleFor( 'Translate' ),
                    $messageGroup->getLabel(),
                    [],
                    [
                        'group' => $messageGroupId,
                    ]
                ) .
                "</li>\n";
        }

        $output .= "</ul>\n";
        return $output;
    }

    /**
     * Get a list of message group labels on a user's subscriptions, excluding talk pages,
     * and return as a two-dimensional array with namespace and title.
     *
     * @return array
     */
    private function getMessageGroupSubscriptionInfo(): array {
        $labels = [];

        $subscribedItems = $this->messageGroupSubscription->getUserSubscriptions( $this->getUser() );
        foreach ( $subscribedItems as $subscriptionItem ) {
            $messageGroup = MessageGroups::getGroup( $subscriptionItem );
            if ( !$messageGroup ) {
                $labels[self::NS_INVALID][$subscriptionItem] = $subscriptionItem;
                continue;
            }

            // For items with other namespaces that don't exist in Translate but broader MediaWiki
            $relatedPage = $messageGroup->getRelatedPage();
            $namespace = $relatedPage ? $relatedPage->getNamespace() : $messageGroup->getNamespace();

            $groupLabel = $messageGroup->getLabel();
            $labels[$namespace][$groupLabel] = $messageGroup;
        }
        return $labels;
    }

    public function submitNormal( $data ): bool {
        $toRemove = [];

        foreach ( $data as $messageGroups ) {
            // ignore the 'check all' checkbox, which is a boolean value
            if ( is_array( $messageGroups ) ) {
                $toRemove = array_merge( $toRemove, $messageGroups );
            }
        }

        if ( count( $toRemove ) > 0 ) {
            $this->messageGroupSubscription->unsubscribeFromGroupsById( $toRemove, $this->getUser() );
            $successMessage = $this->msg( 'tpt-manage-message-group-subscriptions-normal-done' )
                ->numParams( count( $toRemove ) )
                ->parse();
            $successMessage .= $this->showTitles( $toRemove );
            $this->getOutput()->addHTML( $successMessage );
            return true;
        }

        return false;
    }

    /** Get the standard subscriptions editing form */
    private function getNormalForm(): HTMLForm {
        $fields = [];

        // Allow subscribers to manipulate the list of watched pages (or use it
        // to preload lots of details at once)
        $subscriptionInfo = $this->getMessageGroupSubscriptionInfo();

        foreach ( $subscriptionInfo as $namespace => $messageGroups ) {
            $options = [];
            foreach ( $messageGroups as $messageGroup ) {
                if ( is_string( $messageGroup ) ) {
                    $options[htmlspecialchars( $messageGroup )] = $messageGroup;
                } else {
                    $text = $this->buildRemoveLine( $messageGroup );
                    $options[$text] = $messageGroup->getId();
                }
            }
            ksort( $options );

            // checkTitle can filter some options out, avoid empty sections
            if ( count( $options ) > 0 ) {
                // add a checkbox to select all entries in namespace
                $fields['CheckAllNs' . $namespace] = [
                    'cssclass' => 'tpt-manage-subscriptions-messagegroups-checkall',
                    'type' => 'check',
                    'section' => "ns$namespace",
                    'label' => $this->msg( 'tpt-manage-message-group-subscriptions-normal-check-all' )->text()
                ];

                $fields['TitlesNs' . $namespace] = [
                    'cssclass' => 'tpt-manage-message-group-subscriptions-messagegroups-check',
                    'class' => EditWatchlistCheckboxSeriesField::class,
                    'options' => $options,
                    'section' => "ns$namespace",
                ];
            }
        }

        $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() );
        $form->setTitle( $this->getPageTitle() ); // Remove subpage
        $form->setSubmitTextMsg( 'tpt-manage-message-group-subscriptions-normal-submit' );
        $form->setSubmitDestructive();
        $form->setSubmitTooltip( 'tpt-manage-message-group-subscriptions-normal-submit' );
        $form->setWrapperLegendMsg( 'tpt-manage-message-group-subscriptions-normal-legend' );
        $form->addHeaderHtml( $this->msg( 'tpt-manage-message-group-subscriptions-normal-explain' )->parse() );
        $form->setSubmitCallback( [ $this, 'submitNormal' ] );

        return $form;
    }

    /** Build the label for a checkbox, with a link to the title. */
    private function buildRemoveLine( MessageGroup $messageGroup ): string {
        return $this->getLinkRenderer()->makeKnownLink(
            SpecialPage::getTitleFor( 'Translate' ),
            $messageGroup->getLabel(),
            [],
            [
                'group' => $messageGroup->getId(),
            ]
        );
    }

    /** Get a form for editing the subscriptions in "raw" mode */
    private function getRawForm(): HTMLForm {
        $messageGroupIds = $this->messageGroupSubscription->getUserSubscriptions( $this->getUser() );
        $labels = implode( "\n", $messageGroupIds );

        $fields = [
            'Titles' => [
                'type' => 'textarea',
                'label-message' => 'tpt-manage-message-group-subscriptions-raw-titles',
                'default' => $labels,
            ],
        ];
        $form = new OOUIHTMLForm( $fields, $this->getContext() );
        $form->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
        $form->setSubmitTextMsg( 'tpt-manage-message-group-subscriptions-raw-submit' );
        $form->setSubmitTooltip( 'tpt-manage-message-group-subscriptions-raw-submit' );
        $form->setWrapperLegendMsg( 'tpt-manage-message-group-subscriptions-raw-legend' );
        $form->addHeaderHtml( $this->msg( 'tpt-manage-message-group-subscriptions-raw-explain' )->parse() );
        $form->setSubmitCallback( [ $this, 'submitRaw' ] );

        return $form;
    }
}