Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeleteEqualTranslationsMaintenanceScript
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 7
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 getEqualMessages
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getUserStats
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 printUserStats
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 printMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 deleteMessages
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Diagnostics;
5
6use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups;
7use MediaWiki\Extension\Translate\MessageLoading\Message;
8use MediaWiki\Extension\Translate\MessageLoading\MessageCollection;
9use MediaWiki\Extension\Translate\SystemUsers\FuzzyBot;
10use MediaWiki\Extension\Translate\Utilities\BaseMaintenanceScript;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Title\Title;
13use MediaWiki\Title\TitleValue;
14use SplObjectStorage;
15use const SORT_NUMERIC;
16
17/**
18 * @since 2021.01
19 * @license GPL-2.0-or-later
20 * @author Niklas Laxström
21 */
22class DeleteEqualTranslationsMaintenanceScript extends BaseMaintenanceScript {
23    public function __construct() {
24        parent::__construct();
25        $this->addDescription( 'Delete translations that are equal to the definition' );
26
27        $this->addOption(
28            'group',
29            'Which group to scan',
30            self::REQUIRED,
31            self::HAS_ARG
32        );
33        $this->addOption(
34            'language',
35            'Which language to scan',
36            self::REQUIRED,
37            self::HAS_ARG
38        );
39        $this->addOption(
40            'really',
41            'Delete the listed pages instead of just listing them'
42        );
43        $this->addOption(
44            'comment',
45            'Comment for the deletions'
46        );
47
48        $this->requireExtension( 'Translate' );
49    }
50
51    /** @inheritDoc */
52    public function execute() {
53        $groupId = $this->getOption( 'group' );
54        $language = $this->getOption( 'language' );
55        $group = MessageGroups::getGroup( $groupId );
56        if ( !$group ) {
57            $this->fatalError( "Message group '$groupId' does not exist" );
58        }
59
60        $collection = $group->initCollection( $language );
61        $equalMessages = $this->getEqualMessages( $collection );
62        $equalMessageCount = count( $equalMessages );
63        if ( $equalMessageCount === 0 ) {
64            $this->output( "No translations equal to definition found\n" );
65            return;
66        }
67
68        $stats = $this->getUserStats( $equalMessages );
69        $this->printUserStats( $stats, $equalMessageCount );
70        $this->output( "\n" );
71        $this->printMessages( $equalMessages );
72
73        if ( $this->hasOption( 'really' ) ) {
74            $comment = $this->getOption( 'comment' ) ?? '';
75            $this->deleteMessages( $equalMessages, $comment );
76        } else {
77            $this->output( "This is a dry-run. Run again with --really to delete these messages\n" );
78        }
79    }
80
81    private function getEqualMessages( MessageCollection $collection ): SplObjectStorage {
82        $collection->filter( MessageCollection::FILTER_TRANSLATED, MessageCollection::INCLUDE_MATCHING );
83        $collection->loadTranslations();
84
85        $messages = new SplObjectStorage();
86        foreach ( $collection->keys() as $key => $titleValue ) {
87            /** @var Message $message */
88            $message = $collection[$key];
89
90            if ( $message->definition() === $message->translation() ) {
91                $messages->attach( $titleValue, $message );
92            }
93        }
94
95        return $messages;
96    }
97
98    private function getUserStats( SplObjectStorage $messages ): array {
99        $stats = [];
100        foreach ( $messages as $key ) {
101            /** @var Message $message */
102            $message = $messages[$key];
103            $index = $message->getProperty( 'last-translator-text' );
104            $stats[$index] = ( $stats[$index] ?? 0 ) + 1;
105        }
106
107        return $stats;
108    }
109
110    private function printUserStats( array $stats, int $equalMessageCount ): void {
111        $this->output( "Found $equalMessageCount message(s) created by these user(s):\n" );
112        arsort( $stats, SORT_NUMERIC );
113        foreach ( $stats as $userName => $count ) {
114            $this->output( sprintf( "%6d | %s\n", $count, $userName ) );
115        }
116    }
117
118    private function printMessages( SplObjectStorage $messages ): void {
119        /** @var TitleValue $key */
120        foreach ( $messages as $key ) {
121            /** @var Message $message */
122            $message = $messages[$key];
123            $title = Title::newFromLinkTarget( $key );
124            $this->output(
125                sprintf( "== %s ==\n%s\n\n", $title->getPrefixedText(), $message->translation() )
126            );
127        }
128    }
129
130    private function deleteMessages( SplObjectStorage $messages, string $reason ): void {
131        $services = MediaWikiServices::getInstance();
132        $wikiPageFactory = $services->getWikiPageFactory();
133        $deletePageFactory = $services->getDeletePageFactory();
134
135        $user = FuzzyBot::getUser();
136
137        /** @var TitleValue $key */
138        foreach ( $messages as $key ) {
139            $title = Title::newFromLinkTarget( $key );
140            $page = $wikiPageFactory->newFromTitle( $title );
141            $status = $deletePageFactory->newDeletePage( $page, $user )
142                ->deleteUnsafe( $reason );
143
144            if ( $status->isOK() ) {
145                $this->output( '.', 'deletions' );
146            } else {
147                $pageName = $title->getPrefixedText();
148                $this->output( "FAILED to delete page $pageName\n" );
149            }
150        }
151    }
152}