Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.24% covered (success)
90.24%
74 / 82
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
MigrateCampaigns
97.37% covered (success)
97.37%
74 / 76
75.00% covered (warning)
75.00%
3 / 4
14
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 doDBUpdates
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
5.03
 doCampaign
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
1 / 1
7
 getUpdateKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\MediaUploader\Maintenance;
4
5use CommentStoreComment;
6use LoggedUpdateMaintenance;
7use MediaWiki\Extension\MediaUploader\Campaign\CampaignContent;
8use MediaWiki\Extension\MediaUploader\MediaUploaderServices;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Page\ExistingPageRecord;
11use MediaWiki\Revision\SlotRecord;
12use Message;
13use Symfony\Component\Yaml\Yaml;
14use WikiPage;
15
16$IP = getenv( 'MW_INSTALL_PATH' );
17if ( $IP === false ) {
18    $IP = dirname( __DIR__, 3 );
19}
20require_once "$IP/maintenance/Maintenance.php";
21
22class MigrateCampaigns extends LoggedUpdateMaintenance {
23
24    public function __construct() {
25        parent::__construct();
26        $this->requireExtension( 'MediaUploader' );
27        $this->addDescription(
28            "Inspects campaign definitions carried over from UploadWizard and\n" .
29            "converts them to YAML.\n"
30        );
31        $this->addOption(
32            'dry-run',
33            'Do not convert campaign pages to YAML, just list the issues.'
34        );
35    }
36
37    /**
38     * @inheritDoc
39     */
40    protected function doDBUpdates() {
41        $services = MediaWikiServices::getInstance();
42        $pageStore = $services->getPageStore();
43        $wikiPageFactory = $services->getWikiPageFactory();
44
45        $dbr = $this->getDB( DB_REPLICA );
46
47        $this->output( "Inspecting MediaUploader campaigns...\n" );
48        $pageRecords = $pageStore->newSelectQueryBuilder( $dbr )
49            ->whereNamespace( NS_CAMPAIGN )
50            ->fetchPageRecords();
51
52        /** @var ExistingPageRecord $record */
53        foreach ( $pageRecords as $record ) {
54            if ( $record->isRedirect() ) {
55                continue;
56            }
57            if ( $record->getDBkey() === CampaignContent::GLOBAL_CONFIG_ANCHOR_DBKEY ) {
58                continue;
59            }
60
61            $page = $wikiPageFactory->newFromTitle( $record );
62            $content = $page->getContent();
63            if ( !( $content instanceof CampaignContent ) ) {
64                continue;
65            }
66
67            $this->doCampaign( $content, $page );
68        }
69
70        $this->output( "\n\n" );
71        return true;
72    }
73
74    /**
75     * Applies fixes/prettification to a campaign.
76     *
77     * @param CampaignContent $content
78     * @param WikiPage $page
79     */
80    private function doCampaign( CampaignContent $content, WikiPage $page ) {
81        $status = $content->getValidationStatus();
82        $titleText = $page->getTitle()->getPrefixedText();
83        $dryRun = $this->getOption( 'dry-run' );
84        $toFix = 0;
85
86        $this->output( "\n\n---- $titleText\n" );
87
88        if ( $status->isGood() ) {
89            $this->output( "VALID\n" );
90        }
91
92        $data = null;
93        if ( $content->getData()->isGood() ) {
94            // Parse the YAML again, but this time use objects to avoid
95            // PHP's array type ambiguity (associative vs list).
96            $data = Yaml::parse(
97                $content->getText(),
98                Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE
99                | Yaml::PARSE_OBJECT_FOR_MAP
100            );
101        }
102
103        foreach ( $status->getErrors() as $error ) {
104            $this->output(
105                wfMessage( $error['message'], ...$error['params'] )->text() . "\n"
106            );
107
108            $this->output( "ERROR: This must be fixed manually.\n" );
109            $toFix++;
110        }
111
112        if ( $toFix ) {
113            $this->output( "Issues to fix: $toFix" );
114        }
115
116        if ( $dryRun ) {
117            return;
118        }
119
120        if ( $data ) {
121            // Save changes
122            $text = Yaml::dump(
123                $data,
124                10,
125                2,
126                Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE
127            );
128            // Symfony for some reason prefers to start objects in a new line following
129            // a dash (-). This is contrary to my preference, and it seems most YAML
130            // style guides out there, so this regex removes the extra newline.
131            $text = preg_replace(
132                '/^( *)-\n +([^-])/m',
133                '$1- $2',
134                $text
135            );
136            $updater = $page->newPageUpdater( MediaUploaderServices::getSystemUser() );
137            $content = new CampaignContent( $text );
138            $updater->setContent( SlotRecord::MAIN, $content );
139            $comment = CommentStoreComment::newUnsavedComment(
140                new Message( 'mediauploader-fix-campaign-comment-prettified' )
141            );
142            $updater->saveRevision( $comment, EDIT_UPDATE );
143            $this->output( "Saved changes.\n" );
144        } else {
145            $this->output( "Cannot process this campaign.\n" );
146        }
147    }
148
149    /**
150     * @inheritDoc
151     */
152    protected function getUpdateKey() {
153        return 'migrate MediaUploader campaigns';
154    }
155}
156
157$maintClass = MigrateCampaigns::class;
158
159require_once RUN_MAINTENANCE_IF_MAIN;