27 private $hasPermission =
false;
29 private $linkBatchFactory;
31 private $languageSelector;
33 public function __construct( LinkBatchFactory $linkBatchFactory ) {
34 parent::__construct(
'AggregateGroups',
'translate-manage' );
35 $this->linkBatchFactory = $linkBatchFactory;
38 protected function getGroupName():
string {
42 public function execute( $parameters ) {
44 $this->addHelpLink(
'Help:Extension:Translate/Page translation administration' );
46 $out = $this->getOutput();
47 $out->addModuleStyles(
'ext.translate.specialpages.styles' );
50 if ( $this->getUser()->isAllowed(
'translate-manage' ) ) {
51 $this->hasPermission =
true;
55 TranslateMetadata::preloadGroups( array_keys( $groupsPreload ), __METHOD__ );
58 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
61 foreach ( $groups as $group ) {
66 $subgroups = TranslateMetadata::getSubgroups( $group->getId() );
67 if ( $subgroups !==
null ) {
68 $aggregates[] = $group;
75 $out->addWikiMsg(
'tpt-list-nopages' );
80 $this->showAggregateGroups( $aggregates );
84 $id = $group->
getId();
87 $sourceLanguage = TranslateMetadata::get( $id,
'sourcelanguagecode' );
96 if ( $this->hasPermission ) {
98 $edit = Html::element(
'span', [
'class' =>
'tp-aggregate-edit-ag-button' ] );
99 $remove = Html::element(
'span', [
'class' =>
'tp-aggregate-remove-ag-button' ] );
102 $languageSelector = $this->getLanguageSelector(
'edit', $sourceLanguage ?:
'-' );
104 $editGroupNameLabel = $this->msg(
'tpt-aggregategroup-edit-name' )->escaped();
105 $editGroupName = Html::input(
109 [
'class' =>
'tp-aggregategroup-edit-name',
'maxlength' =>
'200' ]
111 $editGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-edit-description' )->escaped();
112 $editGroupDescription = Html::input(
116 [
'class' =>
'tp-aggregategroup-edit-description' ]
118 $saveButton = Xml::submitButton(
119 $this->msg(
'tpt-aggregategroup-update' )->text(),
120 [
'class' =>
'tp-aggregategroup-update' ]
122 $cancelButton = Xml::submitButton(
123 $this->msg(
'tpt-aggregategroup-update-cancel' )->text(),
124 [
'class' =>
'tp-aggregategroup-update-cancel' ]
126 $editGroup = Html::rawElement(
128 [
'class' =>
'tp-edit-group hidden' ],
129 $editGroupNameLabel .
132 $editGroupDescriptionLabel .
133 $editGroupDescription .
142 $select = Html::input(
'tp-subgroups-input',
'',
'text', [
'class' =>
'tp-group-input' ] );
143 $addButton = Html::element(
'input',
146 'value' => $this->msg(
'tpt-aggregategroup-add' )->text(),
147 'class' =>
'tp-aggregate-add-button'
153 $subGroups = TranslateMetadata::getSubgroups( $id );
154 $shouldExpand = count( $subGroups ) <= 3;
155 $subGroupsId = $this->htmlIdForGroup( $group->
getId(),
'tp-subgroup-' );
158 $groupName = Html::rawElement(
160 [
'class' =>
'tp-name' ],
161 $this->getGroupToggleIcon( $subGroupsId, $shouldExpand ) . htmlspecialchars( $label ) . $edit . $remove
163 $groupDesc = Html::element(
165 [
'class' =>
'tp-desc' ],
168 $groupInfo = Html::rawElement(
170 [
'class' =>
'tp-display-group' ],
171 $groupName . $groupDesc
174 $out = Html::openElement(
177 'class' =>
'mw-tpa-group js-mw-tpa-group' . ( $shouldExpand ?
' mw-tpa-group-open' :
'' ),
178 'data-groupid' => $id,
179 'data-id' => $this->htmlIdForGroup( $group->
getId() )
184 $out .= Html::openElement(
'div', [
'class' =>
'tp-sub-groups',
'id' => $subGroupsId ] );
185 $out .= $this->listSubgroups( $id, $subGroups );
186 $out .= $select . $addButton;
187 $out .= Html::closeElement(
'div' );
194 private function showAggregateGroups( array $aggregates ):
void {
195 $out = $this->getOutput();
196 $out->addModules(
'ext.translate.special.aggregategroups' );
198 $nojs = Html::errorBox(
199 $this->msg(
'tux-nojs' )->plain(),
204 $out->addHTML( $nojs );
207 if ( $this->hasPermission ) {
209 "<a class='tpt-add-new-group' href='#'>" .
210 $this->msg(
'tpt-aggregategroup-add-new' )->escaped() .
213 $languageSelector = $this->getLanguageSelector(
'add',
'-' );
214 $newGroupNameLabel = $this->msg(
'tpt-aggregategroup-new-name' )->escaped();
215 $newGroupName = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-name',
'maxlength' =>
'200' ] );
216 $newGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-new-description' )->escaped();
217 $newGroupDescription = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-description' ] );
218 $saveButton = Html::element(
222 'value' => $this->msg(
'tpt-aggregategroup-save' )->text(),
223 'id' =>
'tpt-aggregategroups-save',
224 'class' =>
'tp-aggregate-save-button'
227 $closeButton = Html::element(
231 'value' => $this->msg(
'tpt-aggregategroup-close' )->text(),
232 'id' =>
'tpt-aggregategroups-close'
235 $newGroupDiv = Html::rawElement(
237 [
'class' =>
'tpt-add-new-group hidden' ],
238 "$newGroupNameLabel $newGroupName<br />" .
239 "$newGroupDescriptionLabel $newGroupDescription<br />" .
240 "$languageSelector <br />"
244 $out->addHTML( $newGroupDiv );
247 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-tpa-groups' ] ) );
248 foreach ( $aggregates as $group ) {
249 $out->addHTML( $this->showAggregateGroup( $group ) );
251 $out->addHTML( Html::closeElement(
'div' ) );
254 private function listSubgroups(
string $groupId, array $subGroupIds ):
string {
255 $id = $this->htmlIdForGroup( $groupId,
'mw-tpa-grouplist-' );
256 $out = Html::openElement(
'ol', [
'id' => $id ] );
260 '@phan-var WikiPageMessageGroup[] $subgroups';
261 uasort( $subgroups, [ MessageGroups::class,
'groupLabelSort' ] );
264 $lb = $this->linkBatchFactory->newLinkBatch();
265 foreach ( $subgroups as $group ) {
266 $lb->addObj( $group->getTitle() );
268 $lb->setCaller( __METHOD__ );
272 foreach ( $subGroupIds as $id ) {
273 if ( !isset( $subgroups[$id] ) ) {
274 $subgroups[$id] =
null;
278 foreach ( $subgroups as $id => $group ) {
280 if ( $this->hasPermission ) {
281 $remove = Html::element(
283 [
'class' =>
'tp-aggregate-remove-button',
'data-groupid' => $id ]
288 $text = $this->getLinkRenderer()->makeKnownLink( $group->getTitle() );
291 $text = htmlspecialchars( $id );
292 $note = $this->msg(
'tpt-aggregategroup-invalid-group' )->escaped();
295 $out .= Html::rawElement(
'li', [],
"$text$remove $note" );
297 $out .= Html::closeElement(
'ol' );
302 private function htmlIdForGroup(
string $groupId,
string $prefix =
'' ):
string {
303 $id = sha1( $groupId );
304 $id = substr( $id, 5, 8 );
306 return $prefix . $id;
309 private function getGroupToggleIcon(
string $targetElementId,
bool $shouldExpand ):
string {
310 if ( $shouldExpand ) {
311 $title = $this->msg(
'tpt-aggregategroup-collapse-group' )->plain();
313 $title = $this->msg(
'tpt-aggregategroup-expand-group' )->plain();
316 return Html::rawElement(
321 'class' =>
'js-tp-toggle-groups tp-toggle-group-icon',
322 'aria-expanded' => $shouldExpand ?
'true' :
'false',
323 'aria-controls' => $targetElementId
328 private function getLanguageSelector(
string $action,
string $languageToSelect ):
string {
329 if ( $this->languageSelector ==
null ) {
331 $this->languageSelector = Utilities::getLanguageSelector(
332 $this->getContext()->getLanguage()->getCode(),
'-'
336 $this->languageSelector->setAttribute(
'class',
"tp-aggregategroup-$action-source-language" );
337 $this->languageSelector->setDefault( $languageToSelect );
338 $selector = $this->languageSelector->getHTML();
340 $languageSelectorLabel = $this->msg(
'tpt-aggregategroup-select-source-language' )->escaped();
341 return $languageSelectorLabel . $selector;