30 private $jobQueueGroup;
32 protected static $right =
'translate-manage';
33 private const NO_LANGUAGE_CODE =
'-';
35 public function __construct(
38 JobQueueGroup $jobQueueGroup
40 parent::__construct( $main, $action );
41 $this->jobQueueGroup = $jobQueueGroup;
44 public function execute():
void {
45 $this->checkUserRightsAny( self::$right );
46 $block = $this->getUser()->getBlock();
47 if ( $block && $block->isSitewide() ) {
48 $this->dieBlocked( $block );
51 $params = $this->extractRequestParams();
52 $action = $params[
'do'];
54 if ( $action ===
'associate' || $action ===
'dissociate' ) {
56 if ( !isset( $params[
'group'] ) ) {
57 $this->dieWithError( [
'apierror-missingparam',
'group' ] );
59 if ( !isset( $params[
'aggregategroup'] ) ) {
60 $this->dieWithError( [
'apierror-missingparam',
'aggregategroup' ] );
62 $aggregateGroup = $params[
'aggregategroup'];
63 $subgroups = TranslateMetadata::getSubgroups( $aggregateGroup );
64 if ( $subgroups ===
null ) {
68 $this->dieWithError(
'apierror-translate-invalidaggregategroup',
'invalidaggregategroup' );
71 $subgroupId = $params[
'group'];
75 if ( $action ===
'associate' ) {
77 $this->dieWithError(
'apierror-translate-invalidgroup',
'invalidgroup' );
80 $messageGroupLanguage = $group->getSourceLanguage();
81 $aggregateGroupLanguage = TranslateMetadata::get( $aggregateGroup,
'sourcelanguagecode' );
83 if ( $aggregateGroupLanguage !==
false && $messageGroupLanguage !== $aggregateGroupLanguage ) {
84 $this->dieWithError( [
85 'apierror-translate-grouplanguagemismatch',
86 $messageGroupLanguage,
87 $aggregateGroupLanguage
90 $subgroups[] = $subgroupId;
91 $subgroups = array_unique( $subgroups );
92 } elseif ( $action ===
'dissociate' ) {
94 $subgroups = array_flip( $subgroups );
95 unset( $subgroups[$subgroupId] );
96 $subgroups = array_flip( $subgroups );
99 TranslateMetadata::setSubgroups( $aggregateGroup, $subgroups );
102 'aggregategroup' => TranslateMetadata::get( $aggregateGroup,
'name' ),
103 'aggregategroup-id' => $aggregateGroup,
112 Title::newFromText(
"Special:Translate/$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',
'invalidaggregategroupname'
136 TranslateMetadata::deleteGroup( $params[
'aggregategroup'] );
137 $logger = LoggerFactory::getInstance(
'Translate' );
139 'Aggregate group {groupId} has been deleted.',
140 [
'groupId' => $aggregateGroupId ]
142 } elseif ( $action ===
'add' ) {
143 if ( !isset( $params[
'groupname'] ) ) {
144 $this->dieWithError( [
'apierror-missingparam',
'groupname' ] );
146 $name = trim( $params[
'groupname'] );
147 if ( strlen( $name ) === 0 ) {
149 'apierror-translate-invalidaggregategroupname',
'invalidaggregategroupname'
153 if ( !isset( $params[
'groupdescription'] ) ) {
154 $this->dieWithError( [
'apierror-missingparam',
'groupdescription' ] );
156 $desc = trim( $params[
'groupdescription'] );
158 $aggregateGroupId = self::generateAggregateGroupId( $name );
163 $this->dieWithError(
'apierror-translate-duplicateaggregategroup',
'duplicateaggregategroup' );
171 $tempId = $aggregateGroupId .
'-' . $i;
174 }
while ( $idExists );
175 $aggregateGroupId = $tempId;
177 $sourceLanguageCode = trim( $params[
'groupsourcelanguagecode'] );
179 TranslateMetadata::set( $aggregateGroupId,
'name', $name );
180 TranslateMetadata::set( $aggregateGroupId,
'description', $desc );
181 if ( $sourceLanguageCode !== self::NO_LANGUAGE_CODE ) {
182 TranslateMetadata::set( $aggregateGroupId,
'sourcelanguagecode', $sourceLanguageCode );
184 TranslateMetadata::setSubgroups( $aggregateGroupId, [] );
187 $output[
'groups'] = self::getAllPages();
188 $output[
'aggregategroupId'] = $aggregateGroupId;
190 } elseif ( $action ===
'update' ) {
191 if ( !isset( $params[
'groupname'] ) ) {
192 $this->dieWithError( [
'apierror-missingparam',
'groupname' ] );
194 $name = trim( $params[
'groupname'] );
195 if ( strlen( $name ) === 0 ) {
197 'apierror-translate-invalidaggregategroupname',
'invalidaggregategroupname'
200 $desc = trim( $params[
'groupdescription'] );
201 $aggregateGroupId = $params[
'aggregategroup'];
202 $newLanguageCode = trim( $params[
'groupsourcelanguagecode'] );
204 $oldName = TranslateMetadata::get( $aggregateGroupId,
'name' );
205 $oldDesc = TranslateMetadata::get( $aggregateGroupId,
'description' );
206 $currentLanguageCode = TranslateMetadata::get( $aggregateGroupId,
'sourcelanguagecode' );
208 if ( $newLanguageCode !== self::NO_LANGUAGE_CODE && $newLanguageCode !== $currentLanguageCode ) {
209 $groupsWithDifferentLanguage =
210 $this->getGroupsWithDifferentLanguage( $aggregateGroupId, $newLanguageCode );
212 if ( count( $groupsWithDifferentLanguage ) ) {
213 $this->dieWithError( [
214 'apierror-translate-messagegroup-aggregategrouplanguagemismatch',
215 implode(
", ", $groupsWithDifferentLanguage ),
217 count( $groupsWithDifferentLanguage )
224 if ( $exists && $oldName !== $name ) {
225 $this->dieWithError(
'apierror-translate-duplicateaggregategroup',
'duplicateaggregategroup' );
230 && $oldDesc === $desc
231 && $newLanguageCode === $currentLanguageCode
233 $this->dieWithError(
'apierror-translate-invalidupdate',
'invalidupdate' );
235 TranslateMetadata::set( $aggregateGroupId,
'name', $name );
236 TranslateMetadata::set( $aggregateGroupId,
'description', $desc );
237 if ( $newLanguageCode === self::NO_LANGUAGE_CODE ) {
238 TranslateMetadata::clearMetadata( $aggregateGroupId, [
'sourcelanguagecode' ] );
240 TranslateMetadata::set( $aggregateGroupId,
'sourcelanguagecode', $newLanguageCode );
245 $output[
'result'] =
'ok';
246 $this->getResult()->addValue(
null, $this->getModuleName(), $output );
248 MessageGroups::singleton()->recache();
249 $this->jobQueueGroup->push( MessageIndexRebuildJob::newJob() );
257 private function getGroupsWithDifferentLanguage(
258 string $aggregateGroupId,
259 string $sourceLanguageCode
261 $groupsWithDifferentLanguage = [];
262 $subgroups = TranslateMetadata::getSubgroups( $aggregateGroupId );
263 foreach ( $subgroups as $group ) {
265 $messageGroupLanguage = $messageGroup->getSourceLanguage();
266 if ( $messageGroupLanguage !== $sourceLanguageCode ) {
267 $groupsWithDifferentLanguage[] = $messageGroup->getLabel();
271 return $groupsWithDifferentLanguage;
274 protected function generateAggregateGroupId(
string $aggregateGroupName,
string $prefix =
'agg-' ):
string {
276 if ( strlen( $aggregateGroupName ) + strlen( $prefix ) >= 200 ) {
277 return $prefix . substr( sha1( $aggregateGroupName ), 0, 5 );
279 $pattern =
'/[\x00-\x1f\x23\x27\x2c\x2e\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/i';
280 return $prefix . preg_replace( $pattern,
'_', $aggregateGroupName );
284 public function isWriteMode():
bool {
288 public function needsToken():
string {
292 protected function getAllowedParams(): array {
295 ParamValidator::PARAM_TYPE => [
'associate',
'dissociate',
'remove',
'add',
'update' ],
296 ParamValidator::PARAM_REQUIRED =>
true,
298 'aggregategroup' => [
299 ParamValidator::PARAM_TYPE =>
'string',
303 ParamValidator::PARAM_TYPE =>
'string',
306 ParamValidator::PARAM_TYPE =>
'string',
308 'groupdescription' => [
309 ParamValidator::PARAM_TYPE =>
'string',
311 'groupsourcelanguagecode' => [
312 ParamValidator::PARAM_TYPE =>
'string',
315 ParamValidator::PARAM_TYPE =>
'string',
316 ParamValidator::PARAM_REQUIRED =>
true,
321 protected function getExamplesMessages(): array {
323 'action=aggregategroups&do=associate&group=groupId&aggregategroup=aggregateGroupId'
324 =>
'apihelp-aggregategroups-example-1',
328 public static function getAllPages(): array {
331 foreach ( $groups as $group ) {
333 $pages[$group->getId()] = $group->getTitle()->getPrefixedText();