Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
AggregateGroupsSpecialPage.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
7use Html;
8use MediaWiki\Cache\LinkBatchFactory;
10use SpecialPage;
13use Xml;
14use XmlSelect;
15
25class AggregateGroupsSpecialPage extends SpecialPage {
27 private $hasPermission = false;
29 private $linkBatchFactory;
31 private $languageSelector;
32
33 public function __construct( LinkBatchFactory $linkBatchFactory ) {
34 parent::__construct( 'AggregateGroups', 'translate-manage' );
35 $this->linkBatchFactory = $linkBatchFactory;
36 }
37
38 protected function getGroupName(): string {
39 return 'translation';
40 }
41
42 public function execute( $parameters ) {
43 $this->setHeaders();
44 $this->addHelpLink( 'Help:Extension:Translate/Page translation administration' );
45
46 $out = $this->getOutput();
47 $out->addModuleStyles( 'ext.translate.specialpages.styles' );
48
49 // Check permissions
50 if ( $this->getUser()->isAllowed( 'translate-manage' ) ) {
51 $this->hasPermission = true;
52 }
53
54 $groupsPreload = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
55 TranslateMetadata::preloadGroups( array_keys( $groupsPreload ), __METHOD__ );
56
58 uasort( $groups, [ MessageGroups::class, 'groupLabelSort' ] );
59 $aggregates = [];
60 $pages = [];
61 foreach ( $groups as $group ) {
62 if ( $group instanceof WikiPageMessageGroup ) {
63 $pages[] = $group;
64 } elseif ( $group instanceof AggregateMessageGroup ) {
65 // Filter out AggregateGroups configured in YAML
66 $subgroups = TranslateMetadata::getSubgroups( $group->getId() );
67 if ( $subgroups !== null ) {
68 $aggregates[] = $group;
69 }
70 }
71 }
72
73 if ( !$pages ) {
74 // @todo Use different message
75 $out->addWikiMsg( 'tpt-list-nopages' );
76
77 return;
78 }
79
80 $this->showAggregateGroups( $aggregates );
81 }
82
83 protected function showAggregateGroup( AggregateMessageGroup $group ): string {
84 $id = $group->getId();
85 $label = $group->getLabel();
86 $desc = $group->getDescription( $this->getContext() );
87 $sourceLanguage = TranslateMetadata::get( $id, 'sourcelanguagecode' );
88
89 $edit = '';
90 $remove = '';
91 $editGroup = '';
92
93 // Add divs for editing Aggregate Groups
94 if ( $this->hasPermission ) {
95 // Group edit and remove buttons
96 $edit = Html::element( 'span', [ 'class' => 'tp-aggregate-edit-ag-button' ] );
97 $remove = Html::element( 'span', [ 'class' => 'tp-aggregate-remove-ag-button' ] );
98
99 // Edit group div
100 $languageSelector = $this->getLanguageSelector( 'edit', $sourceLanguage ?: '-' );
101
102 $editGroupNameLabel = $this->msg( 'tpt-aggregategroup-edit-name' )->escaped();
103 $editGroupName = Html::input(
104 'tp-agg-name',
105 $label,
106 'text',
107 [ 'class' => 'tp-aggregategroup-edit-name', 'maxlength' => '200' ]
108 );
109 $editGroupDescriptionLabel = $this->msg( 'tpt-aggregategroup-edit-description' )->escaped();
110 $editGroupDescription = Html::input(
111 'tp-agg-desc',
112 $desc,
113 'text',
114 [ 'class' => 'tp-aggregategroup-edit-description' ]
115 );
116 $saveButton = Xml::submitButton(
117 $this->msg( 'tpt-aggregategroup-update' )->text(),
118 [ 'class' => 'tp-aggregategroup-update' ]
119 );
120 $cancelButton = Xml::submitButton(
121 $this->msg( 'tpt-aggregategroup-update-cancel' )->text(),
122 [ 'class' => 'tp-aggregategroup-update-cancel' ]
123 );
124 $editGroup = Html::rawElement(
125 'div',
126 [ 'class' => 'tp-edit-group hidden' ],
127 $editGroupNameLabel .
128 $editGroupName .
129 '<br />' .
130 $editGroupDescriptionLabel .
131 $editGroupDescription .
132 '<br />' .
133 $languageSelector .
134 '<br />' .
135 $saveButton .
136 $cancelButton
137 );
138 }
139
140 // Not calling $parent->getGroups() because it has done filtering already
141 $subGroups = TranslateMetadata::getSubgroups( $id );
142 $shouldExpand = count( $subGroups ) <= 3;
143 $subGroupsId = $this->htmlIdForGroup( $group->getId(), 'tp-subgroup-' );
144
145 // Aggregate Group info div
146 $groupName = Html::rawElement(
147 'h2',
148 [ 'class' => 'tp-name' ],
149 $this->getGroupToggleIcon( $subGroupsId, $shouldExpand ) . htmlspecialchars( $label ) . $edit . $remove
150 );
151 $groupDesc = Html::element(
152 'p',
153 [ 'class' => 'tp-desc' ],
154 $desc
155 );
156 $groupInfo = Html::rawElement(
157 'div',
158 [ 'class' => 'tp-display-group' ],
159 $groupName . $groupDesc
160 );
161
162 $out = Html::openElement(
163 'div',
164 [
165 'class' => 'mw-tpa-group js-mw-tpa-group' . ( $shouldExpand ? ' mw-tpa-group-open' : '' ),
166 'data-groupid' => $id,
167 'data-id' => $this->htmlIdForGroup( $group->getId() )
168 ]
169 );
170 $out .= $groupInfo;
171 $out .= $editGroup;
172 $out .= Html::openElement( 'div', [ 'class' => 'tp-sub-groups', 'id' => $subGroupsId ] );
173 $out .= $this->listSubgroups( $id, $subGroups );
174 $out .= Html::closeElement( 'div' );
175 $out .= '</div>';
176
177 return $out;
178 }
179
181 private function showAggregateGroups( array $aggregates ): void {
182 $out = $this->getOutput();
183 $out->addModules( 'ext.translate.special.aggregategroups' );
184
185 $nojs = Html::errorBox(
186 $this->msg( 'tux-nojs' )->plain(),
187 '',
188 'tux-nojs'
189 );
190
191 $out->addHTML( $nojs );
192
193 // Add new group if user has permissions
194 if ( $this->hasPermission ) {
195 $out->addHTML(
196 "<a class='tpt-add-new-group' href='#'>" .
197 $this->msg( 'tpt-aggregategroup-add-new' )->escaped() .
198 '</a>'
199 );
200 $languageSelector = $this->getLanguageSelector( 'add', '-' );
201 $newGroupNameLabel = $this->msg( 'tpt-aggregategroup-new-name' )->escaped();
202 $newGroupName = Html::element( 'input', [ 'class' => 'tp-aggregategroup-add-name', 'maxlength' => '200' ] );
203 $newGroupDescriptionLabel = $this->msg( 'tpt-aggregategroup-new-description' )->escaped();
204 $newGroupDescription = Html::element( 'input', [ 'class' => 'tp-aggregategroup-add-description' ] );
205 $saveButton = Html::element(
206 'input',
207 [
208 'type' => 'button',
209 'value' => $this->msg( 'tpt-aggregategroup-save' )->text(),
210 'id' => 'tpt-aggregategroups-save',
211 'class' => 'tp-aggregate-save-button'
212 ]
213 );
214 $closeButton = Html::element(
215 'input',
216 [
217 'type' => 'button',
218 'value' => $this->msg( 'tpt-aggregategroup-close' )->text(),
219 'id' => 'tpt-aggregategroups-close'
220 ]
221 );
222 $newGroupDiv = Html::rawElement(
223 'div',
224 [ 'class' => 'tpt-add-new-group hidden' ],
225 "$newGroupNameLabel $newGroupName<br />" .
226 "$newGroupDescriptionLabel $newGroupDescription<br />" .
227 "$languageSelector <br />"
228 . $saveButton
229 . $closeButton
230 );
231 $out->addHTML( $newGroupDiv );
232 }
233
234 $out->addHTML( Html::openElement( 'div', [ 'class' => 'mw-tpa-groups' ] ) );
235 foreach ( $aggregates as $group ) {
236 $out->addHTML( $this->showAggregateGroup( $group ) );
237 }
238 $out->addHTML( Html::closeElement( 'div' ) );
239 }
240
241 private function listSubgroups( string $groupId, array $subGroupIds ): string {
242 $id = $this->htmlIdForGroup( $groupId, 'mw-tpa-grouplist-' );
243 $out = Html::openElement( 'ol', [ 'id' => $id ] );
244
245 // Get the respective groups and sort them
246 $subgroups = MessageGroups::getGroupsById( $subGroupIds );
247 '@phan-var WikiPageMessageGroup[] $subgroups';
248 uasort( $subgroups, [ MessageGroups::class, 'groupLabelSort' ] );
249
250 // Avoid potentially thousands of separate database queries
251 $lb = $this->linkBatchFactory->newLinkBatch();
252 foreach ( $subgroups as $group ) {
253 $lb->addObj( $group->getTitle() );
254 }
255 $lb->setCaller( __METHOD__ );
256 $lb->execute();
257
258 // Add missing invalid group ids back, not returned by getGroupsById
259 foreach ( $subGroupIds as $id ) {
260 if ( !isset( $subgroups[$id] ) ) {
261 $subgroups[$id] = null;
262 }
263 }
264
265 foreach ( $subgroups as $id => $group ) {
266 $remove = '';
267 if ( $this->hasPermission ) {
268 $remove = Html::element(
269 'span',
270 [ 'class' => 'tp-aggregate-remove-button', 'data-groupid' => $id ]
271 );
272 }
273
274 if ( $group ) {
275 $text = $this->getLinkRenderer()->makeKnownLink( $group->getTitle() );
276 $note = htmlspecialchars( MessageGroups::getPriority( $id ) );
277 } else {
278 $text = htmlspecialchars( $id );
279 $note = $this->msg( 'tpt-aggregategroup-invalid-group' )->escaped();
280 }
281
282 $out .= Html::rawElement( 'li', [], "$text$remove $note" );
283 }
284 $out .= Html::closeElement( 'ol' );
285
286 return $out;
287 }
288
289 private function htmlIdForGroup( string $groupId, string $prefix = '' ): string {
290 $id = sha1( $groupId );
291 $id = substr( $id, 5, 8 );
292
293 return $prefix . $id;
294 }
295
296 private function getGroupToggleIcon( string $targetElementId, bool $shouldExpand ): string {
297 if ( $shouldExpand ) {
298 $title = $this->msg( 'tpt-aggregategroup-collapse-group' )->plain();
299 } else {
300 $title = $this->msg( 'tpt-aggregategroup-expand-group' )->plain();
301 }
302
303 return Html::rawElement(
304 'button',
305 [
306 'type' => 'button',
307 'title' => $title,
308 'class' => 'js-tp-toggle-groups tp-toggle-group-icon',
309 'aria-expanded' => $shouldExpand ? 'true' : 'false',
310 'aria-controls' => $targetElementId
311 ]
312 );
313 }
314
315 private function getLanguageSelector( string $action, string $languageToSelect ): string {
316 if ( $this->languageSelector == null ) {
317 // This should be set according to UI language
318 $this->languageSelector = Utilities::getLanguageSelector(
319 $this->getContext()->getLanguage()->getCode(), '-'
320 );
321 }
322
323 $this->languageSelector->setAttribute( 'class', "tp-aggregategroup-$action-source-language" );
324 $this->languageSelector->setDefault( $languageToSelect );
325 $selector = $this->languageSelector->getHTML();
326
327 $languageSelectorLabel = $this->msg( 'tpt-aggregategroup-select-source-language' )->escaped();
328 return $languageSelectorLabel . $selector;
329 }
330}
Groups multiple message groups together as one group.
static getPriority( $group)
We want to de-emphasize time sensitive groups like news for 2009.
static getGroupsByType(string $type)
Get only groups of specific type (class).
static getGroupsById(array $ids, bool $skipMeta=false)
Get message groups for corresponding message group ids.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
getDescription(IContextSource $context=null)
Returns a longer description about the group.
getLabel(IContextSource $context=null)
Returns the human readable label (as plain text).
getId()
Returns the unique identifier for this group.
Wraps the translatable page sections into a message group.