Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslatablePageStore.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
7use InvalidArgumentException;
8use JobQueueGroup;
9use MediaWiki\Content\TextContent;
10use MediaWiki\Deferred\DeferredUpdates;
17use MediaWiki\Page\PageIdentity;
18use MediaWiki\Revision\RevisionRecord;
19use MediaWiki\Revision\SlotRecord;
20use MediaWiki\Title\Title;
21use RuntimeException;
22use Wikimedia\Rdbms\IConnectionProvider;
23use Wikimedia\Rdbms\IDBAccessObject;
24
32 private MessageIndex $messageIndex;
33 private JobQueueGroup $jobQueue;
34 private RevTagStore $revTagStore;
35 private IConnectionProvider $dbProvider;
36 private TranslatableBundleStatusStore $translatableBundleStatusStore;
37 private TranslatablePageParser $translatablePageParser;
38 private MessageGroupMetadata $messageGroupMetadata;
39
40 public function __construct(
41 MessageIndex $messageIndex,
42 JobQueueGroup $jobQueue,
43 RevTagStore $revTagStore,
44 IConnectionProvider $dbProvider,
45 TranslatableBundleStatusStore $translatableBundleStatusStore,
46 TranslatablePageParser $translatablePageParser,
47 MessageGroupMetadata $messageGroupMetadata
48 ) {
49 $this->messageIndex = $messageIndex;
50 $this->jobQueue = $jobQueue;
51 $this->revTagStore = $revTagStore;
52 $this->dbProvider = $dbProvider;
53 $this->translatableBundleStatusStore = $translatableBundleStatusStore;
54 $this->translatablePageParser = $translatablePageParser;
55 $this->messageGroupMetadata = $messageGroupMetadata;
56 }
57
58 public function move( Title $oldName, Title $newName ): void {
59 $oldTranslatablePage = TranslatablePage::newFromTitle( $oldName );
60 $newTranslatablePage = TranslatablePage::newFromTitle( $newName );
61 $oldGroupId = $oldTranslatablePage->getMessageGroupId();
62 $newGroupId = $newTranslatablePage->getMessageGroupId();
63
64 $this->messageGroupMetadata->moveMetadata( $oldGroupId, $newGroupId, TranslatablePage::METADATA_KEYS );
65
66 $this->moveMetadata( $oldGroupId, $newGroupId );
67
68 TranslatablePage::clearSourcePageCache();
69
70 // Re-render the pages to get everything in sync
71 MessageGroups::singleton()->recache();
72 // Update message index now so that, when after this job the MoveTranslationUnits hook
73 // runs in deferred updates, it will not run RebuildMessageIndexJob (T175834).
74 $this->messageIndex->rebuild();
75
76 $job = UpdateTranslatablePageJob::newFromPage( TranslatablePage::newFromTitle( $newName ) );
77 $this->jobQueue->push( $job );
78 }
79
80 public function handleNullRevisionInsert( TranslatableBundle $bundle, RevisionRecord $revision ): void {
81 if ( !$bundle instanceof TranslatablePage ) {
82 throw new InvalidArgumentException(
83 'Expected $bundle to be of type TranslatablePage, got ' . get_class( $bundle )
84 );
85 }
86
87 $pageContent = $revision->getContent( SlotRecord::MAIN );
88 if ( !$pageContent instanceof TextContent ) {
89 throw new RuntimeException( "Translatable page {$bundle->getTitle()} has non-textual content." );
90 }
91
92 // Check if the revision still has the <translate> tag
93 $pageText = $pageContent->getText();
94 if ( $this->translatablePageParser->containsMarkup( $pageText ) ) {
95 $this->revTagStore->replaceTag( $bundle->getTitle(), RevTagStore::TP_READY_TAG, $revision->getId() );
96 TranslatablePage::clearSourcePageCache();
97 }
98 }
99
101 public function delete( Title $title ): void {
102 $dbw = $this->dbProvider->getPrimaryDatabase();
103 $dbw->newDeleteQueryBuilder()
104 ->deleteFrom( 'translate_sections' )
105 ->where( [ 'trs_page' => $title->getArticleID() ] )
106 ->caller( __METHOD__ )
107 ->execute();
108
109 $this->unmark( $title );
110 }
111
113 public function unmark( PageIdentity $title ): void {
114 $translatablePage = TranslatablePage::newFromTitle( $title );
115 foreach ( $translatablePage->getTranslationPages() as $page ) {
116 $page->invalidateCache();
117 }
118
119 $groupId = $translatablePage->getMessageGroupId();
120 $this->messageGroupMetadata->clearMetadata( $groupId, TranslatablePage::METADATA_KEYS );
121 $this->removeFromAggregateGroups( $groupId );
122
123 // Remove tags after all group related work is done in order to avoid breaking calls to
124 // TranslatablePage::getMessageGroup incase the group cache is not populated
125 $this->revTagStore->removeTags( $title, RevTagStore::TP_MARK_TAG, RevTagStore::TP_READY_TAG );
126 $this->translatableBundleStatusStore->removeStatus( $title->getId() );
127
128 MessageGroups::singleton()->recache();
129 $this->jobQueue->push( RebuildMessageIndexJob::newJob( __METHOD__ ) );
130
131 TranslatablePage::clearSourcePageCache();
132 $translatablePage->getTitle()->invalidateCache();
133 }
134
136 public function performStatusUpdate( Title $title ): void {
137 DeferredUpdates::addCallableUpdate(
138 function () use ( $title ) {
139 $this->updateStatus( $title );
140 }
141 );
142 }
143
145 public function updateStatus( Title $title ): ?TranslatableBundleStatus {
146 $revTags = $this->revTagStore->getLatestRevisionsForTags(
147 $title,
148 RevTagStore::TP_MARK_TAG,
149 RevTagStore::TP_READY_TAG
150 );
151
152 $status = TranslatablePage::determineStatus(
153 $revTags[RevTagStore::TP_READY_TAG] ?? null,
154 $revTags[RevTagStore::TP_MARK_TAG] ?? null,
155 $title->getLatestRevID( IDBAccessObject::READ_LATEST )
156 );
157
158 if ( $status ) {
159 $this->translatableBundleStatusStore->setStatus(
160 $title,
161 $status,
162 TranslatablePage::class
163 );
164 }
165
166 return $status;
167 }
168
169 private function moveMetadata( string $oldGroupId, string $newGroupId ): void {
170 // Make the changes in aggregate groups metadata, if present in any of them.
171 $aggregateGroups = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
172 $this->messageGroupMetadata->preloadGroups( array_keys( $aggregateGroups ), __METHOD__ );
173
174 foreach ( $aggregateGroups as $id => $group ) {
175 $subgroups = $this->messageGroupMetadata->get( $id, 'subgroups' );
176 if ( $subgroups === false ) {
177 continue;
178 }
179
180 $subgroups = explode( ',', $subgroups );
181 $subgroups = array_flip( $subgroups );
182 if ( isset( $subgroups[$oldGroupId] ) ) {
183 $subgroups[$newGroupId] = $subgroups[$oldGroupId];
184 unset( $subgroups[$oldGroupId] );
185 $subgroups = array_flip( $subgroups );
186 $this->messageGroupMetadata->set(
187 $group->getId(),
188 'subgroups',
189 implode( ',', $subgroups )
190 );
191 }
192 }
193
194 // Move discouraged status
195 $priority = MessageGroups::getPriority( $oldGroupId );
196 if ( $priority !== '' ) {
197 MessageGroups::setPriority( $newGroupId, $priority );
198 MessageGroups::setPriority( $oldGroupId, '' );
199 }
200 }
201
202 private function removeFromAggregateGroups( string $groupId ): void {
203 // remove the page from aggregate groups, if present in any of them.
204 $aggregateGroups = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
205 $this->messageGroupMetadata->preloadGroups( array_keys( $aggregateGroups ), __METHOD__ );
206 foreach ( $aggregateGroups as $group ) {
207 $subgroups = $this->messageGroupMetadata->get( $group->getId(), 'subgroups' );
208 if ( $subgroups !== false ) {
209 $subgroups = explode( ',', $subgroups );
210 $subgroups = array_flip( $subgroups );
211 if ( isset( $subgroups[$groupId] ) ) {
212 unset( $subgroups[$groupId] );
213 $subgroups = array_flip( $subgroups );
214 $this->messageGroupMetadata->set(
215 $group->getId(),
216 'subgroups',
217 implode( ',', $subgroups )
218 );
219 }
220 }
221 }
222 }
223}
Groups multiple message groups together as one group.
Class to manage revision tags for translatable bundles.
const TP_READY_TAG
Indicates a revision of a translatable page that is marked for translation.
const TP_MARK_TAG
Indicates a revision of a page that can be marked for translation.
Store service for looking up and storing status for translatable bundle status.
Translatable bundle represents a message group where its translatable content is defined on a wiki pa...
getTitle()
Return the title of the page where the translatable bundle is defined.
performStatusUpdate(Title $title)
Queues an update for the status of the translatable page.
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
Offers functionality for reading and updating Translate group related metadata.
Generates ParserOutput from text or removes all tags from a text.
Mixed bag of methods related to translatable pages.
Job for updating translation units and translation pages when a translatable page is marked for trans...