summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/specials/SpecialManageTranslatorSandbox.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Translate/specials/SpecialManageTranslatorSandbox.php')
-rw-r--r--www/wiki/extensions/Translate/specials/SpecialManageTranslatorSandbox.php334
1 files changed, 334 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/specials/SpecialManageTranslatorSandbox.php b/www/wiki/extensions/Translate/specials/SpecialManageTranslatorSandbox.php
new file mode 100644
index 00000000..28311452
--- /dev/null
+++ b/www/wiki/extensions/Translate/specials/SpecialManageTranslatorSandbox.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * Contains logic for Special:ManageTranslatorSandbox
+ *
+ * @file
+ * @author Niklas Laxström
+ * @author Amir E. Aharoni
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Special page for managing sandboxed users.
+ *
+ * @ingroup SpecialPage TranslateSpecialPage
+ */
+class SpecialManageTranslatorSandbox extends SpecialPage {
+ /** @var TranslationStashStorage */
+ protected $stash;
+
+ public function __construct() {
+ global $wgTranslateUseSandbox;
+ parent::__construct(
+ 'ManageTranslatorSandbox',
+ 'translate-sandboxmanage',
+ $wgTranslateUseSandbox
+ );
+ }
+
+ public function doesWrites() {
+ return true;
+ }
+
+ protected function getGroupName() {
+ return 'users';
+ }
+
+ 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->stash = new TranslationStashStorage( wfGetDB( DB_MASTER ) );
+
+ $this->prepareForTests();
+ $this->showPage();
+ }
+
+ /**
+ * Deletes a user page if it exists.
+ * This is needed especially when deleting sandbox users
+ * that were created as part of the integration tests.
+ * @param User $user
+ */
+ protected function deleteUserPage( $user ) {
+ $userpage = WikiPage::factory( $user->getUserPage() );
+ if ( $userpage->exists() ) {
+ $dummyError = '';
+ $userpage->doDeleteArticleReal(
+ wfMessage( 'tsb-delete-userpage-summary' )->inContentLanguage()->text(),
+ false,
+ 0,
+ true,
+ $dummyError,
+ $this->getUser()
+ );
+ }
+ }
+
+ /**
+ * Add users to the sandbox or delete them to facilitate browsers tests.
+ * Use with caution!
+ */
+ public function prepareForTests() {
+ global $wgTranslateTestUsers;
+
+ $user = $this->getUser();
+ $request = $this->getRequest();
+
+ if ( !in_array( $user->getName(), $wgTranslateTestUsers, true ) ) {
+ return;
+ }
+
+ if ( $request->getVal( 'integrationtesting' ) === 'populate' ) {
+ // Empty all the users, even if they were created manually
+ // to ensure the number of users is what the tests expect
+ $this->emptySandbox();
+
+ $textUsernamePrefixes = [ 'Pupu', 'Orava' ];
+ $testLanguages = [ 'fi', 'uk', 'nl', 'he', 'bn' ];
+ $testLanguagesCount = count( $testLanguages );
+
+ foreach ( $textUsernamePrefixes as $prefix ) {
+ for ( $i = 0; $i < $testLanguagesCount; $i++ ) {
+ $name = "$prefix$i";
+
+ // Get rid of users, even if promoted during tests
+ $userToDelete = User::newFromName( $name, false );
+ $this->deleteUserPage( $userToDelete );
+ TranslateSandbox::deleteUser( $userToDelete, 'force' );
+
+ $user = TranslateSandbox::addUser( $name, "$name@blackhole.io", 'porkkana' );
+ $user->setOption(
+ 'translate-sandbox',
+ FormatJson::encode( [
+ 'languages' => [ $testLanguages[$i] ],
+ 'comment' => '',
+ ] )
+ );
+
+ $reminders = [];
+ for ( $reminderIndex = 0; $reminderIndex < $i; $reminderIndex++ ) {
+ $reminders[] = wfTimestamp() - $reminderIndex * $i * 10000;
+ }
+
+ $user->setOption(
+ 'translate-sandbox-reminders',
+ implode( '|', $reminders )
+ );
+ $user->saveSettings();
+
+ for ( $j = 0; $j < $i; $j++ ) {
+ $title = Title::makeTitle(
+ NS_MEDIAWIKI,
+ wfRandomString( 24 ) . '/' . $testLanguages[$i]
+ );
+ $translation = 'plop';
+ $stashedTranslation = new StashedTranslation( $user, $title, $translation );
+ $this->stash->addTranslation( $stashedTranslation );
+ }
+ }
+ }
+
+ // Another account for testing a translator to multiple languages
+ $oldPolyglotUser = User::newFromName( 'Kissa', false );
+ $this->deleteUserPage( $oldPolyglotUser );
+ TranslateSandbox::deleteUser( $oldPolyglotUser, 'force' );
+
+ $polyglotUser = TranslateSandbox::addUser( 'Kissa', 'kissa@blackhole.io', 'porkkana' );
+ $polyglotUser->setOption(
+ 'translate-sandbox',
+ FormatJson::encode( [
+ 'languages' => $testLanguages,
+ 'comment' => "I know some languages, and I'm a developer.",
+ ] )
+ );
+ $polyglotUser->saveSettings();
+ for ( $polyglotLang = 0; $polyglotLang < $testLanguagesCount; $polyglotLang++ ) {
+ $title = Title::makeTitle(
+ NS_MEDIAWIKI,
+ wfRandomString( 24 ) . '/' . $testLanguages[$polyglotLang]
+ );
+ $translation = "plop in $testLanguages[$polyglotLang]";
+ $stashedTranslation = new StashedTranslation( $polyglotUser, $title, $translation );
+ $this->stash->addTranslation( $stashedTranslation );
+ }
+ } elseif ( $request->getVal( 'integrationtesting' ) === 'empty' ) {
+ $this->emptySandbox();
+ }
+ }
+
+ /**
+ * Delete all the users in the sandbox.
+ * Use with caution!
+ * To facilitate browsers tests.
+ */
+ protected function emptySandbox() {
+ $users = TranslateSandbox::getUsers();
+ foreach ( $users as $user ) {
+ TranslateSandbox::deleteUser( $user );
+ }
+ }
+
+ /**
+ * Generates the whole page html and appends it to output
+ */
+ protected function showPage() {
+ $out = $this->getOutput();
+
+ $nojs = Html::element(
+ 'div',
+ [ 'class' => 'tux-nojs errorbox' ],
+ $this->msg( 'tux-nojs' )->plain()
+ );
+ $out->addHTML( $nojs );
+
+ $out->addHTML( <<<HTML
+<div class="grid">
+ <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>
+ &nbsp;
+ <a href="#" class="older-requests-indicator"></a>
+ </div>
+ </div>
+ <div class="eight columns pane details"></div>
+ </div>
+</div>
+HTML
+ );
+ }
+
+ protected function makeFilter() {
+ return $this->msg( 'tsb-filter-pending' )->escaped();
+ }
+
+ protected function makeSearchBox() {
+ return <<<HTML
+<input class="request-filter-box right"
+ placeholder="{$this->msg( 'tsb-search-requests' )->escaped()}" type="search">
+</input>
+HTML;
+ }
+
+ protected function makeList() {
+ $items = [];
+ $requests = [];
+ $users = TranslateSandbox::getUsers();
+
+ /** @var User $user */
+ foreach ( $users as $user ) {
+ $reminders = $user->getOption( 'translate-sandbox-reminders' );
+ $reminders = $reminders ? explode( '|', $reminders ) : [];
+ $remindersCount = count( $reminders );
+ if ( $remindersCount ) {
+ $lastReminderTimestamp = new MWTimestamp( end( $reminders ) );
+ $lastReminderAgo = htmlspecialchars(
+ $lastReminderTimestamp->getHumanTimestamp()
+ );
+ } else {
+ $lastReminderAgo = '';
+ }
+
+ $requests[] = [
+ 'username' => $user->getName(),
+ 'email' => $user->getEmail(),
+ 'gender' => $user->getOption( 'gender' ),
+ 'registrationdate' => $user->getRegistration(),
+ 'translations' => count( $this->stash->getTranslations( $user ) ),
+ 'languagepreferences' => FormatJson::decode( $user->getOption( 'translate-sandbox' ) ),
+ 'userid' => $user->getId(),
+ 'reminderscount' => $remindersCount,
+ 'lastreminder' => $lastReminderAgo,
+ ];
+ }
+
+ // Sort the requests based on translations and registration date
+ usort( $requests, [ __CLASS__, '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 center">
+ <input class="request-selector-all" name="request" type="checkbox" />
+ </div>
+</div>
+<div class="requests-list">
+ {$requestsList}
+</div>
+HTML;
+ }
+
+ protected function makeRequestItem( $request ) {
+ $requestdataEnc = htmlspecialchars( FormatJson::encode( $request ) );
+ $nameEnc = htmlspecialchars( $request['username'] );
+ $nameEncForId = htmlspecialchars( Sanitizer::escapeId( $request['username'], 'noninitial' ) );
+ $emailEnc = htmlspecialchars( $request['email'] );
+ $countEnc = htmlspecialchars( $request['translations'] );
+ $timestamp = new MWTimestamp( $request['registrationdate'] );
+ $agoEnc = htmlspecialchars( $timestamp->getHumanTimestamp() );
+
+ return <<<HTML
+<div class="row request" data-data="$requestdataEnc" id="tsb-request-$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">$emailEnc</div>
+ </div>
+ <div class="three columns approval center">
+ <input class="row request-selector" name="request" type="checkbox" />
+ <div class="row signup-age">$agoEnc</div>
+ </div>
+</div>
+HTML;
+ }
+
+ /**
+ * Sorts groups by descending order of number of translations,
+ * registration date and username
+ *
+ * @since 2013.12
+ * @param array $a Translation request
+ * @param array $b Translation request
+ * @return int comparison result
+ */
+ public static function translatorRequestSort( $a, $b ) {
+ $translationCountDiff = $b['translations'] - $a['translations'];
+ if ( $translationCountDiff !== 0 ) {
+ return $translationCountDiff;
+ }
+
+ $registrationDateDiff = $b['registrationdate'] - $a['registrationdate'];
+ if ( $registrationDateDiff !== 0 ) {
+ return $registrationDateDiff;
+ }
+
+ return strcmp( $a['username'], $b['username'] );
+ }
+}