Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
AggregateGroupsActionApi.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
7use ApiBase;
8use ApiMain;
9use JobQueueGroup;
10use ManualLogEntry;
11use MediaWiki\Logger\LoggerFactory;
14use Title;
16use Wikimedia\ParamValidator\ParamValidator;
18
29class AggregateGroupsActionApi extends ApiBase {
31 private $jobQueueGroup;
33 protected static $right = 'translate-manage';
34
35 public function __construct(
36 ApiMain $main,
37 string $action,
38 JobQueueGroup $jobQueueGroup
39 ) {
40 parent::__construct( $main, $action );
41 $this->jobQueueGroup = $jobQueueGroup;
42 }
43
44 public function execute(): void {
45 $this->checkUserRightsAny( self::$right );
46 $block = $this->getUser()->getBlock();
47 if ( $block && $block->isSitewide() ) {
48 $this->dieBlocked( $block );
49 }
50
51 $params = $this->extractRequestParams();
52 $action = $params['do'];
53 $output = [];
54 if ( $action === 'associate' || $action === 'dissociate' ) {
55 // Group is mandatory only for these two actions
56 if ( !isset( $params['group'] ) ) {
57 $this->dieWithError( [ 'apierror-missingparam', 'group' ] );
58 }
59 if ( !isset( $params['aggregategroup'] ) ) {
60 $this->dieWithError( [ 'apierror-missingparam', 'aggregategroup' ] );
61 }
62 $aggregateGroup = $params['aggregategroup'];
63 $subgroups = TranslateMetadata::getSubgroups( $aggregateGroup );
64 if ( $subgroups === null ) {
65 // For a newly created aggregate group, it may contain no subgroups, but null
66 // means the group does not exist or something has gone wrong.
67
68 $this->dieWithError( 'apierror-translate-invalidaggregategroup', 'invalidaggregategroup' );
69 }
70
71 $subgroupId = $params['group'];
72 $group = MessageGroups::getGroup( $subgroupId );
73
74 // Add or remove from the list
75 if ( $action === 'associate' ) {
76 if ( !$group instanceof WikiPageMessageGroup ) {
77 $this->dieWithError( 'apierror-translate-invalidgroup', 'invalidgroup' );
78 }
79
80 $subgroups[] = $subgroupId;
81 $subgroups = array_unique( $subgroups );
82 } elseif ( $action === 'dissociate' ) {
83 // Allow removal of non-existing groups
84 $subgroups = array_flip( $subgroups );
85 unset( $subgroups[$subgroupId] );
86 $subgroups = array_flip( $subgroups );
87 }
88
89 TranslateMetadata::setSubgroups( $aggregateGroup, $subgroups );
90
91 $logParams = [
92 'aggregategroup' => TranslateMetadata::get( $aggregateGroup, 'name' ),
93 'aggregategroup-id' => $aggregateGroup,
94 ];
95
96 /* Note that to allow removing no longer existing groups from
97 * aggregate message groups, the message group object $group
98 * might not always be available. In this case we need to fake
99 * some title. */
100 $title = $group instanceof WikiPageMessageGroup ?
101 $group->getTitle() :
102 Title::newFromText( "Special:Translate/$subgroupId" );
103
104 $entry = new ManualLogEntry( 'pagetranslation', $action );
105 $entry->setPerformer( $this->getUser() );
106 $entry->setTarget( $title );
107 // @todo
108 // $entry->setComment( $comment );
109 $entry->setParameters( $logParams );
110
111 $logid = $entry->insert();
112 $entry->publish( $logid );
113 } elseif ( $action === 'remove' ) {
114 if ( !isset( $params['aggregategroup'] ) ) {
115 $this->dieWithError( [ 'apierror-missingparam', 'aggregategroup' ] );
116 }
117
118 $aggregateGroupId = $params['aggregategroup'];
119 $group = MessageGroups::getGroup( $aggregateGroupId );
120 if ( !$group || !( $group instanceof AggregateMessageGroup ) ) {
121 $this->dieWithError(
122 'apierror-translate-invalidaggregategroupname', 'invalidaggregategroupname'
123 );
124 }
125
126 TranslateMetadata::deleteGroup( $params['aggregategroup'] );
127 $logger = LoggerFactory::getInstance( 'Translate' );
128 $logger->info(
129 'Aggregate group {groupId} has been deleted.',
130 [ 'groupId' => $aggregateGroupId ]
131 );
132 } elseif ( $action === 'add' ) {
133 if ( !isset( $params['groupname'] ) ) {
134 $this->dieWithError( [ 'apierror-missingparam', 'groupname' ] );
135 }
136 $name = trim( $params['groupname'] );
137 if ( strlen( $name ) === 0 ) {
138 $this->dieWithError(
139 'apierror-translate-invalidaggregategroupname', 'invalidaggregategroupname'
140 );
141 }
142
143 if ( !isset( $params['groupdescription'] ) ) {
144 $this->dieWithError( [ 'apierror-missingparam', 'groupdescription' ] );
145 }
146 $desc = trim( $params['groupdescription'] );
147
148 $aggregateGroupId = self::generateAggregateGroupId( $name );
149
150 // Throw error if group already exists
151 $nameExists = MessageGroups::labelExists( $name );
152 if ( $nameExists ) {
153 $this->dieWithError( 'apierror-translate-duplicateaggregategroup', 'duplicateaggregategroup' );
154 }
155
156 // ID already exists- Generate a new ID by adding a number to it.
157 $idExists = MessageGroups::getGroup( $aggregateGroupId );
158 if ( $idExists ) {
159 $i = 1;
160 do {
161 $tempId = $aggregateGroupId . '-' . $i;
162 $idExists = MessageGroups::getGroup( $tempId );
163 $i++;
164 } while ( $idExists );
165 $aggregateGroupId = $tempId;
166 }
167
168 TranslateMetadata::set( $aggregateGroupId, 'name', $name );
169 TranslateMetadata::set( $aggregateGroupId, 'description', $desc );
170 TranslateMetadata::setSubgroups( $aggregateGroupId, [] );
171
172 // Once new aggregate group added, we need to show all the pages that can be added to that.
173 $output['groups'] = self::getAllPages();
174 $output['aggregategroupId'] = $aggregateGroupId;
175 // @todo Logging
176 } elseif ( $action === 'update' ) {
177 if ( !isset( $params['groupname'] ) ) {
178 $this->dieWithError( [ 'apierror-missingparam', 'groupname' ] );
179 }
180 $name = trim( $params['groupname'] );
181 if ( strlen( $name ) === 0 ) {
182 $this->dieWithError(
183 'apierror-translate-invalidaggregategroupname', 'invalidaggregategroupname'
184 );
185 }
186 $desc = trim( $params['groupdescription'] );
187 $aggregateGroupId = $params['aggregategroup'];
188
189 $oldName = TranslateMetadata::get( $aggregateGroupId, 'name' );
190 $oldDesc = TranslateMetadata::get( $aggregateGroupId, 'description' );
191
192 // Error if the label exists already
193 $exists = MessageGroups::labelExists( $name );
194 if ( $exists && $oldName !== $name ) {
195 $this->dieWithError( 'apierror-translate-duplicateaggregategroup', 'duplicateaggregategroup' );
196 }
197
198 if ( $oldName === $name && $oldDesc === $desc ) {
199 $this->dieWithError( 'apierror-translate-invalidupdate', 'invalidupdate' );
200 }
201 TranslateMetadata::set( $aggregateGroupId, 'name', $name );
202 TranslateMetadata::set( $aggregateGroupId, 'description', $desc );
203 }
204
205 // If we got this far, nothing has failed
206 $output['result'] = 'ok';
207 $this->getResult()->addValue( null, $this->getModuleName(), $output );
208 // Cache needs to be cleared after any changes to groups
209 MessageGroups::singleton()->recache();
210 $this->jobQueueGroup->push( MessageIndexRebuildJob::newJob() );
211 }
212
213 protected function generateAggregateGroupId( string $aggregateGroupName, string $prefix = 'agg-' ): string {
214 // The database field has maximum limit of 200 bytes
215 if ( strlen( $aggregateGroupName ) + strlen( $prefix ) >= 200 ) {
216 return $prefix . substr( sha1( $aggregateGroupName ), 0, 5 );
217 } else {
218 $pattern = '/[\x00-\x1f\x23\x27\x2c\x2e\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/i';
219 return $prefix . preg_replace( $pattern, '_', $aggregateGroupName );
220 }
221 }
222
223 public function isWriteMode(): bool {
224 return true;
225 }
226
227 public function needsToken(): string {
228 return 'csrf';
229 }
230
231 protected function getAllowedParams(): array {
232 return [
233 'do' => [
234 ParamValidator::PARAM_TYPE => [ 'associate', 'dissociate', 'remove', 'add', 'update' ],
235 ParamValidator::PARAM_REQUIRED => true,
236 ],
237 'aggregategroup' => [
238 ParamValidator::PARAM_TYPE => 'string',
239 ],
240 'group' => [
241 // Not providing list of values, to allow dissociation of unknown groups
242 ParamValidator::PARAM_TYPE => 'string',
243 ],
244 'groupname' => [
245 ParamValidator::PARAM_TYPE => 'string',
246 ],
247 'groupdescription' => [
248 ParamValidator::PARAM_TYPE => 'string',
249 ],
250 'token' => [
251 ParamValidator::PARAM_TYPE => 'string',
252 ParamValidator::PARAM_REQUIRED => true,
253 ],
254 ];
255 }
256
257 protected function getExamplesMessages(): array {
258 return [
259 'action=aggregategroups&do=associate&group=groupId&aggregategroup=aggregateGroupId'
260 => 'apihelp-aggregategroups-example-1',
261 ];
262 }
263
264 public static function getAllPages(): array {
265 $groups = MessageGroups::getAllGroups();
266 $pages = [];
267 foreach ( $groups as $group ) {
268 if ( $group instanceof WikiPageMessageGroup ) {
269 $pages[$group->getId()] = $group->getTitle()->getPrefixedText();
270 }
271 }
272
273 return $pages;
274 }
275}
Groups multiple message groups together as one group.
API module for managing aggregate message groups Only supports aggregate message groups defined insid...
Factory class for accessing message groups individually by id or all of them as an list.
Job for rebuilding message index.
Wraps the translatable page sections into a message group.