Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
FuzzyTranslationsMaintenanceScript.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Diagnostics;
5
6use ActorMigration;
7use ContentHandler;
8use IDBAccessObject;
12use MediaWiki\MediaWikiServices;
13use MediaWiki\Revision\RevisionStore;
14use MediaWiki\Revision\SlotRecord;
15use MediaWiki\User\UserFactory;
16use Title;
17use User;
18use Wikimedia\Rdbms\ILoadBalancer;
19use Wikimedia\Rdbms\IResultWrapper;
20
28 private $actorMigration;
30 private $userFactory;
32 private $revisionStore;
34 private $DBLoadBalancer;
35
36 public function __construct() {
37 parent::__construct();
38 $this->addDescription( 'Fuzzy bot command line script.' );
39 $this->addArg(
40 'arg',
41 'Title pattern or username if user option is provided.'
42 );
43 $this->addOption(
44 'really',
45 '(optional) Really fuzzy, no dry-run'
46 );
47 $this->addOption(
48 'skiplanguages',
49 '(optional) Skip some languages (comma separated)',
50 false, /*required*/
51 true /*has arg*/
52 );
53 $this->addOption(
54 'comment',
55 '(optional) Comment for updating',
56 false, /*required*/
57 true /*has arg*/
58 );
59 $this->addOption(
60 'user',
61 '(optional) Fuzzy the translations made by user given as an argument.',
62 false, /*required*/
63 false /*has arg*/
64 );
65 $this->requireExtension( 'Translate' );
66 }
67
68 private function initServices() {
69 $mwServices = MediaWikiServices::getInstance();
70 $this->actorMigration = $mwServices->getActorMigration();
71 $this->userFactory = $mwServices->getUserFactory();
72 $this->revisionStore = $mwServices->getRevisionStore();
73 $this->DBLoadBalancer = $mwServices->getDBLoadBalancer();
74 }
75
76 public function execute() {
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, $comment ) {
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( $rows ) {
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( $pattern, $skipLanguages = [] ) {
133 $dbr = $this->DBLoadBalancer->getMaintenanceConnectionRef( DB_REPLICA );
134
135 $search = [];
136 foreach ( (array)$pattern as $title ) {
137 $title = Title::newFromText( $title );
138 $ns = $title->getNamespace();
139 if ( !isset( $search[$ns] ) ) {
140 $search[$ns] = [];
141 }
142 $search[$ns][] = 'page_title' . $dbr->buildLike( $title->getDBkey(), $dbr->anyString() );
143 }
144
145 $title_conds = [];
146 foreach ( $search as $ns => $names ) {
147 if ( $ns === NS_MAIN ) {
148 $ns = $this->getConfig()->get( 'TranslateMessageNamespaces' );
149 }
150 $titles = $dbr->makeList( $names, LIST_OR );
151 $title_conds[] = $dbr->makeList( [ 'page_namespace' => $ns, $titles ], LIST_AND );
152 }
153
154 $conds = [
155 'page_latest=rev_id',
156 $dbr->makeList( $title_conds, LIST_OR ),
157 ];
158
159 if ( count( $skipLanguages ) ) {
160 $skiplist = $dbr->makeList( $skipLanguages );
161 $conds[] = "substring_index(page_title, '/', -1) NOT IN ($skiplist)";
162 }
163
164 $queryInfo = $this->revisionStore->getQueryInfo( [ 'page' ] );
165 $rows = $dbr->select(
166 $queryInfo['tables'],
167 $queryInfo['fields'],
168 $conds,
169 __METHOD__,
170 [],
171 $queryInfo['joins']
172 );
173 return $this->getMessageContentsFromRows( $rows );
174 }
175
176 private function getPagesForUser( User $user, $skipLanguages = [] ) {
177 $dbr = $this->DBLoadBalancer->getMaintenanceConnectionRef( DB_REPLICA );
178
179 $revWhere = $this->actorMigration->getWhere( $dbr, 'rev_user', $user );
180 $conds = [
181 'page_latest=rev_id',
182 $revWhere['conds'],
183 'page_namespace' => $this->getConfig()->get( 'TranslateMessageNamespaces' ),
184 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ),
185 ];
186 if ( count( $skipLanguages ) ) {
187 $skiplist = $dbr->makeList( $skipLanguages );
188 $conds[] = "substring_index(page_title, '/', -1) NOT IN ($skiplist)";
189 }
190
191 $queryInfo = $this->revisionStore->getQueryInfo( [ 'page', 'user' ] );
192 $rows = $dbr->select(
193 $queryInfo['tables'],
194 $queryInfo['fields'],
195 $conds,
196 __METHOD__,
197 [],
198 $queryInfo['joins'] + $revWhere['joins']
199 );
200
201 return $this->getMessageContentsFromRows( $rows );
202 }
203
211 private function updateMessage( $title, $text, $dryrun, $comment = null ) {
212 $this->output( "Updating {$title->getPrefixedText()}... ", $title );
213 if ( !$title instanceof Title ) {
214 $this->output( 'INVALID TITLE!', $title );
215
216 return;
217 }
218
219 $documentationLanguageCode = $this->getConfig()->get( 'TranslateDocumentationLanguageCode' );
220 $items = explode( '/', $title->getText(), 2 );
221 if ( isset( $items[1] ) && $items[1] === $documentationLanguageCode ) {
222 $this->output( 'IGNORED!', $title );
223
224 return;
225 }
226
227 if ( $dryrun ) {
228 $this->output( 'DRY RUN!', $title );
229
230 return;
231 }
232
233 $wikipage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
234 $content = ContentHandler::makeContent( $text, $title );
235 $status = $wikipage->doUserEditContent(
236 $content,
237 FuzzyBot::getUser(),
238 $comment ?: 'Marking as fuzzy',
239 EDIT_FORCE_BOT | EDIT_UPDATE
240 );
241
242 $this->output( $status->isOK() ? 'OK' : 'FAILED', $title );
243 }
244}
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Constants for making code for maintenance scripts more readable.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31