28 private $actorMigration;
32 private $revisionStore;
34 private $DBLoadBalancer;
36 public function __construct() {
37 parent::__construct();
38 $this->addDescription(
'Fuzzy bot command line script.' );
41 'Title pattern or username if user option is provided.'
45 '(optional) Really fuzzy, no dry-run'
49 '(optional) Skip some languages (comma separated)',
55 '(optional) Comment for updating',
61 '(optional) Fuzzy the translations made by user given as an argument.',
65 $this->requireExtension(
'Translate' );
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();
76 public function execute() {
77 $this->initServices();
80 if ( $this->hasOption(
'skiplanguages' ) ) {
81 $skipLanguages = array_map(
83 explode(
',', $this->getOption(
'skiplanguages' ) )
87 if ( $this->hasOption(
'user' ) ) {
88 $user = $this->userFactory->newFromName( $this->getArg( 0 ) );
89 $pages = $this->getPagesForUser( $user, $skipLanguages );
91 $pages = $this->getPagesForPattern( $this->getArg( 0 ), $skipLanguages );
94 $dryrun = !$this->hasOption(
'really' );
95 $comment = $this->getOption(
'comment' );
96 $this->fuzzyTranslations( $pages, $dryrun, $comment );
99 private function fuzzyTranslations( array $pages,
bool $dryrun, $comment ) {
100 $count = count( $pages );
101 $this->output(
"Found $count pages to update.",
'pagecount' );
103 foreach ( $pages as [ $title, $text ] ) {
104 $this->updateMessage( $title, TRANSLATE_FUZZY . $text, $dryrun, $comment );
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;
121 $content = $this->revisionStore
122 ->newRevisionFromRow( $row, IDBAccessObject::READ_NORMAL, $title )
123 ->getContent( SlotRecord::MAIN );
124 $text = Utilities::getTextFromTextContent( $content );
126 $messagesContents[] = [ $title, $text ];
128 return $messagesContents;
132 private function getPagesForPattern( $pattern, $skipLanguages = [] ) {
133 $dbr = $this->DBLoadBalancer->getMaintenanceConnectionRef( DB_REPLICA );
136 foreach ( (array)$pattern as $title ) {
137 $title = Title::newFromText( $title );
138 $ns = $title->getNamespace();
139 if ( !isset( $search[$ns] ) ) {
142 $search[$ns][] =
'page_title' . $dbr->buildLike( $title->getDBkey(), $dbr->anyString() );
146 foreach ( $search as $ns => $names ) {
147 if ( $ns === NS_MAIN ) {
148 $ns = $this->getConfig()->get(
'TranslateMessageNamespaces' );
150 $titles = $dbr->makeList( $names, LIST_OR );
151 $title_conds[] = $dbr->makeList( [
'page_namespace' => $ns, $titles ], LIST_AND );
155 'page_latest=rev_id',
156 $dbr->makeList( $title_conds, LIST_OR ),
159 if ( count( $skipLanguages ) ) {
160 $skiplist = $dbr->makeList( $skipLanguages );
161 $conds[] =
"substring_index(page_title, '/', -1) NOT IN ($skiplist)";
164 $queryInfo = $this->revisionStore->getQueryInfo( [
'page' ] );
165 $rows = $dbr->select(
166 $queryInfo[
'tables'],
167 $queryInfo[
'fields'],
173 return $this->getMessageContentsFromRows( $rows );
176 private function getPagesForUser( User $user, $skipLanguages = [] ) {
177 $dbr = $this->DBLoadBalancer->getMaintenanceConnectionRef( DB_REPLICA );
179 $revWhere = $this->actorMigration->getWhere( $dbr,
'rev_user', $user );
181 'page_latest=rev_id',
183 'page_namespace' => $this->getConfig()->get(
'TranslateMessageNamespaces' ),
184 'page_title' . $dbr->buildLike( $dbr->anyString(),
'/', $dbr->anyString() ),
186 if ( count( $skipLanguages ) ) {
187 $skiplist = $dbr->makeList( $skipLanguages );
188 $conds[] =
"substring_index(page_title, '/', -1) NOT IN ($skiplist)";
191 $queryInfo = $this->revisionStore->getQueryInfo( [
'page',
'user' ] );
192 $rows = $dbr->select(
193 $queryInfo[
'tables'],
194 $queryInfo[
'fields'],
198 $queryInfo[
'joins'] + $revWhere[
'joins']
201 return $this->getMessageContentsFromRows( $rows );
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 );
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 );
228 $this->output(
'DRY RUN!', $title );
233 $wikipage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
234 $content = ContentHandler::makeContent( $text, $title );
235 $status = $wikipage->doUserEditContent(
238 $comment ?:
'Marking as fuzzy',
239 EDIT_FORCE_BOT | EDIT_UPDATE
242 $this->output( $status->isOK() ?
'OK' :
'FAILED', $title );