24 private bool $hasPermission =
false;
25 private LinkBatchFactory $linkBatchFactory;
26 private ?XmlSelect $languageSelector =
null;
30 public function __construct(
31 LinkBatchFactory $linkBatchFactory,
35 parent::__construct(
'AggregateGroups',
'translate-manage' );
36 $this->linkBatchFactory = $linkBatchFactory;
37 $this->messageGroupMetadata = $messageGroupMetadata;
38 $this->aggregateGroupManager = $aggregateGroupManager;
41 protected function getGroupName():
string {
45 public function execute( $parameters ):
void {
47 $this->addHelpLink(
'Help:Extension:Translate/Page translation administration' );
49 $out = $this->getOutput();
50 $out->addModuleStyles( [
51 'ext.translate.specialpages.styles',
52 'mediawiki.codex.messagebox.styles',
56 if ( $this->getUser()->isAllowed(
'translate-manage' ) ) {
57 $this->hasPermission =
true;
61 $this->messageGroupMetadata->preloadGroups( array_keys( $groupsPreload ), __METHOD__ );
64 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
67 foreach ( $groups as $group ) {
68 if ( $this->aggregateGroupManager->supportsAggregation( $group ) ) {
72 $subgroups = $this->messageGroupMetadata->getSubgroups( $group->getId() );
73 if ( $subgroups !==
null ) {
74 $aggregates[] = $group;
81 $out->addWikiMsg(
'tpt-list-nopages' );
86 $this->showAggregateGroups( $aggregates );
90 $id = $group->
getId();
93 $sourceLanguage = $this->messageGroupMetadata->get( $id,
'sourcelanguagecode' );
100 if ( $this->hasPermission ) {
102 $edit = Html::element(
'span', [
'class' =>
'tp-aggregate-edit-ag-button' ] );
103 $remove = Html::element(
'span', [
'class' =>
'tp-aggregate-remove-ag-button' ] );
106 $languageSelector = $this->getLanguageSelector(
'edit', $sourceLanguage ?:
'-' );
108 $editGroupNameLabel = $this->msg(
'tpt-aggregategroup-edit-name' )->escaped();
109 $editGroupName = Html::input(
113 [
'class' =>
'tp-aggregategroup-edit-name',
'maxlength' =>
'200' ]
115 $editGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-edit-description' )->escaped();
116 $editGroupDescription = Html::input(
120 [
'class' =>
'tp-aggregategroup-edit-description' ]
122 $saveButton = Html::submitButton(
123 $this->msg(
'tpt-aggregategroup-update' )->text(),
124 [
'class' =>
'tp-aggregategroup-update' ]
126 $cancelButton = Html::submitButton(
127 $this->msg(
'tpt-aggregategroup-update-cancel' )->text(),
128 [
'class' =>
'tp-aggregategroup-update-cancel' ]
130 $editGroup = Html::rawElement(
132 [
'class' =>
'tp-edit-group hidden' ],
133 $editGroupNameLabel .
136 $editGroupDescriptionLabel .
137 $editGroupDescription .
147 $subGroups = $this->messageGroupMetadata->getSubgroups( $id );
148 $shouldExpand = count( $subGroups ) <= 3;
149 $subGroupsId = $this->htmlIdForGroup( $group->
getId(),
'tp-subgroup-' );
152 $groupName = Html::rawElement(
154 [
'class' =>
'tp-name' ],
155 $this->getGroupToggleIcon( $subGroupsId, $shouldExpand ) . htmlspecialchars( $label ) . $edit . $remove
157 $groupDesc = Html::element(
159 [
'class' =>
'tp-desc' ],
162 $groupInfo = Html::rawElement(
164 [
'class' =>
'tp-display-group' ],
165 $groupName . $groupDesc
168 $out = Html::openElement(
171 'class' =>
'mw-tpa-group js-mw-tpa-group' . ( $shouldExpand ?
' mw-tpa-group-open' :
'' ),
172 'data-groupid' => $id,
173 'data-id' => $this->htmlIdForGroup( $group->
getId() )
178 $out .= Html::openElement(
'div', [
'class' =>
'tp-sub-groups',
'id' => $subGroupsId ] );
179 $out .= $this->listSubgroups( $id, $subGroups );
180 $out .= Html::closeElement(
'div' );
187 private function showAggregateGroups( array $aggregates ):
void {
188 $out = $this->getOutput();
189 $out->addModules(
'ext.translate.special.aggregategroups' );
191 $nojs = Html::errorBox(
192 $this->msg(
'tux-nojs' )->escaped(),
197 $out->addHTML( $nojs );
200 if ( $this->hasPermission ) {
202 "<a class='tpt-add-new-group' href='#'>" .
203 $this->msg(
'tpt-aggregategroup-add-new' )->escaped() .
206 $languageSelector = $this->getLanguageSelector(
'add',
'-' );
207 $newGroupNameLabel = $this->msg(
'tpt-aggregategroup-new-name' )->escaped();
208 $newGroupName = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-name',
'maxlength' =>
'200' ] );
209 $newGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-new-description' )->escaped();
210 $newGroupDescription = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-description' ] );
211 $saveButton = Html::element(
215 'value' => $this->msg(
'tpt-aggregategroup-save' )->text(),
216 'id' =>
'tpt-aggregategroups-save',
217 'class' =>
'tp-aggregate-save-button'
220 $closeButton = Html::element(
224 'value' => $this->msg(
'tpt-aggregategroup-close' )->text(),
225 'id' =>
'tpt-aggregategroups-close'
228 $newGroupDiv = Html::rawElement(
230 [
'class' =>
'tpt-add-new-group hidden' ],
231 "$newGroupNameLabel $newGroupName<br />" .
232 "$newGroupDescriptionLabel $newGroupDescription<br />" .
233 "$languageSelector <br />"
237 $out->addHTML( $newGroupDiv );
240 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-tpa-groups' ] ) );
241 foreach ( $aggregates as $group ) {
242 $out->addHTML( $this->showAggregateGroup( $group ) );
244 $out->addHTML( Html::closeElement(
'div' ) );
247 private function listSubgroups(
string $groupId, array $subGroupIds ):
string {
248 $id = $this->htmlIdForGroup( $groupId,
'mw-tpa-grouplist-' );
249 $out = Html::openElement(
'ol', [
'id' => $id ] );
253 uasort( $subgroups, [ MessageGroups::class,
'groupLabelSort' ] );
257 $lb = $this->linkBatchFactory->newLinkBatch();
258 foreach ( $subgroups as $group ) {
259 $subGroupId = $group->getId();
260 $groupCache[ $subGroupId ] = $this->aggregateGroupManager->getTargetTitleByGroupId( $subGroupId );
261 $lb->addObj( $groupCache[ $subGroupId ] );
263 $lb->setCaller( __METHOD__ );
267 foreach ( $subGroupIds as $id ) {
268 if ( !isset( $subgroups[$id] ) ) {
269 $subgroups[$id] =
null;
273 foreach ( $subgroups as $id => $group ) {
275 if ( $this->hasPermission ) {
276 $remove = Html::element(
278 [
'class' =>
'tp-aggregate-remove-button',
'data-groupid' => $id ]
283 $text = $this->getLinkRenderer()->makeKnownLink( $groupCache[ $group->getId() ], $group->getLabel() );
286 $text = htmlspecialchars( $id );
287 $note = $this->msg(
'tpt-aggregategroup-invalid-group' )->escaped();
290 $out .= Html::rawElement(
'li', [],
"$text$remove $note" );
292 $out .= Html::closeElement(
'ol' );
297 private function htmlIdForGroup(
string $groupId,
string $prefix =
'' ):
string {
298 $id = sha1( $groupId );
299 $id = substr( $id, 5, 8 );
301 return $prefix . $id;
304 private function getGroupToggleIcon(
string $targetElementId,
bool $shouldExpand ):
string {
305 if ( $shouldExpand ) {
306 $title = $this->msg(
'tpt-aggregategroup-collapse-group' )->plain();
308 $title = $this->msg(
'tpt-aggregategroup-expand-group' )->plain();
311 return Html::rawElement(
316 'class' =>
'js-tp-toggle-groups tp-toggle-group-icon skin-invert',
317 'aria-expanded' => $shouldExpand ?
'true' :
'false',
318 'aria-controls' => $targetElementId
323 private function getLanguageSelector(
string $action,
string $languageToSelect ):
string {
324 if ( $this->languageSelector ==
null ) {
326 $this->languageSelector = Utilities::getLanguageSelector(
327 $this->getContext()->getLanguage()->getCode(),
332 $this->languageSelector->setAttribute(
'class',
"tp-aggregategroup-$action-source-language" );
333 $this->languageSelector->setDefault( $languageToSelect );
334 $selector = $this->languageSelector->getHTML();
336 $languageSelectorLabel = $this->msg(
'tpt-aggregategroup-select-source-language' )->escaped();
337 return $languageSelectorLabel . $selector;