wikimedia/mediawiki-extensions-Translate

View on GitHub
src/TranslatorSandbox/ManageTranslatorSandboxSpecialPage.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php
declare( strict_types = 1 );

namespace MediaWiki\Extension\Translate\TranslatorSandbox;

use FormatJson;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Html\Html;
use MediaWiki\Parser\Sanitizer;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use MediaWiki\Utils\MWTimestamp;

/**
 * Special page for managing sandboxed users.
 *
 * @author Niklas Laxström
 * @author Amir E. Aharoni
 * @license GPL-2.0-or-later
 * @ingroup SpecialPage TranslateSpecialPage
 */
class ManageTranslatorSandboxSpecialPage extends SpecialPage {
    /** @var TranslationStashReader */
    private $stash;
    /** @var UserOptionsLookup */
    private $userOptionsLookup;
    private TranslateSandbox $translateSandbox;

    public const CONSTRUCTOR_OPTIONS = [
        'TranslateUseSandbox',
    ];

    public function __construct(
        TranslationStashReader $stash,
        UserOptionsLookup $userOptionsLookup,
        TranslateSandbox $translateSandbox,
        ServiceOptions $options
    ) {
        $this->stash = $stash;
        $this->userOptionsLookup = $userOptionsLookup;
        $this->translateSandbox = $translateSandbox;

        parent::__construct(
            'ManageTranslatorSandbox',
            'translate-sandboxmanage',
            $options->get( 'TranslateUseSandbox' )
        );
    }

    public function doesWrites() {
        return true;
    }

    protected function getGroupName() {
        return 'translation';
    }

    public function execute( $params ) {
        $this->setHeaders();
        $this->checkPermissions();
        $out = $this->getOutput();
        $out->addModuleStyles(
            [
                'ext.translate.special.managetranslatorsandbox.styles',
                'mediawiki.ui.button',
                'jquery.uls.grid',
            ]
        );
        $out->addModules( 'ext.translate.special.managetranslatorsandbox' );

        $this->showPage();
    }

    /** Generates the whole page html and appends it to output */
    private function showPage(): void {
        $out = $this->getOutput();

        $nojs = Html::errorBox(
            $this->msg( 'tux-nojs' )->escaped(),
            '',
            'tux-nojs'
        );
        $out->addHTML( $nojs );

        $out->addHTML(
            <<<HTML
                <div class="grid tsb-container">
                    <div class="row">
                        <div class="nine columns pane filter">{$this->makeFilter()}</div>
                        <div class="three columns pane search">{$this->makeSearchBox()}</div>
                    </div>
                    <div class="row tsb-body">
                        <div class="four columns pane requests">
                            {$this->makeList()}
                            <div class="request-footer">
                                <span class="selected-counter">
                                    {$this->msg( 'tsb-selected-count' )->numParams( 0 )->escaped()}
                                </span>
                                \u{00A0}
                                <a href="#" class="older-requests-indicator"></a>
                            </div>
                        </div>
                        <div class="eight columns pane details"></div>
                    </div>
                </div>
                HTML
        );
    }

    private function makeFilter(): string {
        return $this->msg( 'tsb-filter-pending' )->escaped();
    }

    private function makeSearchBox(): string {
        return <<<HTML
            <input class="request-filter-box right"
                placeholder="{$this->msg( 'tsb-search-requests' )->escaped()}" type="search" />
            HTML;
    }

    private function makeList(): string {
        $items = [];
        $requests = [];
        $users = $this->translateSandbox->getUsers();

        /** @var User $user */
        foreach ( $users as $user ) {
            $reminders = $this->userOptionsLookup->getOption( $user, 'translate-sandbox-reminders' );
            $reminders = $reminders ? explode( '|', $reminders ) : [];
            $remindersCount = count( $reminders );
            if ( $remindersCount ) {
                $lastReminderTimestamp = new MWTimestamp( end( $reminders ) );
                $lastReminderAgo = htmlspecialchars(
                    $this->getHumanTimestamp( $lastReminderTimestamp )
                );
            } else {
                $lastReminderAgo = '';
            }

            $requests[] = [
                'username' => $user->getName(),
                'email' => $user->getEmail(),
                'gender' => $this->userOptionsLookup->getOption( $user, 'gender' ),
                'registrationdate' => $user->getRegistration(),
                'translations' => count( $this->stash->getTranslations( $user ) ),
                'languagepreferences' => FormatJson::decode(
                    $this->userOptionsLookup->getOption( $user, 'translate-sandbox' )
                ),
                'userid' => $user->getId(),
                'reminderscount' => $remindersCount,
                'lastreminder' => $lastReminderAgo,
            ];
        }

        // Sort the requests based on translations and registration date
        usort( $requests, [ $this, 'translatorRequestSort' ] );

        foreach ( $requests as $request ) {
            $items[] = $this->makeRequestItem( $request );
        }

        $requestsList = implode( "\n", $items );

        return <<<HTML
            <div class="row request-header">
                <div class="four columns">
                    <button class="language-selector unselected">
                        {$this->msg( 'tsb-all-languages-button-label' )->escaped()}
                    </button>
                </div>
                <div class="five columns request-count"></div>
                <div class="three columns text-center">
                    <input class="request-selector-all" name="request" type="checkbox" />
                </div>
            </div>
            <div class="requests-list">
                {$requestsList}
            </div>
            HTML;
    }

    private function makeRequestItem( array $request ): string {
        $requestdataEnc = htmlspecialchars( FormatJson::encode( $request ) );
        $nameEnc = htmlspecialchars( $request['username'] );
        $nameEncForId =
            htmlspecialchars(
                Sanitizer::escapeIdForAttribute( 'tsb-request-' . $request['username'] )
            );
        $emailEnc = htmlspecialchars( $request['email'] );
        $countEnc = htmlspecialchars( (string)$request['translations'] );
        $timestamp = new MWTimestamp( $request['registrationdate'] );
        $agoEnc = htmlspecialchars( $this->getHumanTimestamp( $timestamp ) );

        return <<<HTML
            <div class="row request" data-data="$requestdataEnc" id="$nameEncForId">
                <div class="two columns amount">
                    <div class="translation-count">$countEnc</div>
                </div>
                <div class="seven columns request-info">
                    <div class="row username">$nameEnc</div>
                    <div class="row email" dir="ltr">$emailEnc</div>
                </div>
                <div class="three columns approval text-center">
                    <input class="row request-selector" name="request" type="checkbox" />
                    <div class="row signup-age">$agoEnc</div>
                </div>
            </div>
            HTML;
    }

    private function getHumanTimestamp( MWTimestamp $ts ): string {
        return $this->getLanguage()->getHumanTimestamp( $ts, null, $this->getUser() );
    }

    /**
     * Sorts groups by descending order of number of translations,
     * registration date and username
     */
    private function translatorRequestSort( array $a, array $b ): int {
        return $b['translations'] <=> $a['translations']
            ?: $b['registrationdate'] <=> $a['registrationdate']
                ?: strnatcasecmp( $a['username'], $b['username'] );
    }
}