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 MediaWiki\Content\TextContent;
9use MediaWiki\Deferred\DeferredUpdates;
16use MediaWiki\JobQueue\JobQueueGroup;
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
33 public function __construct(
34 private readonly MessageIndex $messageIndex,
35 private readonly JobQueueGroup $jobQueue,
36 private readonly RevTagStore $revTagStore,
37 private readonly IConnectionProvider $dbProvider,
38 private readonly TranslatableBundleStatusStore $translatableBundleStatusStore,
39 private readonly TranslatablePageParser $translatablePageParser,
40 private readonly MessageGroupMetadata $messageGroupMetadata,
41 ) {
42 }
43
44 public function move( Title $oldName, Title $newName ): void {
45 $oldTranslatablePage = TranslatablePage::newFromTitle( $oldName );
46 $newTranslatablePage = TranslatablePage::newFromTitle( $newName );
47 $oldGroupId = $oldTranslatablePage->getMessageGroupId();
48 $newGroupId = $newTranslatablePage->getMessageGroupId();
49
50 $this->messageGroupMetadata->moveMetadata( $oldGroupId, $newGroupId, TranslatablePage::METADATA_KEYS );
51
52 $this->moveMetadata( $oldGroupId, $newGroupId );
53
54 TranslatablePage::clearSourcePageCache();
55
56 // Re-render the pages to get everything in sync
57 MessageGroups::singleton()->recache();
58 // Update message index now so that, when after this job the MoveTranslationUnits hook
59 // runs in deferred updates, it will not run RebuildMessageIndexJob (T175834).
60 $this->messageIndex->rebuild();
61
62 $job = UpdateTranslatablePageJob::newFromPage( TranslatablePage::newFromTitle( $newName ) );
63 $this->jobQueue->push( $job );
64 }
65
66 public function handleNullRevisionInsert( TranslatableBundle $bundle, RevisionRecord $revision ): void {
67 if ( !$bundle instanceof TranslatablePage ) {
68 throw new InvalidArgumentException(
69 'Expected $bundle to be of type TranslatablePage, got ' . get_class( $bundle )
70 );
71 }
72
73 $pageContent = $revision->getContent( SlotRecord::MAIN );
74 if ( !$pageContent instanceof TextContent ) {
75 throw new RuntimeException( "Translatable page {$bundle->getTitle()} has non-textual content." );
76 }
77
78 // Check if the revision still has the <translate> tag
79 $pageText = $pageContent->getText();
80 if ( $this->translatablePageParser->containsMarkup( $pageText ) ) {
81 $this->revTagStore->replaceTag( $bundle->getPageIdentity(), RevTagStore::TP_READY_TAG, $revision->getId() );
82 TranslatablePage::clearSourcePageCache();
83 }
84 }
85
87 public function delete( Title $title ): void {
88 $dbw = $this->dbProvider->getPrimaryDatabase();
89 $dbw->newDeleteQueryBuilder()
90 ->deleteFrom( 'translate_sections' )
91 ->where( [ 'trs_page' => $title->getId() ] )
92 ->caller( __METHOD__ )
93 ->execute();
94
95 $this->unmark( $title );
96 }
97
99 public function unmark( PageIdentity $title ): void {
100 $translatablePage = TranslatablePage::newFromTitle( $title );
101 foreach ( $translatablePage->getTranslationPages() as $page ) {
102 $page->invalidateCache();
103 }
104
105 $groupId = $translatablePage->getMessageGroupId();
106 $this->messageGroupMetadata->clearMetadata( $groupId, TranslatablePage::METADATA_KEYS );
107 $this->removeFromAggregateGroups( $groupId );
108
109 // Remove tags after all group related work is done in order to avoid breaking calls to
110 // TranslatablePage::getMessageGroup incase the group cache is not populated
111 $this->revTagStore->removeTags( $title, RevTagStore::TP_MARK_TAG, RevTagStore::TP_READY_TAG );
112 $this->translatableBundleStatusStore->removeStatus( $title->getId() );
113
114 MessageGroups::singleton()->recache();
115 $this->jobQueue->push( RebuildMessageIndexJob::newJob( __METHOD__ ) );
116
117 TranslatablePage::clearSourcePageCache();
118 $translatablePage->getTitle()->invalidateCache();
119 }
120
122 public function performStatusUpdate( Title $title ): void {
123 DeferredUpdates::addCallableUpdate(
124 function () use ( $title ) {
125 $this->updateStatus( $title );
126 }
127 );
128 }
129
131 public function updateStatus( Title $title ): ?TranslatableBundleStatus {
132 $revTags = $this->revTagStore->getLatestRevisionsForTags(
133 $title,
134 RevTagStore::TP_MARK_TAG,
135 RevTagStore::TP_READY_TAG
136 );
137
138 $status = TranslatablePage::determineStatus(
139 $revTags[RevTagStore::TP_READY_TAG] ?? null,
140 $revTags[RevTagStore::TP_MARK_TAG] ?? null,
141 $title->getLatestRevID( IDBAccessObject::READ_LATEST )
142 );
143
144 if ( $status ) {
145 $this->translatableBundleStatusStore->setStatus(
146 $title,
147 $status,
148 TranslatablePage::class
149 );
150 }
151
152 return $status;
153 }
154
155 private function moveMetadata( string $oldGroupId, string $newGroupId ): void {
156 // Make the changes in aggregate groups metadata, if present in any of them.
157 $aggregateGroups = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
158 $this->messageGroupMetadata->preloadGroups( array_keys( $aggregateGroups ), __METHOD__ );
159
160 foreach ( $aggregateGroups as $id => $group ) {
161 $subgroups = $this->messageGroupMetadata->get( $id, 'subgroups' );
162 if ( $subgroups === false ) {
163 continue;
164 }
165
166 $subgroups = explode( ',', $subgroups );
167 $subgroups = array_flip( $subgroups );
168 if ( isset( $subgroups[$oldGroupId] ) ) {
169 $subgroups[$newGroupId] = $subgroups[$oldGroupId];
170 unset( $subgroups[$oldGroupId] );
171 $subgroups = array_flip( $subgroups );
172 $this->messageGroupMetadata->set(
173 $group,
174 'subgroups',
175 implode( ',', $subgroups )
176 );
177 }
178 }
179
180 // Move discouraged status
181 $priority = MessageGroups::getPriority( $oldGroupId );
182 if ( $priority !== '' ) {
183 MessageGroups::setPriority( $newGroupId, $priority );
184 MessageGroups::setPriority( $oldGroupId, '' );
185 }
186 }
187
188 private function removeFromAggregateGroups( string $groupId ): void {
189 // remove the page from aggregate groups, if present in any of them.
190 $aggregateGroups = MessageGroups::getGroupsByType( AggregateMessageGroup::class );
191 $this->messageGroupMetadata->preloadGroups( array_keys( $aggregateGroups ), __METHOD__ );
192 foreach ( $aggregateGroups as $group ) {
193 $subgroups = $this->messageGroupMetadata->get( $group, 'subgroups' );
194 if ( $subgroups !== false ) {
195 $subgroups = explode( ',', $subgroups );
196 $subgroups = array_flip( $subgroups );
197 if ( isset( $subgroups[$groupId] ) ) {
198 unset( $subgroups[$groupId] );
199 $subgroups = array_flip( $subgroups );
200 $this->messageGroupMetadata->set(
201 $group,
202 'subgroups',
203 implode( ',', $subgroups )
204 );
205 }
206 }
207 }
208 }
209}
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...
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...