Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslateEditAddons.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorInterface;
5
6use ManualLogEntry;
7use MediaWiki\CommentStore\CommentStoreComment;
8use MediaWiki\Content\TextContent;
9use MediaWiki\Deferred\DeferredUpdates;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Parser\ParserOptions;
19use MediaWiki\Revision\RevisionRecord;
20use MediaWiki\Storage\EditResult;
21use MediaWiki\Title\Title;
22use MediaWiki\User\User;
23use MediaWiki\User\UserIdentity;
24use WikiPage;
25
45 public static function disallowLangTranslations(
46 Title $title,
47 User $user,
48 string $action,
49 &$result
50 ): bool {
51 if ( $action !== 'edit' ) {
52 return true;
53 }
54
55 $handle = new MessageHandle( $title );
56 if ( !$handle->isValid() ) {
57 return true;
58 }
59
60 if ( $user->isAllowed( 'translate-manage' ) ) {
61 return true;
62 }
63
64 $langCode = $handle->getCode();
65 if ( $langCode === '' ) {
66 return true;
67 }
68
69 $group = $handle->getGroup();
70 if ( !$group ) {
71 return true;
72 }
73
74 $configHelper = Services::getInstance()->getConfigHelper();
75 $isDisabled = $configHelper->isTargetLanguageDisabled( $group, $langCode, $reason );
76 if ( !$isDisabled ) {
77 return true;
78 }
79
80 if ( $reason === null ) {
81 $result = [ 'translate-language-disabled' ];
82 } else {
83 $result = [ 'translate-page-disabled', $reason ];
84 }
85
86 return false;
87 }
88
93 public static function onSaveComplete(
94 WikiPage $wikiPage,
95 UserIdentity $userIdentity,
96 string $summary,
97 int $flags,
98 RevisionRecord $revisionRecord,
99 EditResult $editResult
100 ): void {
101 global $wgEnablePageTranslation;
102
103 $content = $wikiPage->getContent();
104
105 if ( !$content instanceof TextContent ) {
106 // Screw it, not interested
107 return;
108 }
109
110 $text = $content->getText();
111 $title = $wikiPage->getTitle();
112 $handle = new MessageHandle( $title );
113
114 if ( !$handle->isValid() ) {
115 return;
116 }
117
118 // TODO: Convert this method to a listener for PageRevisionUpdatedEvent,
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( $title->getArticleID(), $parentId );
155 }
156 if ( !$fuzzy && $wasFuzzy ) {
157 $title = $mwServices->getTitleFactory()->newFromPageIdentity( $wikiPage );
158 $user = $mwServices->getUserFactory()->newFromUserIdentity( $userIdentity );
159
160 if ( !$mwServices->getPermissionManager()->userCan( 'unfuzzy', $user, $title ) ) {
161 // No permission to unfuzzy this unit so leave it fuzzy
162 $fuzzy = true;
163 } elseif ( $isNullEdit ) {
164 $entry = new ManualLogEntry( 'translationreview', 'unfuzzy' );
165 // Generate a log entry and null revision for the otherwise
166 // invisible unfuzzying
167 // NOTE: This will trigger onSaveComplete again! We have to be
168 // careful to avoid infinite recursion!
169 $nullRevision = $mwServices->getPageUpdaterFactory()
170 ->newPageUpdater( $wikiPage, $userIdentity )
171 ->saveDummyRevision(
172 CommentStoreComment::newUnsavedComment(
173 $summary !== '' ? $summary : wfMessage( "translate-unfuzzy-comment" )
174 ),
175 EDIT_SILENT
176 );
177
178 // Set $revId to the revision ID of the dummy revision so it (rather than the previous revision)
179 // has the fuzzy and transver tags updated below.
180 $revId = $nullRevision->getId();
181 $entry->setAssociatedRevId( $revId );
182 $entry->setPerformer( $userIdentity );
183 $entry->setTarget( $title );
184 $logId = $entry->insert();
185 $entry->publish( $logId );
186 }
187 }
188 // If the edit is already fuzzy due to validation, !!FUZZY!!, or lacking permissions to unfuzzy
189 // then don't do revert checking (and don't set a transver below)
190 // Otherwise, if the edit undoes or rolls back (but not manually reverts) to a fuzzy revision
191 // then set the fuzzy status
192 $revertedTo = null;
193 if ( !$fuzzy && $editResult->isExactRevert() ) {
194 $method = $editResult->getRevertMethod();
195 if ( $method !== EditResult::REVERT_MANUAL ) {
196 $revertedTo = $editResult->getOriginalRevisionId();
197 // This is a paranoia check - if somehow getOriginalRevisionId returns null despite isExactRevert
198 // being true just handle it like it wasn't a revert rather than crashing
199 if ( $revertedTo !== null ) {
200 $fuzzy = $revTagStore->isRevIdFuzzy( $title->getArticleID(), $revertedTo );
201 }
202 }
203 }
204
205 self::updateFuzzyTag( $title, $revId, $fuzzy );
206
207 $group = $handle->getGroup();
208 // Update translation stats - source language should always be up to date
209 if ( $handle->getCode() !== $group->getSourceLanguage() ) {
210 // This will update in-process cache immediately, but the value is saved
211 // to the database in a deferred update. See MessageGroupStats::queueUpdates.
212 // In case an error happens before that, the stats may be stale, but that
213 // would be fixed by the next update or purge.
214 MessageGroupStats::clear( $handle );
215 }
216
217 // This job asks for stats, however the updated stats are written in a deferred update.
218 // To make it less likely that the job would be executed before the updated stats are
219 // written, create the job inside a deferred update too.
220 DeferredUpdates::addCallableUpdate(
221 static function () use ( $handle ) {
223 }
224 );
225 $user = $mwServices->getUserFactory()
226 ->newFromId( $userIdentity->getId() );
227
228 if ( !$fuzzy ) {
229 Services::getInstance()->getHookRunner()
230 ->onTranslate_newTranslation( $handle, $revId, $text, $user );
231 } elseif ( $revertedTo !== null ) {
232 // If a revert sets fuzzy status then also set tp:transver
233 // to the tp:transver of the version being reverted to
234 $oldTransver = $revTagStore->getTransver( $title, $revertedTo );
235 if ( $oldTransver !== null ) {
236 $revTagStore->setTransver( $title, $revId, $oldTransver );
237 }
238 }
239
240 TtmServer::onChange( $handle );
241
242 if ( $wgEnablePageTranslation && $handle->isPageTranslation() ) {
243 // Updates for translatable pages only
244 $minor = (bool)( $flags & EDIT_MINOR );
245 PageTranslationHooks::onSectionSave( $wikiPage, $user, $content,
246 $summary, $minor, $flags, $handle );
247 }
248 }
249
255 private static function updateFuzzyTag( Title $title, int $revision, bool $fuzzy ): void {
256 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
257
258 $conds = [
259 'rt_page' => $title->getArticleID(),
260 'rt_type' => RevTagStore::FUZZY_TAG,
261 'rt_revision' => $revision
262 ];
263
264 // Replace the existing fuzzy tag, if any
265 if ( $fuzzy ) {
266 $index = array_keys( $conds );
267 $dbw->newReplaceQueryBuilder()
268 ->replaceInto( 'revtag' )
269 ->uniqueIndexFields( $index )
270 ->row( $conds )
271 ->caller( __METHOD__ )
272 ->execute();
273 } else {
274 $dbw->newDeleteQueryBuilder()
275 ->deleteFrom( 'revtag' )
276 ->where( $conds )
277 ->caller( __METHOD__ )
278 ->execute();
279 }
280 }
281
288 public static function updateTransverTag(
289 MessageHandle $handle,
290 int $revision,
291 string $text,
292 User $user
293 ): bool {
294 if ( $user->isAllowed( 'bot' ) ) {
295 return false;
296 }
297
298 $group = $handle->getGroup();
299
300 $title = $handle->getTitle();
301 $name = $handle->getKey() . '/' . $group->getSourceLanguage();
302 $definitionTitle = Title::makeTitleSafe( $title->getNamespace(), $name );
303 if ( !$definitionTitle || !$definitionTitle->exists() ) {
304 return true;
305 }
306
307 $definitionRevision = $definitionTitle->getLatestRevID();
308 $revTagStore = Services::getInstance()->getRevTagStore();
309 $revTagStore->setTransver( $title, $revision, $definitionRevision );
310
311 return true;
312 }
313
315 public static function disablePreSaveTransform(
316 WikiPage $wikiPage,
317 ParserOptions $popts
318 ): void {
319 global $wgTranslateUsePreSaveTransform;
320
321 if ( !$wgTranslateUsePreSaveTransform ) {
322 $handle = new MessageHandle( $wikiPage->getTitle() );
323 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
324 $popts->setPreSaveTransform( false );
325 }
326 }
327 }
328}
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:367
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