Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
AggregateGroupsActionApi.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
7use JobQueueGroup;
8use ManualLogEntry;
9use MediaWiki\Api\ApiBase;
10use MediaWiki\Api\ApiMain;
14use MediaWiki\Logger\LoggerFactory;
15use Wikimedia\ParamValidator\ParamValidator;
16
27class AggregateGroupsActionApi extends ApiBase {
28 private JobQueueGroup $jobQueueGroup;
29 protected static string $right = 'translate-manage';
30 private MessageGroupMetadata $messageGroupMetadata;
31 private AggregateGroupManager $aggregateGroupManager;
32
33 public function __construct(
34 ApiMain $main,
35 string $action,
36 JobQueueGroup $jobQueueGroup,
37 MessageGroupMetadata $messageGroupMetadata,
38 AggregateGroupManager $aggregateGroupManager
39 ) {
40 parent::__construct( $main, $action );
41 $this->jobQueueGroup = $jobQueueGroup;
42 $this->messageGroupMetadata = $messageGroupMetadata;
43 $this->aggregateGroupManager = $aggregateGroupManager;
44 }
45
46 public function execute(): void {
47 $this->checkUserRightsAny( self::$right );
48 $block = $this->getUser()->getBlock();
49 if ( $block && $block->isSitewide() ) {
50 $this->dieBlocked( $block );
51 }
52
53 $params = $this->extractRequestParams();
54 $action = $params['do'];
55 $output = [];
56 if ( $action === 'associate' || $action === 'dissociate' ) {
57 // Group or groups is mandatory only for these two actions
58 $this->requireOnlyOneParameter( $params, 'group', 'groups' );
59
60 if ( isset( $params['groups'] ) ) {
61 $subgroupIds = array_map( 'trim', $params['groups'] );
62 } else {
63 $subgroupIds = [ $params['group'] ];
64 }
65
66 if ( !isset( $params['aggregategroup'] ) ) {
67 $this->dieWithError( [ 'apierror-missingparam', 'aggregategroup' ] );
68 }
69
70 $aggregateGroupId = $params['aggregategroup'];
71
72 try {
73 if ( $action === 'associate' ) {
74 // Not all subgroups passed maybe added, as some may already be part of the aggregate group
75 $groupIdsToLog = $this->aggregateGroupManager->associate( $aggregateGroupId, $subgroupIds );
76 $output[ 'groupUrls'] = [];
77 foreach ( $groupIdsToLog as $subgroupId ) {
78 $output[ 'groupUrls'][ $subgroupId ] =
79 $this->aggregateGroupManager->getTargetTitleByGroupId( $subgroupId )->getFullURL();
80 }
81 } else {
82 $groupIdsToLog = $this->aggregateGroupManager->disassociate( $aggregateGroupId, $subgroupIds );
83 }
84 } catch (
88 ) {
89 $this->dieWithException( $e );
90 }
91
92 $logParams = [
93 'aggregategroup' => $this->messageGroupMetadata->get( $aggregateGroupId, 'name' ),
94 'aggregategroup-id' => $aggregateGroupId,
95 ];
96
97 foreach ( $groupIdsToLog as $subgroupId ) {
98 $title = $this->aggregateGroupManager->getTargetTitleByGroupId( $subgroupId );
99 $entry = new ManualLogEntry( 'pagetranslation', $action );
100 $entry->setPerformer( $this->getUser() );
101 $entry->setTarget( $title );
102 $entry->setParameters( $logParams );
103
104 $logId = $entry->insert();
105 $entry->publish( $logId );
106 }
107 } elseif ( $action === 'remove' ) {
108 if ( !isset( $params['aggregategroup'] ) ) {
109 $this->dieWithError( [ 'apierror-missingparam', 'aggregategroup' ] );
110 }
111
112 $aggregateGroupId = $params['aggregategroup'];
113 $group = MessageGroups::getGroup( $aggregateGroupId );
114 if ( !( $group instanceof AggregateMessageGroup ) ) {
115 $this->dieWithError(
116 'apierror-translate-invalidaggregategroupname',
117 'invalidaggregategroupname'
118 );
119 }
120
121 $this->messageGroupMetadata->deleteGroup( $params['aggregategroup'] );
122 $logger = LoggerFactory::getInstance( LogNames::MAIN );
123 $logger->info(
124 'Aggregate group {groupId} has been deleted.',
125 [ 'groupId' => $aggregateGroupId ]
126 );
127 } elseif ( $action === 'add' ) {
128 if ( !isset( $params['groupname'] ) ) {
129 $this->dieWithError( [ 'apierror-missingparam', 'groupname' ] );
130 }
131 $name = trim( $params['groupname'] );
132 if ( strlen( $name ) === 0 ) {
133 $this->dieWithError(
134 'apierror-translate-invalidaggregategroupname',
135 'invalidaggregategroupname'
136 );
137 }
138
139 $desc = trim( $params['groupdescription'] );
140 $languageCode = trim( $params['groupsourcelanguagecode'] );
141 $languageCode = $languageCode === AggregateMessageGroup::UNDETERMINED_LANGUAGE_CODE ?
142 null : $languageCode;
143 try {
144 $aggregateGroupId = $this->aggregateGroupManager->add( $name, $desc, $languageCode );
145 } catch ( DuplicateAggregateGroupException $e ) {
146 $this->dieWithException( $e );
147 }
148
149 // Once a new aggregate group is added, we need to show all the pages that can be added to that.
150 $output['groups'] = $this->getIncludableGroups();
151 $output['aggregategroupId'] = $aggregateGroupId;
152 // @todo Logging
153 } elseif ( $action === 'update' ) {
154 if ( !isset( $params['groupname'] ) ) {
155 $this->dieWithError( [ 'apierror-missingparam', 'groupname' ] );
156 }
157 $name = trim( $params['groupname'] );
158 if ( strlen( $name ) === 0 ) {
159 $this->dieWithError(
160 'apierror-translate-invalidaggregategroupname',
161 'invalidaggregategroupname'
162 );
163 }
164
165 $aggregateGroupId = $params['aggregategroup'];
166 $oldName = $this->messageGroupMetadata->get( $aggregateGroupId, 'name' );
167
168 // Error if the label exists already
169 $exists = MessageGroups::labelExists( $name );
170 if ( $exists && $oldName !== $name ) {
171 $this->dieWithException( new DuplicateAggregateGroupException( $name ) );
172 }
173
174 $desc = trim( $params['groupdescription'] );
175
176 $newLanguageCode = trim( $params['groupsourcelanguagecode'] );
177
178 $oldDesc = $this->messageGroupMetadata->get( $aggregateGroupId, 'description' );
179 $currentLanguageCode = $this->messageGroupMetadata->get( $aggregateGroupId, 'sourcelanguagecode' );
180
181 if (
182 $newLanguageCode !== AggregateMessageGroup::UNDETERMINED_LANGUAGE_CODE &&
183 $newLanguageCode !== $currentLanguageCode
184 ) {
185 $groupsWithDifferentLanguage =
186 $this->getGroupsWithDifferentLanguage( $aggregateGroupId, $newLanguageCode );
187
188 if ( count( $groupsWithDifferentLanguage ) ) {
189 $this->dieWithError( [
190 'translate-error-aggregategroup-source-language-mismatch',
191 implode( ', ', $groupsWithDifferentLanguage ),
192 $newLanguageCode,
193 count( $groupsWithDifferentLanguage )
194 ] );
195 }
196 }
197
198 if (
199 $oldName === $name
200 && $oldDesc === $desc
201 && $newLanguageCode === $currentLanguageCode
202 ) {
203 $this->dieWithError( 'apierror-translate-invalidupdate', 'invalidupdate' );
204 }
205 $this->messageGroupMetadata->set( $aggregateGroupId, 'name', $name );
206 $this->messageGroupMetadata->set( $aggregateGroupId, 'description', $desc );
207 if ( $newLanguageCode === AggregateMessageGroup::UNDETERMINED_LANGUAGE_CODE ) {
208 $this->messageGroupMetadata->clearMetadata( $aggregateGroupId, [ 'sourcelanguagecode' ] );
209 } else {
210 $this->messageGroupMetadata->set( $aggregateGroupId, 'sourcelanguagecode', $newLanguageCode );
211 }
212 }
213
214 // If we got this far, nothing has failed
215 $output['result'] = 'ok';
216 $this->getResult()->addValue( null, $this->getModuleName(), $output );
217 // Cache needs to be cleared after any changes to groups
218 MessageGroups::singleton()->recache();
219 $this->jobQueueGroup->push( RebuildMessageIndexJob::newJob() );
220 }
221
227 private function getGroupsWithDifferentLanguage(
228 string $aggregateGroupId,
229 string $sourceLanguageCode
230 ): array {
231 $groupsWithDifferentLanguage = [];
232 $subgroups = $this->messageGroupMetadata->getSubgroups( $aggregateGroupId );
233 foreach ( $subgroups as $group ) {
234 $messageGroup = MessageGroups::getGroup( $group );
235 $messageGroupLanguage = $messageGroup->getSourceLanguage();
236 if ( $messageGroupLanguage !== $sourceLanguageCode ) {
237 $groupsWithDifferentLanguage[] = $messageGroup->getLabel();
238 }
239 }
240
241 return $groupsWithDifferentLanguage;
242 }
243
244 public function isWriteMode(): bool {
245 return true;
246 }
247
248 public function needsToken(): string {
249 return 'csrf';
250 }
251
252 protected function getAllowedParams(): array {
253 return [
254 'do' => [
255 ParamValidator::PARAM_TYPE => [ 'associate', 'dissociate', 'remove', 'add', 'update' ],
256 ParamValidator::PARAM_REQUIRED => true,
257 ],
258 'aggregategroup' => [
259 ParamValidator::PARAM_TYPE => 'string',
260 ],
261 'group' => [
262 // For backward compatibility
263 ParamValidator::PARAM_TYPE => 'string',
264 ParamValidator::PARAM_DEPRECATED => true,
265 ],
266 'groups' => [
267 // Not providing a list of values to allow dissociation of unknown groups
268 ParamValidator::PARAM_TYPE => 'string',
269 ParamValidator::PARAM_ISMULTI => true,
270 ],
271 'groupname' => [
272 ParamValidator::PARAM_TYPE => 'string',
273 ],
274 'groupdescription' => [
275 ParamValidator::PARAM_TYPE => 'string',
276 ParamValidator::PARAM_DEFAULT => '',
277 ],
278 'groupsourcelanguagecode' => [
279 ParamValidator::PARAM_TYPE => 'string',
280 ParamValidator::PARAM_DEFAULT => AggregateMessageGroup::UNDETERMINED_LANGUAGE_CODE,
281 ],
282 ];
283 }
284
285 protected function getExamplesMessages(): array {
286 return [
287 'action=aggregategroups&do=associate&groups=groupId1|groupId2&aggregategroup=aggregateGroupId'
288 => 'apihelp-aggregategroups-example-1',
289 ];
290 }
291
292 private function getIncludableGroups(): array {
293 $groups = MessageGroups::getAllGroups();
294 $pages = [];
295 foreach ( $groups as $group ) {
296 if ( $this->aggregateGroupManager->supportsAggregation( $group ) ) {
297 $pages[$group->getId()] = $group->getLabel( $this->getContext() );
298 }
299 }
300
301 return $pages;
302 }
303}
Groups multiple message groups together as one group.
Constants for log channel names used in this extension.
Definition LogNames.php:13
const MAIN
Default log channel for the extension.
Definition LogNames.php:15
Exception thrown when a message group could not be associated to an aggregate group.
Exception thrown when message group languages do not match the aggregate message group's language.
Contains logic to manage aggregate groups and their subgroups.
API module for managing aggregate message groups Only supports aggregate message groups defined insid...
Exception thrown when a duplicate aggregate group with the given name is found.
static getGroup(string $id)
Fetch a message group by id.
static labelExists(string $name)
Check if a particular aggregate group label exists.
Offers functionality for reading and updating Translate group related metadata.