Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 98 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
RenderTranslationPageJob | |
0.00% |
0 / 98 |
|
0.00% |
0 / 15 |
1056 | |
0.00% |
0 / 1 |
newJob | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
newNonPrioritizedJob | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
run | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
182 | |||
setFlags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFlags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setSummary | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDeduplicationInfo | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getSummary | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setUser | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getUser | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isDeleteTrigger | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
logJobStart | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
deleteTranslationPage | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
hasOnlyFuzzyBotAsAuthor | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\PageTranslation; |
5 | |
6 | use JobQueueGroup; |
7 | use MediaWiki\CommentStore\CommentStoreComment; |
8 | use MediaWiki\Extension\Translate\Jobs\GenericTranslateJob; |
9 | use MediaWiki\Extension\Translate\MessageGroupProcessing\DeleteTranslatableBundleJob; |
10 | use MediaWiki\Extension\Translate\MessageLoading\MessageHandle; |
11 | use MediaWiki\Extension\Translate\SystemUsers\FuzzyBot; |
12 | use MediaWiki\MediaWikiServices; |
13 | use MediaWiki\Revision\RevisionStore; |
14 | use MediaWiki\Revision\SlotRecord; |
15 | use MediaWiki\Title\Title; |
16 | use MediaWiki\User\UserIdentity; |
17 | use MediaWiki\User\UserRigorOptions; |
18 | use RecentChange; |
19 | use User; |
20 | |
21 | /** |
22 | * Job for updating translation pages when translation or template changes. |
23 | * @author Niklas Laxström |
24 | * @license GPL-2.0-or-later |
25 | * @ingroup PageTranslation JobQueue |
26 | */ |
27 | class RenderTranslationPageJob extends GenericTranslateJob { |
28 | public const ACTION_DELETE = 'delete'; |
29 | |
30 | public static function newJob( |
31 | Title $target, |
32 | ?string $triggerAction = null, |
33 | ?string $unitTitleText = null |
34 | ): self { |
35 | $job = new self( $target, [ 'triggerAction' => $triggerAction, 'unitTitle' => $unitTitleText ] ); |
36 | $job->setUser( FuzzyBot::getUser() ); |
37 | $job->setFlags( EDIT_FORCE_BOT ); |
38 | $job->setSummary( wfMessage( 'tpt-render-summary' )->inContentLanguage()->text() ); |
39 | |
40 | return $job; |
41 | } |
42 | |
43 | public static function newNonPrioritizedJob( |
44 | Title $target, |
45 | ?string $triggerAction = null, |
46 | ?string $unitTitleText = null |
47 | ): self { |
48 | $job = self::newJob( $target, $triggerAction, $unitTitleText ); |
49 | $job->command = 'NonPrioritizedRenderTranslationPageJob'; |
50 | return $job; |
51 | } |
52 | |
53 | public function __construct( Title $title, array $params = [] ) { |
54 | parent::__construct( 'RenderTranslationPageJob', $title, $params ); |
55 | $this->removeDuplicates = true; |
56 | } |
57 | |
58 | public function run(): bool { |
59 | $this->logJobStart(); |
60 | $mwServices = MediaWikiServices::getInstance(); |
61 | // We may be doing double wait here if this job was spawned by TranslationUpdateJob |
62 | $lb = $mwServices->getDBLoadBalancerFactory(); |
63 | if ( !$lb->waitForReplication() ) { |
64 | $this->logWarning( 'Continuing despite replication lag' ); |
65 | } |
66 | |
67 | // Initialization |
68 | $translationPageTitle = $this->title; |
69 | |
70 | $tpPage = TranslatablePage::getTranslationPageFromTitle( $translationPageTitle ); |
71 | if ( !$tpPage ) { |
72 | $this->logError( 'Cannot render translation page!' ); |
73 | return false; |
74 | } |
75 | |
76 | // Other stuff |
77 | $user = $this->getUser(); |
78 | $summary = $this->getSummary(); |
79 | $flags = $this->getFlags(); |
80 | |
81 | // We should not re-create the translation page if a translation unit is being deleted |
82 | // because it is possible that the translation page may also be queued for deletion. |
83 | // Hence, set the flag to EDIT_UPDATE and remove EDIT_NEW if its added |
84 | if ( $this->isDeleteTrigger() ) { |
85 | $flags = ( $flags | EDIT_UPDATE ) & ~EDIT_NEW; |
86 | } |
87 | |
88 | // @todo FuzzyBot hack |
89 | Hooks::$allowTargetEdit = true; |
90 | |
91 | $commentStoreComment = CommentStoreComment::newUnsavedComment( $summary ); |
92 | // $percentageTranslated is modified by reference |
93 | $content = $tpPage->getPageContent( $mwServices->getParser(), $percentageTranslated ); |
94 | $translationPageTitleExists = $translationPageTitle->exists(); |
95 | if ( $percentageTranslated === 0 && !$translationPageTitleExists ) { |
96 | Hooks::$allowTargetEdit = false; |
97 | $this->logInfo( 'No translations found and translation page does not exist. Nothing to do.' ); |
98 | return true; |
99 | } |
100 | |
101 | if ( |
102 | $percentageTranslated === 0 && |
103 | $translationPageTitleExists && |
104 | $this->hasOnlyFuzzyBotAsAuthor( $mwServices->getRevisionStore(), $translationPageTitle ) |
105 | ) { |
106 | $this->logInfo( 'Deleting translation page having no translations and modified only by Fuzzybot' ); |
107 | // Page is not translated at all but the translation page exists and has been only edited by FuzzyBot |
108 | $this->deleteTranslationPage( $mwServices->getJobQueueGroup(), $translationPageTitle, FuzzyBot::getUser() ); |
109 | } else { |
110 | $pageUpdater = $mwServices->getWikiPageFactory() |
111 | ->newFromTitle( $translationPageTitle ) |
112 | ->newPageUpdater( $user ); |
113 | $pageUpdater->setContent( SlotRecord::MAIN, $content ); |
114 | |
115 | if ( $user->authorizeWrite( 'autopatrol', $translationPageTitle ) ) { |
116 | $pageUpdater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED ); |
117 | } |
118 | |
119 | $pageUpdater->addTag( 'translate-translation-pages' ); |
120 | $pageUpdater->saveRevision( $commentStoreComment, $flags ); |
121 | $status = $pageUpdater->getStatus(); |
122 | |
123 | if ( !$status->isOK() ) { |
124 | if ( $this->isDeleteTrigger() && $status->hasMessage( 'edit-gone-missing' ) ) { |
125 | $this->logInfo( 'Translation page missing with delete trigger' ); |
126 | } else { |
127 | $this->logError( |
128 | 'Error while editing content in page.', |
129 | [ |
130 | 'content' => $content->getTextForSummary(), |
131 | 'errors' => $status->getErrors() |
132 | ] |
133 | ); |
134 | } |
135 | } |
136 | |
137 | $this->logInfo( 'Finished page edit operation' ); |
138 | } |
139 | |
140 | Hooks::$allowTargetEdit = false; |
141 | |
142 | $this->logInfo( 'Finished TranslateRenderJob' ); |
143 | return true; |
144 | } |
145 | |
146 | public function setFlags( int $flags ): void { |
147 | $this->params['flags'] = $flags; |
148 | } |
149 | |
150 | private function getFlags(): int { |
151 | return $this->params['flags']; |
152 | } |
153 | |
154 | public function setSummary( string $summary ): void { |
155 | $this->params['summary'] = $summary; |
156 | } |
157 | |
158 | /** @inheritDoc */ |
159 | public function getDeduplicationInfo(): array { |
160 | $info = parent::getDeduplicationInfo(); |
161 | // Unit title is only passed for logging and should not be used for de-duplication |
162 | unset( $info['params']['unitTitle'] ); |
163 | return $info; |
164 | } |
165 | |
166 | private function getSummary(): string { |
167 | return $this->params['summary']; |
168 | } |
169 | |
170 | /** @param UserIdentity|string $user */ |
171 | public function setUser( $user ): void { |
172 | if ( $user instanceof UserIdentity ) { |
173 | $this->params['user'] = $user->getName(); |
174 | } else { |
175 | $this->params['user'] = $user; |
176 | } |
177 | } |
178 | |
179 | /** Get a user object for doing edits. */ |
180 | private function getUser(): User { |
181 | $userFactory = MediaWikiServices::getInstance()->getUserFactory(); |
182 | return $userFactory->newFromName( $this->params['user'], UserRigorOptions::RIGOR_NONE ); |
183 | } |
184 | |
185 | private function isDeleteTrigger(): bool { |
186 | $triggerAction = $this->params['triggerAction'] ?? null; |
187 | return $triggerAction === self::ACTION_DELETE; |
188 | } |
189 | |
190 | private function logJobStart(): void { |
191 | $unitTitleText = $this->params['unitTitle'] ?? null; |
192 | $logMessage = 'Starting TranslateRenderJob '; |
193 | if ( $unitTitleText ) { |
194 | $logMessage .= "trigged by $unitTitleText "; |
195 | } |
196 | |
197 | if ( $this->isDeleteTrigger() ) { |
198 | $logMessage .= '- [deletion] '; |
199 | } |
200 | |
201 | $this->logInfo( trim( $logMessage ) ); |
202 | } |
203 | |
204 | private function deleteTranslationPage( |
205 | JobQueueGroup $jobQueueGroup, |
206 | Title $translationPageTitle, |
207 | UserIdentity $performer |
208 | ): void { |
209 | $translatablePageTitle = ( new MessageHandle( $translationPageTitle ) )->getTitleForBase(); |
210 | $isTranslationPage = true; |
211 | |
212 | $job = DeleteTranslatableBundleJob::newJob( |
213 | $translationPageTitle, |
214 | $translatablePageTitle->getPrefixedText(), |
215 | TranslatablePage::class, |
216 | $isTranslationPage, |
217 | $performer, |
218 | wfMessage( 'pt-deletepage-lang-outdated-logreason' )->inContentLanguage()->text() |
219 | ); |
220 | |
221 | $jobQueueGroup->push( $job ); |
222 | } |
223 | |
224 | private function hasOnlyFuzzyBotAsAuthor( RevisionStore $revisionStore, Title $title ): bool { |
225 | $fuzzyBot = FuzzyBot::getUser(); |
226 | $pageAuthors = $revisionStore->getAuthorsBetween( $title->getId() ); |
227 | foreach ( $pageAuthors as $author ) { |
228 | if ( !$author->equals( $fuzzyBot ) ) { |
229 | return false; |
230 | } |
231 | } |
232 | return true; |
233 | } |
234 | } |