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