Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslateEditAddons.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorInterface;
5
6use MediaWiki\CommentStore\CommentStoreComment;
7use MediaWiki\Content\TextContent;
8use MediaWiki\Deferred\DeferredUpdates;
16use MediaWiki\Logging\ManualLogEntry;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Page\PageIdentity;
19use MediaWiki\Page\WikiPage;
20use MediaWiki\Parser\ParserOptions;
21use MediaWiki\Revision\RevisionRecord;
22use MediaWiki\Storage\EditResult;
23use MediaWiki\Title\Title;
24use MediaWiki\User\User;
25use MediaWiki\User\UserIdentity;
26
46 public static function disallowLangTranslations(
47 Title $title,
48 User $user,
49 string $action,
50 &$result
51 ): bool {
52 if ( $action !== 'edit' ) {
53 return true;
54 }
55
56 $handle = new MessageHandle( $title );
57 if ( !$handle->isValid() ) {
58 return true;
59 }
60
61 if ( $user->isAllowed( 'translate-manage' ) ) {
62 return true;
63 }
64
65 $langCode = $handle->getCode();
66 if ( $langCode === '' ) {
67 return true;
68 }
69
70 $group = $handle->getGroup();
71 if ( !$group ) {
72 return true;
73 }
74
75 $configHelper = Services::getInstance()->getConfigHelper();
76 $isDisabled = $configHelper->isTargetLanguageDisabled( $group, $langCode, $reason );
77 if ( !$isDisabled ) {
78 return true;
79 }
80
81 if ( $reason === null ) {
82 $result = [ 'translate-language-disabled' ];
83 } else {
84 $result = [ 'translate-page-disabled', $reason ];
85 }
86
87 return false;
88 }
89
94 public static function onSaveComplete(
95 WikiPage $wikiPage,
96 UserIdentity $userIdentity,
97 string $summary,
98 int $flags,
99 RevisionRecord $revisionRecord,
100 EditResult $editResult
101 ): void {
102 global $wgEnablePageTranslation;
103
104 $content = $wikiPage->getContent();
105
106 if ( !$content instanceof TextContent ) {
107 // Screw it, not interested
108 return;
109 }
110
111 $text = $content->getText();
112 $handle = new MessageHandle( $wikiPage->getTitle() );
113
114 if ( !$handle->isValid() ) {
115 return;
116 }
117
118 // TODO: Convert this method to a listener for PageLatestRevisionChangedEvent,
119 // which provides a better way to detect dummy revisions and null edits.
120 $isDummyRevision = !$editResult->isNew()
121 && ( $editResult->getOriginalRevisionId() === $revisionRecord->getParentId() );
122
123 if ( $isDummyRevision ) {
124 // Dummy revisions should not unfuzzy the page, see T392321.
125 // Fuzziness propagation for dummy revisions is handled in
126 // HookHandler::onRevisionRecordInserted.
127 // Returning here also protects against infinite recursion
128 // when we insert a dummy revision below.
129 return;
130 }
131
132 // Check if this is a null edit, i.e. no revision was created,
133 // or a dummy revision, i.e. the content didn't change.
134 // NOTE: $editResult->isNullEdit() does not distinguish between null
135 // edits and dummy revisions (T392333). We need that distinction though,
136 // since we want to create a dummy revision when we are informed about a
137 // null edit.
138 $isNullEdit = $editResult->getOriginalRevisionId() === $revisionRecord->getId();
139
140 // Update it.
141 $revId = $revisionRecord->getId();
142 $mwServices = MediaWikiServices::getInstance();
143
144 $fuzzy = $handle->needsFuzzy( $text );
145 $parentId = $revisionRecord->getParentId();
146 $revTagStore = Services::getInstance()->getRevTagStore();
147 if ( $isNullEdit || $parentId == 0 ) {
148 // In this case the page_latest hasn't changed so we can rely on its fuzzy status
149 $wasFuzzy = $handle->isFuzzy();
150 } else {
151 // In this case the page_latest will (probably) have changed. The above might work by chance
152 // since it reads from a replica database which might not have gotten the update yet, but
153 // don't trust it and read the fuzzy status of the parent ID from the database instead
154 $wasFuzzy = $revTagStore->isRevIdFuzzy( $wikiPage->getId(), $parentId );
155 }
156 if ( !$fuzzy && $wasFuzzy ) {
157 $user = $mwServices->getUserFactory()->newFromUserIdentity( $userIdentity );
158
159 if ( !$mwServices->getPermissionManager()->userCan( 'unfuzzy', $user, $wikiPage->getTitle() ) ) {
160 // No permission to unfuzzy this unit so leave it fuzzy
161 $fuzzy = true;
162 } elseif ( $isNullEdit ) {
163 $entry = new ManualLogEntry( 'translationreview', 'unfuzzy' );
164 // Generate a log entry and null revision for the otherwise
165 // invisible unfuzzying
166 // NOTE: This will trigger onSaveComplete again! We have to be
167 // careful to avoid infinite recursion!
168 $nullRevision = $mwServices->getPageUpdaterFactory()
169 ->newPageUpdater( $wikiPage, $userIdentity )
170 ->saveDummyRevision(
171 CommentStoreComment::newUnsavedComment(
172 $summary !== '' ? $summary : wfMessage( "translate-unfuzzy-comment" )
173 ),
174 EDIT_SILENT
175 );
176
177 // Set $revId to the revision ID of the dummy revision so it (rather than the previous revision)
178 // has the fuzzy and transver tags updated below.
179 $revId = $nullRevision->getId();
180 $entry->setAssociatedRevId( $revId );
181 $entry->setPerformer( $userIdentity );
182 $entry->setTarget( $wikiPage );
183 $logId = $entry->insert();
184 $entry->publish( $logId );
185 }
186 }
187 // If the edit is already fuzzy due to validation, !!FUZZY!!, or lacking permissions to unfuzzy
188 // then don't do revert checking (and don't set a transver below)
189 // Otherwise, if the edit undoes or rolls back (but not manually reverts) to a fuzzy revision
190 // then set the fuzzy status
191 $revertedTo = null;
192 if ( !$fuzzy && $editResult->isExactRevert() ) {
193 $method = $editResult->getRevertMethod();
194 if ( $method !== EditResult::REVERT_MANUAL ) {
195 $revertedTo = $editResult->getOriginalRevisionId();
196 // This is a paranoia check - if somehow getOriginalRevisionId returns null despite isExactRevert
197 // being true just handle it like it wasn't a revert rather than crashing
198 if ( $revertedTo !== null ) {
199 $fuzzy = $revTagStore->isRevIdFuzzy( $wikiPage->getId(), $revertedTo );
200 }
201 }
202 }
203
204 self::updateFuzzyTag( $wikiPage, $revId, $fuzzy );
205
206 $group = $handle->getGroup();
207 // Update translation stats - source language should always be up to date
208 if ( $handle->getCode() !== $group->getSourceLanguage() ) {
209 // This will update in-process cache immediately, but the value is saved
210 // to the database in a deferred update. See MessageGroupStats::queueUpdates.
211 // In case an error happens before that, the stats may be stale, but that
212 // would be fixed by the next update or purge.
213 MessageGroupStats::clear( $handle );
214 }
215
216 // This job asks for stats, however the updated stats are written in a deferred update.
217 // To make it less likely that the job would be executed before the updated stats are
218 // written, create the job inside a deferred update too.
219 DeferredUpdates::addCallableUpdate(
220 static function () use ( $handle ) {
222 }
223 );
224 $user = $mwServices->getUserFactory()
225 ->newFromId( $userIdentity->getId() );
226
227 if ( !$fuzzy ) {
228 Services::getInstance()->getHookRunner()
229 ->onTranslate_newTranslation( $handle, $revId, $text, $user );
230 } elseif ( $revertedTo !== null ) {
231 // If a revert sets fuzzy status then also set tp:transver
232 // to the tp:transver of the version being reverted to
233 $oldTransver = $revTagStore->getTransver( $wikiPage, $revertedTo );
234 if ( $oldTransver !== null ) {
235 $revTagStore->setTransver( $wikiPage, $revId, $oldTransver );
236 }
237 }
238
239 TtmServer::onChange( $handle );
240
241 if ( $wgEnablePageTranslation && $handle->isPageTranslation() ) {
242 // Updates for translatable pages only
243 $minor = (bool)( $flags & EDIT_MINOR );
244 PageTranslationHooks::onSectionSave( $wikiPage, $user, $content,
245 $summary, $minor, $flags, $handle );
246 }
247 }
248
254 private static function updateFuzzyTag( PageIdentity $page, int $revision, bool $fuzzy ): void {
255 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
256
257 $conds = [
258 'rt_page' => $page->getId(),
259 'rt_type' => RevTagStore::FUZZY_TAG,
260 'rt_revision' => $revision
261 ];
262
263 // Replace the existing fuzzy tag, if any
264 if ( $fuzzy ) {
265 $index = array_keys( $conds );
266 $dbw->newReplaceQueryBuilder()
267 ->replaceInto( 'revtag' )
268 ->uniqueIndexFields( $index )
269 ->row( $conds )
270 ->caller( __METHOD__ )
271 ->execute();
272 } else {
273 $dbw->newDeleteQueryBuilder()
274 ->deleteFrom( 'revtag' )
275 ->where( $conds )
276 ->caller( __METHOD__ )
277 ->execute();
278 }
279 }
280
287 public static function updateTransverTag(
288 MessageHandle $handle,
289 int $revision,
290 string $text,
291 User $user
292 ): bool {
293 if ( $user->isAllowed( 'bot' ) ) {
294 return false;
295 }
296
297 $group = $handle->getGroup();
298
299 $title = $handle->getTitle();
300 $name = $handle->getKey() . '/' . $group->getSourceLanguage();
301 $definitionTitle = Title::makeTitleSafe( $title->getNamespace(), $name );
302 if ( !$definitionTitle || !$definitionTitle->exists() ) {
303 return true;
304 }
305
306 $definitionRevision = $definitionTitle->getLatestRevID();
307 $revTagStore = Services::getInstance()->getRevTagStore();
308 $revTagStore->setTransver( $title, $revision, $definitionRevision );
309
310 return true;
311 }
312
314 public static function disablePreSaveTransform(
315 WikiPage $wikiPage,
316 ParserOptions $popts
317 ): void {
318 global $wgTranslateUsePreSaveTransform;
319
320 if ( !$wgTranslateUsePreSaveTransform ) {
321 $handle = new MessageHandle( $wikiPage->getTitle() );
322 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
323 $popts->setPreSaveTransform( false );
324 }
325 }
326 }
327}
static onChange(MessageHandle $handle)
Hook: TranslateEventTranslationReview and also on translation changes.
Class to manage revision tags for translatable bundles.
Class for pointing to messages, like Title class is for titles.
static onSectionSave(WikiPage $wikiPage, User $user, TextContent $content, $summary, $minor, $flags, MessageHandle $handle)
This is triggered after an edit to translation unit page.
Definition Hooks.php:386
Minimal service container.
Definition Services.php:60
This class aims to provide efficient mechanism for fetching translation completion stats.
Various editing enhancements to the edit page interface.
static disablePreSaveTransform(WikiPage $wikiPage, ParserOptions $popts)
Hook: ArticlePrepareTextForEdit.
static onSaveComplete(WikiPage $wikiPage, UserIdentity $userIdentity, string $summary, int $flags, RevisionRecord $revisionRecord, EditResult $editResult)
Runs message checks, adds tp:transver tags and updates statistics.
static disallowLangTranslations(Title $title, User $user, string $action, &$result)
Prevent translations to non-translatable languages for the group Hook: getUserPermissionsErrorsExpens...
static updateTransverTag(MessageHandle $handle, int $revision, string $text, User $user)
Adds tag which identifies the revision of source message at that time.
TtmServer - The Translate extension translation memory interface Some general static methods for inst...
Definition TtmServer.php:19