22 private TitleFactory $titleFactory;
25 private const MESSAGE_GROUP_PREFIX =
'agg-';
27 public function __construct(
28 TitleFactory $titleFactory,
31 $this->titleFactory = $titleFactory;
32 $this->messageGroupMetadata = $messageGroupMetadata;
35 public function supportsAggregation(
MessageGroup $group ):
bool {
39 public function getTargetTitleByGroupId(
string $groupId ): Title {
47 return $this->titleFactory->newFromText(
"Special:Translate/$groupId" );
53 $relatedGroupPage = $group->getRelatedPage();
54 if ( !$relatedGroupPage ) {
55 throw new RuntimeException(
"No related page found for group " . $group->
getId() );
58 return $this->titleFactory->newFromLinkTarget( $relatedGroupPage );
61 public function add(
string $name,
string $description, ?
string $languageCode ): string {
63 if ( MessageGroups::labelExists( $name ) ) {
64 throw new DuplicateAggregateGroupException( $name );
67 $aggregateGroupId = $this->generateAggregateGroupId( $name );
70 $this->messageGroupMetadata->set( $aggregateGroupId,
'name', $name );
71 $this->messageGroupMetadata->set( $aggregateGroupId,
'description', $description );
72 if ( $languageCode ) {
74 $this->messageGroupMetadata->set( $aggregateGroupId,
'sourcelanguagecode', $languageCode );
76 $this->messageGroupMetadata->setSubgroups( $aggregateGroupId, [] );
78 return $aggregateGroupId;
86 public function associate(
string $aggregateGroupId, array $newSubgroupIds ): array {
87 $existingSubgroupIds = $this->getSubgroups( $aggregateGroupId );
88 $newSubgroups = MessageGroups::getGroupsById( $newSubgroupIds );
90 $missingGroupIds = $this->findMissingGroupIds( $newSubgroups, $newSubgroupIds );
91 $invalidGroupIds = [];
92 foreach ( $newSubgroups as $subGroup ) {
93 if ( !$this->supportsAggregation( $subGroup ) ) {
94 $invalidGroupIds[] = $subGroup->getId();
98 if ( $missingGroupIds || $invalidGroupIds ) {
99 $invalidGroupIds = array_unique( array_merge( $missingGroupIds, $invalidGroupIds ) );
100 throw new AggregateGroupAssociationFailure( $invalidGroupIds );
103 $aggregateGroupLanguage = $this->messageGroupMetadata->get( $aggregateGroupId,
'sourcelanguagecode' );
104 if ( $aggregateGroupLanguage !==
false ) {
106 $sourceLanguageMismatchGroupIds = [];
107 foreach ( $newSubgroups as $subGroup ) {
108 if ( $subGroup->getSourceLanguage() !== $aggregateGroupLanguage ) {
109 $sourceLanguageMismatchGroupIds[] = $subGroup->getId();
113 if ( $sourceLanguageMismatchGroupIds ) {
114 throw new AggregateGroupLanguageMismatchException(
115 $sourceLanguageMismatchGroupIds,
116 $aggregateGroupLanguage
121 $allSubgroupIds = array_unique( array_merge( $existingSubgroupIds, $newSubgroupIds ) );
122 if ( array_diff( $newSubgroupIds, $existingSubgroupIds ) === [] ) {
127 $this->messageGroupMetadata->setSubgroups( $aggregateGroupId, $allSubgroupIds );
128 return $newSubgroupIds;
136 public function disassociate(
string $aggregateGroupId, array $subgroupIds ): array {
137 $existingSubGroupIds = $this->getSubgroups( $aggregateGroupId );
138 $remainingSubGroupIds = array_diff( $existingSubGroupIds, $subgroupIds );
139 $this->messageGroupMetadata->setSubgroups( $aggregateGroupId, $remainingSubGroupIds );
140 return array_diff( $existingSubGroupIds, $remainingSubGroupIds );
146 foreach ( $groups as $group ) {
147 if ( $this->supportsAggregation( $group ) ) {
161 $this->messageGroupMetadata->preloadGroups( array_keys( $groupsPreload ), __METHOD__ );
163 $groups = MessageGroups::getAllGroups();
164 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
166 foreach ( $groups as $group ) {
169 $subgroups = $this->messageGroupMetadata->getSubgroups( $group->getId() );
170 if ( $subgroups !==
null ) {
171 $aggregates[] = $group;
180 private function findMissingGroupIds( array $subGroups, array $subGroupIds ): array {
181 $existingIds = array_map( static fn ( $group ) => $group->getId(), $subGroups );
182 return array_diff( $subGroupIds, $existingIds );
185 private function getSubgroups(
string $aggregateGroupId ): array {
186 $existingSubGroupIds = $this->messageGroupMetadata->getSubgroups( $aggregateGroupId );
187 if ( $existingSubGroupIds !==
null ) {
190 return $existingSubGroupIds;
193 throw new AggregateGroupNotFoundException( $aggregateGroupId );
196 private function generateAggregateGroupId(
string $name ): string {
198 if ( strlen( $name ) + strlen( self::MESSAGE_GROUP_PREFIX ) >= 200 ) {
199 $aggregateGroupId = self::MESSAGE_GROUP_PREFIX . substr( sha1( $name ), 0, 5 );
201 $pattern =
'/[\x00-\x1f\x23\x27\x2c\x2e\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/i';
202 $aggregateGroupId = self::MESSAGE_GROUP_PREFIX . preg_replace( $pattern,
'_', $name );
206 $idExists = MessageGroups::getGroup( $aggregateGroupId );
210 $tempId = $aggregateGroupId .
'-' . $i;
211 $idExists = MessageGroups::getGroup( $tempId );
213 }
while ( $idExists );
214 $aggregateGroupId = $tempId;
217 return $aggregateGroupId;
Wraps the translatable page sections into a message group.