Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
MigrateCampaigns
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 7
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getConfigFromDB
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 explodeStringToArray
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 trimArray
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 ensureDefaultLicense
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 getConfigForJSON
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Fixes timestamp corruption caused by one or more webservers temporarily
4 * being set to the wrong time.
5 * The time offset must be known and consistent. Start and end times
6 * (in 14-character format) restrict the search, and must bracket the damage.
7 * There must be a majority of good timestamps in the search period.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * http://www.gnu.org/copyleft/gpl.html
23 *
24 * @file
25 * @ingroup Maintenance
26 * @author Yuvi Panda <yuvipanda@gmail.com>
27 */
28
29$IP = getenv( 'MW_INSTALL_PATH' );
30if ( $IP === false ) {
31    $IP = __DIR__ . '/../../..';
32}
33require_once "$IP/maintenance/Maintenance.php";
34
35use MediaWiki\Extension\UploadWizard\CampaignContent;
36use MediaWiki\Title\Title;
37
38/**
39 * Maintenance script to migrate campaigns from older, database table
40 * to newer page based storage
41 *
42 * @ingroup Maintenance
43 */
44class MigrateCampaigns extends Maintenance {
45
46    /**
47     * @var \Wikimedia\Rdbms\IDatabase
48     */
49    private $dbr = null;
50
51    public function __construct() {
52        parent::__construct();
53
54        $this->requireExtension( 'Upload Wizard' );
55        $this->addDescription( "Migrate UploadCampaigns from database storage to pages" );
56        $this->addOption( 'user', 'The user to perform the migration as', false, true, 'u' );
57    }
58
59    private const OLD_KEY_DEFAULTS = [
60        'headerLabelPage' => '',
61        'thanksLabelPage' => '',
62
63        'defaultLicenseType' => 'choice',
64        'ownWorkOption' => 'choice',
65        'licensesOwnWork' => '',
66        'defaultOwnWorkLicense' => '',
67
68        'skipTutorial' => false,
69        'tutorialTemplate' => 'Licensing_tutorial_$1.svg',
70        'tutorialWidth' => 720,
71        'tutorialHelpdeskCoords' => '27, 1319, 691, 1384',
72
73        'autoWikiText' => '',
74        'autoCategories' => '',
75
76        'defaultDescription' => '',
77        'defaultLat' => '',
78        'defaultLon' => '',
79        'defaultAlt' => '',
80        'defaultCategories' => '',
81
82        'idField' => '',
83        'idFieldLabel' => '',
84        'idFieldLabelPage' => '',
85        'idFieldMaxLength' => 25,
86        'idFieldInitialValue' => '',
87
88        'idField2' => '',
89        'idField2Label' => '',
90        'idField2LabelPage' => '',
91        'idField2MaxLength' => 25,
92        'idField2InitialValue' => ''
93    ];
94
95    private const OLD_NUMBER_CONFIGS = [
96        'idFieldMaxLength',
97        'idField2MaxLength',
98        'tutorialWidth',
99        'defaultLat',
100        'defaultLon',
101        'defaultAlt'
102    ];
103
104    /**
105     * @param int|string $id
106     * @return array
107     */
108    private function getConfigFromDB( $id ) {
109        $config = [];
110
111        $confProps = $this->dbr->newSelectQueryBuilder()
112            ->select( [ 'cc_property', 'cc_value' ] )
113            ->from( 'uw_campaign_conf' )
114            ->where( [ 'cc_campaign_id' => $id ] )
115            ->caller( __METHOD__ )
116            ->fetchResultSet();
117
118        foreach ( $confProps as $confProp ) {
119            if ( in_array( $confProp->cc_property, self::OLD_NUMBER_CONFIGS ) ) {
120                $config[$confProp->cc_property] = intval( $confProp->cc_value );
121            } else {
122                $config[$confProp->cc_property] = $confProp->cc_value;
123            }
124        }
125
126        $mergedConfig = [];
127
128        foreach ( self::OLD_KEY_DEFAULTS as $key => $default ) {
129            if ( array_key_exists( $key, $config ) && $config[$key] !== $default ) {
130                $mergedConfig[$key] = $config[$key];
131            } else {
132                $mergedConfig[$key] = null;
133            }
134        }
135
136        return $mergedConfig;
137    }
138
139    /**
140     * @param string $string
141     * @return string[]
142     */
143    private function explodeStringToArray( $string ) {
144        $parts = explode( '|', $string );
145        $array = [];
146
147        foreach ( $parts as $part ) {
148            $part = trim( $part );
149
150            if ( $part !== '' ) {
151                $array[] = $part;
152            }
153        }
154        return $array;
155    }
156
157    /**
158     * @param array $array
159     * @return array
160     */
161    private function trimArray( $array ) {
162        $newArray = [];
163        foreach ( $array as $key => $value ) {
164            if ( is_array( $value ) ) {
165                $trimmedValue = $this->trimArray( $value );
166                if ( $trimmedValue !== [] ) {
167                    $newArray[$key] = $trimmedValue;
168                }
169            } elseif ( $value !== null ) {
170                $newArray[$key] = $value;
171            }
172        }
173        return $newArray;
174    }
175
176    /**
177     * Ensure that the default license, if set, is the first
178     *
179     * @param array $licenses
180     * @param string $default
181     * @return array
182     */
183    private function ensureDefaultLicense( $licenses, $default ) {
184        if ( count( $licenses ) === 1 || ( $default === null || trim( $default ) === '' ) ) {
185            return $licenses;
186        }
187        if ( in_array( $default, $licenses ) ) {
188            array_splice( $licenses, array_search( $default, $licenses ), 1 );
189        }
190        array_unshift( $licenses, $default );
191        // FIXME: No return value
192    }
193
194    /**
195     * @param stdClass $campaign
196     * @param array $oldConfig
197     * @return array
198     */
199    private function getConfigForJSON( $campaign, $oldConfig ) {
200        $config = [
201            'enabled' => $campaign->campaign_enabled === '1',
202            'display' => [
203                'headerLabelPage' => $oldConfig['headerLabelPage'],
204                'thanksLabelPage' => $oldConfig['thanksLabelPage']
205            ],
206            'defaults' => [
207                'description' => $oldConfig['defaultDescription'],
208                'lat' => $oldConfig['defaultLat'],
209                'lon' => $oldConfig['defaultLon'],
210                'categories' => $this->explodeStringToArray( $oldConfig['defaultCategories'] )
211            ],
212            'autoAdd' => [
213                'wikitext' => $oldConfig['autoWikiText'],
214                'categories' => $this->explodeStringToArray( $oldConfig['autoCategories'] )
215            ],
216            "licensing" => [
217                'defaultType' => $oldConfig['defaultLicenseType'],
218                'ownWorkDefault' => $oldConfig['ownWorkOption'],
219                'ownWork' => [
220                    'licenses' => $this->ensureDefaultLicense(
221                        $this->explodeStringToArray( $oldConfig['licensesOwnWork'] ),
222                        $oldConfig['defaultOwnWorkLicense']
223                    )
224                ]
225            ],
226            'fields' => [
227                [
228                    'wikitext' => $oldConfig['idField'],
229                    'label' => $oldConfig['idFieldLabel'],
230                    # Migrated even though this is a nop.
231                    # People will have to migrate this manually
232                    'labelPage' => $oldConfig['idFieldLabelPage'],
233                    'maxLength' => $oldConfig['idFieldMaxLength'],
234                    'initialValue' => $oldConfig['idFieldInitialValue']
235                ],
236                [
237                    'wikitext' => $oldConfig['idField2'],
238                    'label' => $oldConfig['idField2Label'],
239                    'labelPage' => $oldConfig['idField2LabelPage'],
240                    'maxLength' => $oldConfig['idField2MaxLength'],
241                    'initialValue' => $oldConfig['idField2InitialValue']
242                ]
243            ],
244            'tutorial' => [
245                'skip' => (bool)$oldConfig['skipTutorial'],
246                'template' => $oldConfig['tutorialTemplate'],
247                'helpdeskCoords' => $oldConfig['tutorialHelpdeskCoords'],
248                'width' => $oldConfig['tutorialWidth']
249            ]
250        ];
251
252        return $this->trimArray( $config );
253    }
254
255    public function execute() {
256        $services = $this->getServiceContainer();
257
258        $username = $this->getOption( 'user', 'Maintenance script' );
259
260        $this->dbr = $services->getDBLoadBalancerFactory()->getPrimaryDatabase();
261        $campaigns = $this->dbr->newSelectQueryBuilder()
262            ->select( '*' )
263            ->from( 'uw_campaigns' )
264            ->caller( __METHOD__ )
265            ->fetchResultSet();
266
267        if ( !$campaigns->numRows() ) {
268            $this->output( "Nothing to migrate.\n" );
269            return;
270        }
271
272        $user = $services->getUserFactory()->newFromName( $username );
273        if ( !$user ) {
274            $this->fatalError( 'invalid username.' );
275        }
276        $wikiPageFactory = $services->getWikiPageFactory();
277        foreach ( $campaigns as $campaign ) {
278            $oldConfig = $this->getConfigFromDB( $campaign->campaign_id );
279            $newConfig = $this->getConfigForJSON( $campaign, $oldConfig );
280
281            $title = Title::makeTitleSafe( NS_CAMPAIGN, $campaign->campaign_name );
282            $page = $wikiPageFactory->newFromTitle( $title );
283
284            $content = new CampaignContent( json_encode( $newConfig ) );
285            $page->doUserEditContent(
286                $content,
287                $user,
288                "Migrating from old campaign tables"
289            );
290            $this->output( "Migrated {$campaign->campaign_name}\n" );
291        }
292    }
293}
294
295$maintClass = MigrateCampaigns::class;
296require_once RUN_MAINTENANCE_IF_MAIN;