Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslatablePageStore.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
7use DeferredUpdates;
8use IDBAccessObject;
9use InvalidArgumentException;
10use JobQueueGroup;
17use MediaWiki\Page\PageIdentity;
18use MediaWiki\Revision\RevisionRecord;
19use MediaWiki\Revision\SlotRecord;
20use MediaWiki\Title\Title;
21use RuntimeException;
22use TextContent;
23use Wikimedia\Rdbms\ILoadBalancer;
24
32 private MessageIndex $messageIndex;
33 private JobQueueGroup $jobQueue;
34 private RevTagStore $revTagStore;
35 private ILoadBalancer $loadBalancer;
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 ILoadBalancer $loadBalancer,
45 TranslatableBundleStatusStore $translatableBundleStatusStore,
46 TranslatablePageParser $translatablePageParser,
47 MessageGroupMetadata $messageGroupMetadata
48 ) {
49 $this->messageIndex = $messageIndex;
50 $this->jobQueue = $jobQueue;
51 $this->revTagStore = $revTagStore;
52 $this->loadBalancer = $loadBalancer;
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->loadBalancer->getConnection( DB_PRIMARY );
103 $dbw->delete( 'translate_sections', [ 'trs_page' => $title->getArticleID() ], __METHOD__ );
104
105 $this->unmark( $title );
106 }
107
109 public function unmark( PageIdentity $title ): void {
110 $translatablePage = TranslatablePage::newFromTitle( $title );
111 $translatablePage->getTranslationPercentages();
112 foreach ( $translatablePage->getTranslationPages() as $page ) {
113 $page->invalidateCache();
114 }
115
116 $groupId = $translatablePage->getMessageGroupId();
117 $this->messageGroupMetadata->clearMetadata( $groupId, TranslatablePage::METADATA_KEYS );
118 $this->removeFromAggregateGroups( $groupId );
119
120 // Remove tags after all group related work is done in order to avoid breaking calls to
121 // TranslatablePage::getMessageGroup incase the group cache is not populated
122 $this->revTagStore->removeTags( $title, RevTagStore::TP_MARK_TAG, RevTagStore::TP_READY_TAG );
123 $this->translatableBundleStatusStore->removeStatus( $title->getId() );
124
125 MessageGroups::singleton()->recache();
126 $this->jobQueue->push( RebuildMessageIndexJob::newJob( __METHOD__ ) );
127
128 TranslatablePage::clearSourcePageCache();
129 $translatablePage->getTitle()->invalidateCache();
130 }
131
133 public function performStatusUpdate( Title $title ): void {
134 DeferredUpdates::addCallableUpdate(
135 function () use ( $title ) {
136 $this->updateStatus( $title );
137 }
138 );
139 }
140
142 public function updateStatus( Title $title ): ?TranslatableBundleStatus {
143 $revTags = $this->revTagStore->getLatestRevisionsForTags(
144 $title,
145 RevTagStore::TP_MARK_TAG,
146 RevTagStore::TP_READY_TAG
147 );
148
149 $status = TranslatablePage::determineStatus(
150 $revTags[RevTagStore::TP_READY_TAG] ?? null,
151 $revTags[RevTagStore::TP_MARK_TAG] ?? null,
152 $title->getLatestRevID( IDBAccessObject::READ_LATEST )
153 );
154
155 if ( $status ) {
156 $this->translatableBundleStatusStore->setStatus(
157 $title, $status, TranslatablePage::class
158 );
159 }
160
161 return $status;
162 }
163
164 private function moveMetadata( string $oldGroupId, string $newGroupId ): void {
165 // Make the changes in aggregate groups metadata, if present in any of them.
166 $aggregateGroups = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
167 $this->messageGroupMetadata->preloadGroups( array_keys( $aggregateGroups ), __METHOD__ );
168
169 foreach ( $aggregateGroups as $id => $group ) {
170 $subgroups = $this->messageGroupMetadata->get( $id, 'subgroups' );
171 if ( $subgroups === false ) {
172 continue;
173 }
174
175 $subgroups = explode( ',', $subgroups );
176 $subgroups = array_flip( $subgroups );
177 if ( isset( $subgroups[$oldGroupId] ) ) {
178 $subgroups[$newGroupId] = $subgroups[$oldGroupId];
179 unset( $subgroups[$oldGroupId] );
180 $subgroups = array_flip( $subgroups );
181 $this->messageGroupMetadata->set(
182 $group->getId(),
183 'subgroups',
184 implode( ',', $subgroups )
185 );
186 }
187 }
188
189 // Move discouraged status
190 $priority = MessageGroups::getPriority( $oldGroupId );
191 if ( $priority !== '' ) {
192 MessageGroups::setPriority( $newGroupId, $priority );
193 MessageGroups::setPriority( $oldGroupId, '' );
194 }
195 }
196
197 private function removeFromAggregateGroups( string $groupId ): void {
198 // remove the page from aggregate groups, if present in any of them.
199 $aggregateGroups = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
200 $this->messageGroupMetadata->preloadGroups( array_keys( $aggregateGroups ), __METHOD__ );
201 foreach ( $aggregateGroups as $group ) {
202 $subgroups = $this->messageGroupMetadata->get( $group->getId(), 'subgroups' );
203 if ( $subgroups !== false ) {
204 $subgroups = explode( ',', $subgroups );
205 $subgroups = array_flip( $subgroups );
206 if ( isset( $subgroups[$groupId] ) ) {
207 unset( $subgroups[$groupId] );
208 $subgroups = array_flip( $subgroups );
209 $this->messageGroupMetadata->set(
210 $group->getId(),
211 'subgroups',
212 implode( ',', $subgroups )
213 );
214 }
215 }
216 }
217 }
218}
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...