27 private JobQueueGroup $jobQueueGroup;
28 protected static string $right =
'translate-manage';
29 private const NO_LANGUAGE_CODE =
'-';
33 public function __construct(
36 JobQueueGroup $jobQueueGroup,
40 parent::__construct( $main, $action );
41 $this->jobQueueGroup = $jobQueueGroup;
42 $this->messageGroupMetadata = $messageGroupMetadata;
43 $this->aggregateGroupManager = $aggregateGroupManager;
46 public function execute():
void {
47 $this->checkUserRightsAny( self::$right );
48 $block = $this->getUser()->getBlock();
49 if ( $block && $block->isSitewide() ) {
50 $this->dieBlocked( $block );
53 $params = $this->extractRequestParams();
54 $action = $params[
'do'];
56 if ( $action ===
'associate' || $action ===
'dissociate' ) {
58 if ( !isset( $params[
'group'] ) ) {
59 $this->dieWithError( [
'apierror-missingparam',
'group' ] );
61 if ( !isset( $params[
'aggregategroup'] ) ) {
62 $this->dieWithError( [
'apierror-missingparam',
'aggregategroup' ] );
64 $aggregateGroup = $params[
'aggregategroup'];
65 $subgroups = $this->messageGroupMetadata->getSubgroups( $aggregateGroup );
66 if ( $subgroups ===
null ) {
70 $this->dieWithError(
'apierror-translate-invalidaggregategroup',
'invalidaggregategroup' );
73 $subgroupId = $params[
'group'];
77 if ( $action ===
'associate' ) {
78 if ( !$this->aggregateGroupManager->supportsAggregation( $group ) ) {
79 $this->dieWithError(
'apierror-translate-invalidgroup',
'invalidgroup' );
82 $messageGroupLanguage = $group->getSourceLanguage();
83 $aggregateGroupLanguage = $this->messageGroupMetadata->get( $aggregateGroup,
'sourcelanguagecode' );
85 if ( $aggregateGroupLanguage !==
false && $messageGroupLanguage !== $aggregateGroupLanguage ) {
86 $this->dieWithError( [
87 'apierror-translate-grouplanguagemismatch',
88 $messageGroupLanguage,
89 $aggregateGroupLanguage
92 $subgroups[] = $subgroupId;
93 $subgroups = array_unique( $subgroups );
94 $output[
'groupUrl' ] = $this->aggregateGroupManager->getTargetTitleByGroup( $group )->getFullURL();
95 } elseif ( $action ===
'dissociate' ) {
97 $subgroups = array_flip( $subgroups );
98 unset( $subgroups[$subgroupId] );
99 $subgroups = array_flip( $subgroups );
102 $this->messageGroupMetadata->setSubgroups( $aggregateGroup, $subgroups );
105 'aggregategroup' => $this->messageGroupMetadata->get( $aggregateGroup,
'name' ),
106 'aggregategroup-id' => $aggregateGroup,
112 $title = $this->aggregateGroupManager->getTargetTitleByGroupId( $subgroupId );
114 $entry =
new ManualLogEntry(
'pagetranslation', $action );
115 $entry->setPerformer( $this->getUser() );
116 $entry->setTarget( $title );
119 $entry->setParameters( $logParams );
121 $logId = $entry->insert();
122 $entry->publish( $logId );
123 } elseif ( $action ===
'remove' ) {
124 if ( !isset( $params[
'aggregategroup'] ) ) {
125 $this->dieWithError( [
'apierror-missingparam',
'aggregategroup' ] );
128 $aggregateGroupId = $params[
'aggregategroup'];
132 'apierror-translate-invalidaggregategroupname',
133 'invalidaggregategroupname'
137 $this->messageGroupMetadata->deleteGroup( $params[
'aggregategroup'] );
138 $logger = LoggerFactory::getInstance(
'Translate' );
140 'Aggregate group {groupId} has been deleted.',
141 [
'groupId' => $aggregateGroupId ]
143 } elseif ( $action ===
'add' ) {
144 if ( !isset( $params[
'groupname'] ) ) {
145 $this->dieWithError( [
'apierror-missingparam',
'groupname' ] );
147 $name = trim( $params[
'groupname'] );
148 if ( strlen( $name ) === 0 ) {
150 'apierror-translate-invalidaggregategroupname',
151 'invalidaggregategroupname'
155 if ( !isset( $params[
'groupdescription'] ) ) {
156 $this->dieWithError( [
'apierror-missingparam',
'groupdescription' ] );
158 $desc = trim( $params[
'groupdescription'] );
160 $aggregateGroupId = self::generateAggregateGroupId( $name );
165 $this->dieWithError(
'apierror-translate-duplicateaggregategroup',
'duplicateaggregategroup' );
173 $tempId = $aggregateGroupId .
'-' . $i;
176 }
while ( $idExists );
177 $aggregateGroupId = $tempId;
179 $sourceLanguageCode = trim( $params[
'groupsourcelanguagecode'] );
181 $this->messageGroupMetadata->set( $aggregateGroupId,
'name', $name );
182 $this->messageGroupMetadata->set( $aggregateGroupId,
'description', $desc );
183 if ( $sourceLanguageCode !== self::NO_LANGUAGE_CODE ) {
184 $this->messageGroupMetadata->set( $aggregateGroupId,
'sourcelanguagecode', $sourceLanguageCode );
186 $this->messageGroupMetadata->setSubgroups( $aggregateGroupId, [] );
189 $output[
'groups'] = $this->getIncludableGroups();
190 $output[
'aggregategroupId'] = $aggregateGroupId;
192 } elseif ( $action ===
'update' ) {
193 if ( !isset( $params[
'groupname'] ) ) {
194 $this->dieWithError( [
'apierror-missingparam',
'groupname' ] );
196 $name = trim( $params[
'groupname'] );
197 if ( strlen( $name ) === 0 ) {
199 'apierror-translate-invalidaggregategroupname',
200 'invalidaggregategroupname'
203 $desc = trim( $params[
'groupdescription'] );
204 $aggregateGroupId = $params[
'aggregategroup'];
205 $newLanguageCode = trim( $params[
'groupsourcelanguagecode'] );
207 $oldName = $this->messageGroupMetadata->get( $aggregateGroupId,
'name' );
208 $oldDesc = $this->messageGroupMetadata->get( $aggregateGroupId,
'description' );
209 $currentLanguageCode = $this->messageGroupMetadata->get( $aggregateGroupId,
'sourcelanguagecode' );
211 if ( $newLanguageCode !== self::NO_LANGUAGE_CODE && $newLanguageCode !== $currentLanguageCode ) {
212 $groupsWithDifferentLanguage =
213 $this->getGroupsWithDifferentLanguage( $aggregateGroupId, $newLanguageCode );
215 if ( count( $groupsWithDifferentLanguage ) ) {
216 $this->dieWithError( [
217 'apierror-translate-messagegroup-aggregategrouplanguagemismatch',
218 implode(
', ', $groupsWithDifferentLanguage ),
220 count( $groupsWithDifferentLanguage )
227 if ( $exists && $oldName !== $name ) {
228 $this->dieWithError(
'apierror-translate-duplicateaggregategroup',
'duplicateaggregategroup' );
233 && $oldDesc === $desc
234 && $newLanguageCode === $currentLanguageCode
236 $this->dieWithError(
'apierror-translate-invalidupdate',
'invalidupdate' );
238 $this->messageGroupMetadata->set( $aggregateGroupId,
'name', $name );
239 $this->messageGroupMetadata->set( $aggregateGroupId,
'description', $desc );
240 if ( $newLanguageCode === self::NO_LANGUAGE_CODE ) {
241 $this->messageGroupMetadata->clearMetadata( $aggregateGroupId, [
'sourcelanguagecode' ] );
243 $this->messageGroupMetadata->set( $aggregateGroupId,
'sourcelanguagecode', $newLanguageCode );
248 $output[
'result'] =
'ok';
249 $this->getResult()->addValue(
null, $this->getModuleName(), $output );
251 MessageGroups::singleton()->recache();
252 $this->jobQueueGroup->push( RebuildMessageIndexJob::newJob() );
260 private function getGroupsWithDifferentLanguage(
261 string $aggregateGroupId,
262 string $sourceLanguageCode
264 $groupsWithDifferentLanguage = [];
265 $subgroups = $this->messageGroupMetadata->getSubgroups( $aggregateGroupId );
266 foreach ( $subgroups as $group ) {
268 $messageGroupLanguage = $messageGroup->getSourceLanguage();
269 if ( $messageGroupLanguage !== $sourceLanguageCode ) {
270 $groupsWithDifferentLanguage[] = $messageGroup->getLabel();
274 return $groupsWithDifferentLanguage;
277 protected function generateAggregateGroupId(
string $aggregateGroupName,
string $prefix =
'agg-' ):
string {
279 if ( strlen( $aggregateGroupName ) + strlen( $prefix ) >= 200 ) {
280 return $prefix . substr( sha1( $aggregateGroupName ), 0, 5 );
282 $pattern =
'/[\x00-\x1f\x23\x27\x2c\x2e\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/i';
283 return $prefix . preg_replace( $pattern,
'_', $aggregateGroupName );
287 public function isWriteMode():
bool {
291 public function needsToken():
string {
295 protected function getAllowedParams(): array {
298 ParamValidator::PARAM_TYPE => [
'associate',
'dissociate',
'remove',
'add',
'update' ],
299 ParamValidator::PARAM_REQUIRED =>
true,
301 'aggregategroup' => [
302 ParamValidator::PARAM_TYPE =>
'string',
306 ParamValidator::PARAM_TYPE =>
'string',
309 ParamValidator::PARAM_TYPE =>
'string',
311 'groupdescription' => [
312 ParamValidator::PARAM_TYPE =>
'string',
314 'groupsourcelanguagecode' => [
315 ParamValidator::PARAM_TYPE =>
'string',
316 ParamValidator::PARAM_DEFAULT => self::NO_LANGUAGE_CODE,
319 ParamValidator::PARAM_TYPE =>
'string',
320 ParamValidator::PARAM_REQUIRED =>
true,
325 protected function getExamplesMessages(): array {
327 'action=aggregategroups&do=associate&group=groupId&aggregategroup=aggregateGroupId'
328 =>
'apihelp-aggregategroups-example-1',
332 private function getIncludableGroups(): array {
335 foreach ( $groups as $group ) {
336 if ( $this->aggregateGroupManager->supportsAggregation( $group ) ) {
337 $pages[$group->getId()] = $group->getLabel( $this->getContext() );