Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
FuzzyTranslationsMaintenanceScript.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Diagnostics;
5
6use ContentHandler;
7use IDBAccessObject;
8use MediaWiki\CommentStore\CommentStoreComment;
12use MediaWiki\MediaWikiServices;
13use MediaWiki\Page\WikiPageFactory;
14use MediaWiki\Revision\RevisionStore;
15use MediaWiki\Revision\SlotRecord;
16use MediaWiki\Title\Title;
17use MediaWiki\User\ActorMigration;
18use MediaWiki\User\User;
19use MediaWiki\User\UserFactory;
20use Wikimedia\Rdbms\ILoadBalancer;
21use Wikimedia\Rdbms\IResultWrapper;
22
29 private ActorMigration $actorMigration;
30 private UserFactory $userFactory;
31 private RevisionStore $revisionStore;
32 private ILoadBalancer $DBLoadBalancer;
33 private WikiPageFactory $wikiPageFactory;
34
35 public function __construct() {
36 parent::__construct();
37 $this->addDescription( 'Fuzzy bot command line script.' );
38 $this->addArg(
39 'arg',
40 'Title pattern or username if user option is provided.'
41 );
42 $this->addOption(
43 'really',
44 '(optional) Really fuzzy, no dry-run'
45 );
46 $this->addOption(
47 'skiplanguages',
48 '(optional) Skip some languages (comma separated)',
49 self::OPTIONAL,
50 self::HAS_ARG
51 );
52 $this->addOption(
53 'comment',
54 '(optional) Comment for updating',
55 self::OPTIONAL,
56 self::HAS_ARG
57 );
58 $this->addOption(
59 'user',
60 '(optional) Fuzzy the translations made by user given as an argument.',
61 self::OPTIONAL,
62 self::NO_ARG
63 );
64 $this->requireExtension( 'Translate' );
65 }
66
67 private function initServices(): void {
68 $mwServices = MediaWikiServices::getInstance();
69 $this->actorMigration = $mwServices->getActorMigration();
70 $this->userFactory = $mwServices->getUserFactory();
71 $this->revisionStore = $mwServices->getRevisionStore();
72 $this->DBLoadBalancer = $mwServices->getDBLoadBalancer();
73 $this->wikiPageFactory = $mwServices->getWikiPageFactory();
74 }
75
76 public function execute(): void {
77 $this->initServices();
78
79 $skipLanguages = [];
80 if ( $this->hasOption( 'skiplanguages' ) ) {
81 $skipLanguages = array_map(
82 'trim',
83 explode( ',', $this->getOption( 'skiplanguages' ) )
84 );
85 }
86
87 if ( $this->hasOption( 'user' ) ) {
88 $user = $this->userFactory->newFromName( $this->getArg( 0 ) );
89 $pages = $this->getPagesForUser( $user, $skipLanguages );
90 } else {
91 $pages = $this->getPagesForPattern( $this->getArg( 0 ), $skipLanguages );
92 }
93
94 $dryrun = !$this->hasOption( 'really' );
95 $comment = $this->getOption( 'comment' );
96 $this->fuzzyTranslations( $pages, $dryrun, $comment );
97 }
98
99 private function fuzzyTranslations( array $pages, bool $dryrun, ?string $comment ): void {
100 $count = count( $pages );
101 $this->output( "Found $count pages to update.", 'pagecount' );
102
103 foreach ( $pages as [ $title, $text ] ) {
104 $this->updateMessage( $title, TRANSLATE_FUZZY . $text, $dryrun, $comment );
105 }
106 }
107
113 private function getMessageContentsFromRows( IResultWrapper $rows ): array {
114 $messagesContents = [];
115 $slots = $this->revisionStore->getContentBlobsForBatch( $rows, [ SlotRecord::MAIN ] )->getValue();
116 foreach ( $rows as $row ) {
117 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
118 if ( isset( $slots[$row->rev_id] ) ) {
119 $text = $slots[$row->rev_id][SlotRecord::MAIN]->blob_data;
120 } else {
121 $content = $this->revisionStore
122 ->newRevisionFromRow( $row, IDBAccessObject::READ_NORMAL, $title )
123 ->getContent( SlotRecord::MAIN );
124 $text = Utilities::getTextFromTextContent( $content );
125 }
126 $messagesContents[] = [ $title, $text ];
127 }
128 return $messagesContents;
129 }
130
132 private function getPagesForPattern( string $pattern, array $skipLanguages = [] ): array {
133 $dbr = $this->DBLoadBalancer->getMaintenanceConnectionRef( DB_REPLICA );
134
135 $conds = [
136 'page_latest=rev_id',
137 ];
138
139 $title = Title::newFromText( $pattern );
140 if ( $title->inNamespace( NS_MAIN ) ) {
141 $namespace = $this->getConfig()->get( 'TranslateMessageNamespaces' );
142 } else {
143 $namespace = $title->getNamespace();
144 }
145
146 $conds['page_namespace'] = $namespace;
147 $conds[] = 'page_title' . $dbr->buildLike( $title->getDBkey(), $dbr->anyString() );
148
149 if ( count( $skipLanguages ) ) {
150 $skiplist = $dbr->makeList( $skipLanguages );
151 $conds[] = "substring_index(page_title, '/', -1) NOT IN ($skiplist)";
152 }
153
154 $rows = $this->revisionStore->newSelectQueryBuilder( $dbr )
155 ->joinPage()
156 ->where( $conds )
157 ->caller( __METHOD__ )
158 ->fetchResultSet();
159 return $this->getMessageContentsFromRows( $rows );
160 }
161
162 private function getPagesForUser( User $user, array $skipLanguages = [] ): array {
163 $dbr = $this->DBLoadBalancer->getMaintenanceConnectionRef( DB_REPLICA );
164
165 $revWhere = $this->actorMigration->getWhere( $dbr, 'rev_user', $user );
166 $conds = [
167 'page_latest=rev_id',
168 $revWhere['conds'],
169 'page_namespace' => $this->getConfig()->get( 'TranslateMessageNamespaces' ),
170 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ),
171 ];
172 if ( count( $skipLanguages ) ) {
173 $skiplist = $dbr->makeList( $skipLanguages );
174 $conds[] = "substring_index(page_title, '/', -1) NOT IN ($skiplist)";
175 }
176
177 $rows = $this->revisionStore->newSelectQueryBuilder( $dbr )
178 ->joinPage()
179 ->joinUser()
180 ->where( $conds )
181 ->caller( __METHOD__ )
182 ->fetchResultSet();
183
184 return $this->getMessageContentsFromRows( $rows );
185 }
186
194 private function updateMessage( Title $title, string $text, bool $dryrun, ?string $comment = null ) {
195 $this->output( "Updating {$title->getPrefixedText()}... ", $title );
196
197 $documentationLanguageCode = $this->getConfig()->get( 'TranslateDocumentationLanguageCode' );
198 $items = explode( '/', $title->getText(), 2 );
199 if ( isset( $items[1] ) && $items[1] === $documentationLanguageCode ) {
200 $this->output( 'IGNORED!', $title );
201
202 return;
203 }
204
205 if ( $dryrun ) {
206 $this->output( 'DRY RUN!', $title );
207
208 return;
209 }
210
211 $wikiPage = $this->wikiPageFactory->newFromTitle( $title );
212 $summary = CommentStoreComment::newUnsavedComment( $comment ?? 'Marking as fuzzy' );
213 $content = ContentHandler::makeContent( $text, $title );
214 $updater = $wikiPage->newPageUpdater( FuzzyBot::getUser() );
215 $updater
216 ->setContent( SlotRecord::MAIN, $content )
217 ->saveRevision( $summary, EDIT_FORCE_BOT | EDIT_UPDATE );
218 $status = $updater->getStatus();
219
220 $this->output( $status->isOK() ? 'OK' : 'FAILED', $title );
221 }
222}
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Base maintenance script containing constants and methods used in multiple scripts Hopefully the const...
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31