26 private $hasPermission =
false;
28 private $linkBatchFactory;
30 public function __construct( LinkBatchFactory $linkBatchFactory ) {
31 parent::__construct(
'AggregateGroups',
'translate-manage' );
32 $this->linkBatchFactory = $linkBatchFactory;
35 protected function getGroupName():
string {
39 public function execute( $parameters ) {
41 $this->addHelpLink(
'Help:Extension:Translate/Page translation administration' );
43 $out = $this->getOutput();
44 $out->addModuleStyles(
'ext.translate.specialpages.styles' );
47 if ( $this->getUser()->isAllowed(
'translate-manage' ) ) {
48 $this->hasPermission =
true;
51 $groupsPreload = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
52 TranslateMetadata::preloadGroups( array_keys( $groupsPreload ), __METHOD__ );
54 $groups = MessageGroups::getAllGroups();
55 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
58 foreach ( $groups as $group ) {
63 $subgroups = TranslateMetadata::getSubgroups( $group->getId() );
64 if ( $subgroups !==
null ) {
65 $aggregates[] = $group;
72 $out->addWikiMsg(
'tpt-list-nopages' );
77 $this->showAggregateGroups( $aggregates );
81 $id = $group->
getId();
92 if ( $this->hasPermission ) {
94 $edit = Html::element(
'span', [
'class' =>
'tp-aggregate-edit-ag-button' ] );
95 $remove = Html::element(
'span', [
'class' =>
'tp-aggregate-remove-ag-button' ] );
98 $editGroupNameLabel = $this->msg(
'tpt-aggregategroup-edit-name' )->escaped();
99 $editGroupName = Html::input(
103 [
'class' =>
'tp-aggregategroup-edit-name',
'maxlength' =>
'200' ]
105 $editGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-edit-description' )->escaped();
106 $editGroupDescription = Html::input(
110 [
'class' =>
'tp-aggregategroup-edit-description' ]
112 $saveButton = Xml::submitButton(
113 $this->msg(
'tpt-aggregategroup-update' )->text(),
114 [
'class' =>
'tp-aggregategroup-update' ]
116 $cancelButton = Xml::submitButton(
117 $this->msg(
'tpt-aggregategroup-update-cancel' )->text(),
118 [
'class' =>
'tp-aggregategroup-update-cancel' ]
120 $editGroup = Html::rawElement(
122 [
'class' =>
'tp-edit-group hidden' ],
123 $editGroupNameLabel .
126 $editGroupDescriptionLabel .
127 $editGroupDescription .
133 $select = Html::input(
'tp-subgroups-input',
'',
'text', [
'class' =>
'tp-group-input' ] );
134 $addButton = Html::element(
'input',
137 'value' => $this->msg(
'tpt-aggregategroup-add' )->text(),
138 'class' =>
'tp-aggregate-add-button'
144 $subGroups = TranslateMetadata::getSubgroups( $id );
145 $shouldExpand = count( $subGroups ) <= 3;
146 $subGroupsId = $this->htmlIdForGroup( $group->
getId(),
'tp-subgroup-' );
149 $groupName = Html::rawElement(
151 [
'class' =>
'tp-name' ],
152 $this->getGroupToggleIcon( $subGroupsId, $shouldExpand ) . htmlspecialchars( $label ) . $edit . $remove
154 $groupDesc = Html::element(
156 [
'class' =>
'tp-desc' ],
159 $groupInfo = Html::rawElement(
161 [
'class' =>
'tp-display-group' ],
162 $groupName . $groupDesc
165 $out = Html::openElement(
168 'class' =>
'mw-tpa-group js-mw-tpa-group' . ( $shouldExpand ?
' mw-tpa-group-open' :
'' ),
169 'data-groupid' => $id,
170 'data-id' => $this->htmlIdForGroup( $group->
getId() )
175 $out .= Html::openElement(
'div', [
'class' =>
'tp-sub-groups',
'id' => $subGroupsId ] );
176 $out .= $this->listSubgroups( $id, $subGroups );
177 $out .= $select . $addButton;
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' )->plain(),
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 $newGroupNameLabel = $this->msg(
'tpt-aggregategroup-new-name' )->escaped();
205 $newGroupName = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-name',
'maxlength' =>
'200' ] );
206 $newGroupDescriptionLabel = $this->msg(
'tpt-aggregategroup-new-description' )->escaped();
207 $newGroupDescription = Html::element(
'input', [
'class' =>
'tp-aggregategroup-add-description' ] );
208 $saveButton = Html::element(
212 'value' => $this->msg(
'tpt-aggregategroup-save' )->text(),
213 'id' =>
'tpt-aggregategroups-save',
214 'class' =>
'tp-aggregate-save-button'
217 $closeButton = Html::element(
221 'value' => $this->msg(
'tpt-aggregategroup-close' )->text(),
222 'id' =>
'tpt-aggregategroups-close'
225 $newGroupDiv = Html::rawElement(
227 [
'class' =>
'tpt-add-new-group hidden' ],
228 "$newGroupNameLabel $newGroupName<br />" .
229 "$newGroupDescriptionLabel $newGroupDescription<br />$saveButton $closeButton"
231 $out->addHTML( $newGroupDiv );
235 foreach ( $aggregates as $group ) {
236 $out->addHTML( $this->showAggregateGroup( $group ) );
240 private function listSubgroups(
string $groupId, array $subGroupIds ):
string {
241 $id = $this->htmlIdForGroup( $groupId,
'mw-tpa-grouplist-' );
242 $out = Html::openElement(
'ol', [
'id' => $id ] );
245 $subgroups = MessageGroups::getGroupsById( $subGroupIds );
246 '@phan-var WikiPageMessageGroup[] $subgroups';
247 uasort( $subgroups, [ MessageGroups::class,
'groupLabelSort' ] );
250 $lb = $this->linkBatchFactory->newLinkBatch();
251 foreach ( $subgroups as $group ) {
252 $lb->addObj( $group->getTitle() );
254 $lb->setCaller( __METHOD__ );
258 foreach ( $subGroupIds as $id ) {
259 if ( !isset( $subgroups[$id] ) ) {
260 $subgroups[$id] =
null;
264 foreach ( $subgroups as $id => $group ) {
266 if ( $this->hasPermission ) {
267 $remove = Html::element(
269 [
'class' =>
'tp-aggregate-remove-button',
'data-groupid' => $id ]
274 $text = $this->getLinkRenderer()->makeKnownLink( $group->getTitle() );
275 $note = htmlspecialchars( MessageGroups::getPriority( $id ) );
277 $text = htmlspecialchars( $id );
278 $note = $this->msg(
'tpt-aggregategroup-invalid-group' )->escaped();
281 $out .= Html::rawElement(
'li', [],
"$text$remove $note" );
283 $out .= Html::closeElement(
'ol' );
288 private function htmlIdForGroup(
string $groupId,
string $prefix =
'' ):
string {
289 $id = sha1( $groupId );
290 $id = substr( $id, 5, 8 );
292 return $prefix . $id;
295 private function getGroupToggleIcon(
string $targetElementId,
bool $shouldExpand ):
string {
296 if ( $shouldExpand ) {
297 $title = $this->msg(
'tpt-aggregategroup-collapse-group' )->plain();
299 $title = $this->msg(
'tpt-aggregategroup-expand-group' )->plain();
302 return Html::rawElement(
307 'class' =>
'js-tp-toggle-groups tp-toggle-group-icon',
308 'aria-expanded' => $shouldExpand ?
'true' :
'false',
309 'aria-controls' => $targetElementId
Wraps the translatable page sections into a message group.