Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 99 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
ManageTranslatorSandboxSpecialPage | |
0.00% |
0 / 99 |
|
0.00% |
0 / 11 |
306 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
showPage | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
makeFilter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeSearchBox | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
makeList | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
30 | |||
makeRequestItem | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
getHumanTimestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
translatorRequestSort | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\TranslatorSandbox; |
5 | |
6 | use FormatJson; |
7 | use MediaWiki\Config\ServiceOptions; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\User\UserOptionsLookup; |
10 | use MWTimestamp; |
11 | use Sanitizer; |
12 | use SpecialPage; |
13 | use User; |
14 | |
15 | /** |
16 | * Special page for managing sandboxed users. |
17 | * |
18 | * @author Niklas Laxström |
19 | * @author Amir E. Aharoni |
20 | * @license GPL-2.0-or-later |
21 | * @ingroup SpecialPage TranslateSpecialPage |
22 | */ |
23 | class ManageTranslatorSandboxSpecialPage extends SpecialPage { |
24 | /** @var TranslationStashReader */ |
25 | private $stash; |
26 | /** @var UserOptionsLookup */ |
27 | private $userOptionsLookup; |
28 | private TranslateSandbox $translateSandbox; |
29 | |
30 | public const CONSTRUCTOR_OPTIONS = [ |
31 | 'TranslateUseSandbox', |
32 | ]; |
33 | |
34 | public function __construct( |
35 | TranslationStashReader $stash, |
36 | UserOptionsLookup $userOptionsLookup, |
37 | TranslateSandbox $translateSandbox, |
38 | ServiceOptions $options |
39 | ) { |
40 | $this->stash = $stash; |
41 | $this->userOptionsLookup = $userOptionsLookup; |
42 | $this->translateSandbox = $translateSandbox; |
43 | |
44 | parent::__construct( |
45 | 'ManageTranslatorSandbox', |
46 | 'translate-sandboxmanage', |
47 | $options->get( 'TranslateUseSandbox' ) |
48 | ); |
49 | } |
50 | |
51 | public function doesWrites() { |
52 | return true; |
53 | } |
54 | |
55 | protected function getGroupName() { |
56 | return 'translation'; |
57 | } |
58 | |
59 | public function execute( $params ) { |
60 | $this->setHeaders(); |
61 | $this->checkPermissions(); |
62 | $out = $this->getOutput(); |
63 | $out->addModuleStyles( |
64 | [ |
65 | 'ext.translate.special.managetranslatorsandbox.styles', |
66 | 'mediawiki.ui.button', |
67 | 'jquery.uls.grid', |
68 | ] |
69 | ); |
70 | $out->addModules( 'ext.translate.special.managetranslatorsandbox' ); |
71 | |
72 | $this->showPage(); |
73 | } |
74 | |
75 | /** Generates the whole page html and appends it to output */ |
76 | private function showPage(): void { |
77 | $out = $this->getOutput(); |
78 | |
79 | $nojs = Html::errorBox( |
80 | $this->msg( 'tux-nojs' )->escaped(), |
81 | '', |
82 | 'tux-nojs' |
83 | ); |
84 | $out->addHTML( $nojs ); |
85 | |
86 | $out->addHTML( |
87 | <<<HTML |
88 | <div class="grid tsb-container"> |
89 | <div class="row"> |
90 | <div class="nine columns pane filter">{$this->makeFilter()}</div> |
91 | <div class="three columns pane search">{$this->makeSearchBox()}</div> |
92 | </div> |
93 | <div class="row tsb-body"> |
94 | <div class="four columns pane requests"> |
95 | {$this->makeList()} |
96 | <div class="request-footer"> |
97 | <span class="selected-counter"> |
98 | {$this->msg( 'tsb-selected-count' )->numParams( 0 )->escaped()} |
99 | </span> |
100 | \u{00A0} |
101 | <a href="#" class="older-requests-indicator"></a> |
102 | </div> |
103 | </div> |
104 | <div class="eight columns pane details"></div> |
105 | </div> |
106 | </div> |
107 | HTML |
108 | ); |
109 | } |
110 | |
111 | private function makeFilter(): string { |
112 | return $this->msg( 'tsb-filter-pending' )->escaped(); |
113 | } |
114 | |
115 | private function makeSearchBox(): string { |
116 | return <<<HTML |
117 | <input class="request-filter-box right" |
118 | placeholder="{$this->msg( 'tsb-search-requests' )->escaped()}" type="search" /> |
119 | HTML; |
120 | } |
121 | |
122 | private function makeList(): string { |
123 | $items = []; |
124 | $requests = []; |
125 | $users = $this->translateSandbox->getUsers(); |
126 | |
127 | /** @var User $user */ |
128 | foreach ( $users as $user ) { |
129 | $reminders = $this->userOptionsLookup->getOption( $user, 'translate-sandbox-reminders' ); |
130 | $reminders = $reminders ? explode( '|', $reminders ) : []; |
131 | $remindersCount = count( $reminders ); |
132 | if ( $remindersCount ) { |
133 | $lastReminderTimestamp = new MWTimestamp( end( $reminders ) ); |
134 | $lastReminderAgo = htmlspecialchars( |
135 | $this->getHumanTimestamp( $lastReminderTimestamp ) |
136 | ); |
137 | } else { |
138 | $lastReminderAgo = ''; |
139 | } |
140 | |
141 | $requests[] = [ |
142 | 'username' => $user->getName(), |
143 | 'email' => $user->getEmail(), |
144 | 'gender' => $this->userOptionsLookup->getOption( $user, 'gender' ), |
145 | 'registrationdate' => $user->getRegistration(), |
146 | 'translations' => count( $this->stash->getTranslations( $user ) ), |
147 | 'languagepreferences' => FormatJson::decode( |
148 | $this->userOptionsLookup->getOption( $user, 'translate-sandbox' ) |
149 | ), |
150 | 'userid' => $user->getId(), |
151 | 'reminderscount' => $remindersCount, |
152 | 'lastreminder' => $lastReminderAgo, |
153 | ]; |
154 | } |
155 | |
156 | // Sort the requests based on translations and registration date |
157 | usort( $requests, [ $this, 'translatorRequestSort' ] ); |
158 | |
159 | foreach ( $requests as $request ) { |
160 | $items[] = $this->makeRequestItem( $request ); |
161 | } |
162 | |
163 | $requestsList = implode( "\n", $items ); |
164 | |
165 | return <<<HTML |
166 | <div class="row request-header"> |
167 | <div class="four columns"> |
168 | <button class="language-selector unselected"> |
169 | {$this->msg( 'tsb-all-languages-button-label' )->escaped()} |
170 | </button> |
171 | </div> |
172 | <div class="five columns request-count"></div> |
173 | <div class="three columns text-center"> |
174 | <input class="request-selector-all" name="request" type="checkbox" /> |
175 | </div> |
176 | </div> |
177 | <div class="requests-list"> |
178 | {$requestsList} |
179 | </div> |
180 | HTML; |
181 | } |
182 | |
183 | private function makeRequestItem( array $request ): string { |
184 | $requestdataEnc = htmlspecialchars( FormatJson::encode( $request ) ); |
185 | $nameEnc = htmlspecialchars( $request['username'] ); |
186 | $nameEncForId = |
187 | htmlspecialchars( |
188 | Sanitizer::escapeIdForAttribute( 'tsb-request-' . $request['username'] ) |
189 | ); |
190 | $emailEnc = htmlspecialchars( $request['email'] ); |
191 | $countEnc = htmlspecialchars( (string)$request['translations'] ); |
192 | $timestamp = new MWTimestamp( $request['registrationdate'] ); |
193 | $agoEnc = htmlspecialchars( $this->getHumanTimestamp( $timestamp ) ); |
194 | |
195 | return <<<HTML |
196 | <div class="row request" data-data="$requestdataEnc" id="$nameEncForId"> |
197 | <div class="two columns amount"> |
198 | <div class="translation-count">$countEnc</div> |
199 | </div> |
200 | <div class="seven columns request-info"> |
201 | <div class="row username">$nameEnc</div> |
202 | <div class="row email" dir="ltr">$emailEnc</div> |
203 | </div> |
204 | <div class="three columns approval text-center"> |
205 | <input class="row request-selector" name="request" type="checkbox" /> |
206 | <div class="row signup-age">$agoEnc</div> |
207 | </div> |
208 | </div> |
209 | HTML; |
210 | } |
211 | |
212 | private function getHumanTimestamp( MWTimestamp $ts ): string { |
213 | return $this->getLanguage()->getHumanTimestamp( $ts, null, $this->getUser() ); |
214 | } |
215 | |
216 | /** |
217 | * Sorts groups by descending order of number of translations, |
218 | * registration date and username |
219 | */ |
220 | private function translatorRequestSort( array $a, array $b ): int { |
221 | return $b['translations'] <=> $a['translations'] |
222 | ?: $b['registrationdate'] <=> $a['registrationdate'] |
223 | ?: strnatcasecmp( $a['username'], $b['username'] ); |
224 | } |
225 | } |