25 private bool $hasPermission =
false;
26 private LinkBatchFactory $linkBatchFactory;
27 private ?XmlSelect $languageSelector =
null;
31 public function __construct(
32 LinkBatchFactory $linkBatchFactory,
36 parent::__construct(
'AggregateGroups',
'translate-manage' );
37 $this->linkBatchFactory = $linkBatchFactory;
38 $this->messageGroupMetadata = $messageGroupMetadata;
39 $this->aggregateGroupManager = $aggregateGroupManager;
42 protected function getGroupName():
string {
46 public function execute( $parameters ):
void {
48 $this->addHelpLink(
'Help:Extension:Translate/Page translation administration' );
50 $out = $this->getOutput();
51 $out->addModuleStyles(
'ext.translate.specialpages.styles' );
54 if ( $this->getUser()->isAllowed(
'translate-manage' ) ) {
55 $this->hasPermission =
true;
59 $this->messageGroupMetadata->preloadGroups( array_keys( $groupsPreload ), __METHOD__ );
62 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
65 foreach ( $groups as $group ) {
66 if ( $this->aggregateGroupManager->supportsAggregation( $group ) ) {
70 $subgroups = $this->messageGroupMetadata->getSubgroups( $group->getId() );
71 if ( $subgroups !==
null ) {
72 $aggregates[] = $group;
79 $out->addWikiMsg(
'tpt-list-nopages' );
84 $this->showAggregateGroups( $aggregates );
88 $id = $group->
getId();
91 $sourceLanguage = $this->messageGroupMetadata->get( $id,
'sourcelanguagecode' );
98 if ( $this->hasPermission ) {
100 $edit = Html::element(
'span', [
'class' =>
'tp-aggregate-edit-ag-button' ] );
101 $remove = Html::element(
'span', [
'class' =>
'tp-aggregate-remove-ag-button' ] );
104 $languageSelector = $this->getLanguageSelector(
'edit', $sourceLanguage ?:
'-' );
106 $editGroupNameLabel = $this->msg(
'tpt-aggregategroup-edit-name' )->escaped();
107 $editGroupName = Html::input(
111 [
'class' =>
'tp-aggregategroup-edit-name',
'maxlength' =>
'200' ]
113 $editGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-edit-description' )->escaped();
114 $editGroupDescription = Html::input(
118 [
'class' =>
'tp-aggregategroup-edit-description' ]
120 $saveButton = Xml::submitButton(
121 $this->msg(
'tpt-aggregategroup-update' )->text(),
122 [
'class' =>
'tp-aggregategroup-update' ]
124 $cancelButton = Xml::submitButton(
125 $this->msg(
'tpt-aggregategroup-update-cancel' )->text(),
126 [
'class' =>
'tp-aggregategroup-update-cancel' ]
128 $editGroup = Html::rawElement(
130 [
'class' =>
'tp-edit-group hidden' ],
131 $editGroupNameLabel .
134 $editGroupDescriptionLabel .
135 $editGroupDescription .
145 $subGroups = $this->messageGroupMetadata->getSubgroups( $id );
146 $shouldExpand = count( $subGroups ) <= 3;
147 $subGroupsId = $this->htmlIdForGroup( $group->
getId(),
'tp-subgroup-' );
150 $groupName = Html::rawElement(
152 [
'class' =>
'tp-name' ],
153 $this->getGroupToggleIcon( $subGroupsId, $shouldExpand ) . htmlspecialchars( $label ) . $edit . $remove
155 $groupDesc = Html::element(
157 [
'class' =>
'tp-desc' ],
160 $groupInfo = Html::rawElement(
162 [
'class' =>
'tp-display-group' ],
163 $groupName . $groupDesc
166 $out = Html::openElement(
169 'class' =>
'mw-tpa-group js-mw-tpa-group' . ( $shouldExpand ?
' mw-tpa-group-open' :
'' ),
170 'data-groupid' => $id,
171 'data-id' => $this->htmlIdForGroup( $group->
getId() )
176 $out .= Html::openElement(
'div', [
'class' =>
'tp-sub-groups',
'id' => $subGroupsId ] );
177 $out .= $this->listSubgroups( $id, $subGroups );
178 $out .= Html::closeElement(
'div' );
185 private function showAggregateGroups( array $aggregates ):
void {
186 $out = $this->getOutput();
187 $out->addModules(
'ext.translate.special.aggregategroups' );
189 $nojs = Html::errorBox(
190 $this->msg(
'tux-nojs' )->escaped(),
195 $out->addHTML( $nojs );
198 if ( $this->hasPermission ) {
200 "<a class='tpt-add-new-group' href='#'>" .
201 $this->msg(
'tpt-aggregategroup-add-new' )->escaped() .
204 $languageSelector = $this->getLanguageSelector(
'add',
'-' );
205 $newGroupNameLabel = $this->msg(
'tpt-aggregategroup-new-name' )->escaped();
206 $newGroupName = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-name',
'maxlength' =>
'200' ] );
207 $newGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-new-description' )->escaped();
208 $newGroupDescription = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-description' ] );
209 $saveButton = Html::element(
213 'value' => $this->msg(
'tpt-aggregategroup-save' )->text(),
214 'id' =>
'tpt-aggregategroups-save',
215 'class' =>
'tp-aggregate-save-button'
218 $closeButton = Html::element(
222 'value' => $this->msg(
'tpt-aggregategroup-close' )->text(),
223 'id' =>
'tpt-aggregategroups-close'
226 $newGroupDiv = Html::rawElement(
228 [
'class' =>
'tpt-add-new-group hidden' ],
229 "$newGroupNameLabel $newGroupName<br />" .
230 "$newGroupDescriptionLabel $newGroupDescription<br />" .
231 "$languageSelector <br />"
235 $out->addHTML( $newGroupDiv );
238 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-tpa-groups' ] ) );
239 foreach ( $aggregates as $group ) {
240 $out->addHTML( $this->showAggregateGroup( $group ) );
242 $out->addHTML( Html::closeElement(
'div' ) );
245 private function listSubgroups(
string $groupId, array $subGroupIds ):
string {
246 $id = $this->htmlIdForGroup( $groupId,
'mw-tpa-grouplist-' );
247 $out = Html::openElement(
'ol', [
'id' => $id ] );
251 uasort( $subgroups, [ MessageGroups::class,
'groupLabelSort' ] );
255 $lb = $this->linkBatchFactory->newLinkBatch();
256 foreach ( $subgroups as $group ) {
257 $subGroupId = $group->getId();
258 $groupCache[ $subGroupId ] = $this->aggregateGroupManager->getTargetTitleByGroupId( $subGroupId );
259 $lb->addObj( $groupCache[ $subGroupId ] );
261 $lb->setCaller( __METHOD__ );
265 foreach ( $subGroupIds as $id ) {
266 if ( !isset( $subgroups[$id] ) ) {
267 $subgroups[$id] =
null;
271 foreach ( $subgroups as $id => $group ) {
273 if ( $this->hasPermission ) {
274 $remove = Html::element(
276 [
'class' =>
'tp-aggregate-remove-button',
'data-groupid' => $id ]
281 $text = $this->getLinkRenderer()->makeKnownLink( $groupCache[ $group->getId() ], $group->getLabel() );
284 $text = htmlspecialchars( $id );
285 $note = $this->msg(
'tpt-aggregategroup-invalid-group' )->escaped();
288 $out .= Html::rawElement(
'li', [],
"$text$remove $note" );
290 $out .= Html::closeElement(
'ol' );
295 private function htmlIdForGroup(
string $groupId,
string $prefix =
'' ):
string {
296 $id = sha1( $groupId );
297 $id = substr( $id, 5, 8 );
299 return $prefix . $id;
302 private function getGroupToggleIcon(
string $targetElementId,
bool $shouldExpand ):
string {
303 if ( $shouldExpand ) {
304 $title = $this->msg(
'tpt-aggregategroup-collapse-group' )->plain();
306 $title = $this->msg(
'tpt-aggregategroup-expand-group' )->plain();
309 return Html::rawElement(
314 'class' =>
'js-tp-toggle-groups tp-toggle-group-icon',
315 'aria-expanded' => $shouldExpand ?
'true' :
'false',
316 'aria-controls' => $targetElementId
321 private function getLanguageSelector(
string $action,
string $languageToSelect ):
string {
322 if ( $this->languageSelector ==
null ) {
324 $this->languageSelector = Utilities::getLanguageSelector(
325 $this->getContext()->getLanguage()->getCode(),
330 $this->languageSelector->setAttribute(
'class',
"tp-aggregategroup-$action-source-language" );
331 $this->languageSelector->setDefault( $languageToSelect );
332 $selector = $this->languageSelector->getHTML();
334 $languageSelectorLabel = $this->msg(
'tpt-aggregategroup-select-source-language' )->escaped();
335 return $languageSelectorLabel . $selector;