Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 163
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
LoadPreDefinedObject
0.00% covered (danger)
0.00%
0 / 157
0.00% covered (danger)
0.00%
0 / 3
2352
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 1
992
 makeEdit
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
272
1<?php
2
3/**
4 * WikiLambda loadPreDefinedObject maintenance script
5 *
6 * Loads specified pre-defined Object, range of Objects, or all pre-defined Objects into the database.
7 *
8 * @file
9 * @ingroup Extensions
10 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
11 * @license MIT
12 */
13
14use MediaWiki\Extension\WikiLambda\ZObjectStore;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Title\Title;
18use MediaWiki\Title\TitleFactory;
19
20$IP = getenv( 'MW_INSTALL_PATH' );
21if ( $IP === false ) {
22    $IP = __DIR__ . '/../../..';
23}
24require_once "$IP/maintenance/Maintenance.php";
25
26class LoadPreDefinedObject extends Maintenance {
27
28    /**
29     * @inheritDoc
30     */
31    public function __construct() {
32        parent::__construct();
33        $this->requireExtension( 'WikiLambda' );
34        $this->addDescription( 'Loads a specified pre-defined Object into the database' );
35
36        $this->addOption(
37            'zid',
38            'Which ZID to load',
39            false,
40            true
41        );
42
43        $this->addOption(
44            'from',
45            'Which range of ZIDs to load from',
46            false,
47            true
48        );
49
50        $this->addOption(
51            'to',
52            'Which range of ZIDs to load to',
53            false,
54            true
55        );
56
57        $this->addOption(
58            'all',
59            'Load for all pre-defined ZIDs (Z1 – Z9999)',
60            false,
61            false
62        );
63
64        $this->addOption(
65            'force',
66            'Forces the load even if the Object already exists (clears the Object)',
67            false,
68            false
69        );
70
71        // TODO (T335418): Add a feature to reload the 'content' but retain the user-written/-extended
72        // labels, aliases, and short descriptions, for use e.g. when
73    }
74
75    /**
76     * @inheritDoc
77     */
78    public function execute() {
79        // Construct the ZObjectStore, because ServiceWiring hasn't run
80        $services = MediaWikiServices::getInstance();
81        $titleFactory = $services->getTitleFactory();
82        $zObjectStore = new ZObjectStore(
83            $services->getDBLoadBalancerFactory(),
84            $services->getTitleFactory(),
85            $services->getWikiPageFactory(),
86            $services->getRevisionStore(),
87            $services->getUserGroupManager(),
88            LoggerFactory::getInstance( 'WikiLambda' )
89        );
90
91        $from = $this->getOption( 'from' );
92        $to = $this->getOption( 'to' );
93        $all = $this->getOption( 'all' );
94        $zid = $this->getOption( 'zid' );
95        $force = $this->getOption( 'force' ) || false;
96
97        if ( $all ) {
98            // --all option overrides any --from and --to passed as arguments
99            if ( (bool)$from || (bool)$to ) {
100                $this->fatalError( 'The flag "--all" should not be used along with "--for" and "--to"' . "\n" );
101            }
102
103            $from = 1;
104            $to = 9999;
105        } elseif ( $zid ) {
106            if ( !is_numeric( $zid ) || $zid < 1 || $zid > 9999 ) {
107                $this->fatalError( 'The flag "--zid" must be a number between 1 and 9999' . "\n" );
108            }
109
110            // --zid option overrides any --from and --to passed as arguments
111            if ( (bool)$from || (bool)$to ) {
112                $this->fatalError( 'The flag "--zid" should not be used with "--for" or "--to"' . "\n" );
113            }
114
115            // --zid option also overrides --all if present
116            $from = $zid;
117            $to = $zid;
118        } else {
119            // If no --all and no --zid are entered, then --from and --to are mandatory
120            if ( (bool)$from xor (bool)$to ) {
121                $this->fatalError( 'The flag "--from" must be used with the flag "--to" to set a range' . "\n" );
122            }
123
124            if ( !is_numeric( $from ) || $from < 1 || $from > 9999 ) {
125                $this->fatalError( 'The flag "--from" must be a number between 1 and 9999' . "\n" );
126            }
127
128            if ( !is_numeric( $to ) || $to < 1 || $to > 9999 ) {
129                $this->fatalError( 'The flag "--to" must be a number between 1 and 9999' . "\n" );
130            }
131
132            if ( $from > $to ) {
133                $this->fatalError( 'The flag "--from" must be lower than the flag "--to"' . "\n" );
134            }
135        }
136
137        // Base path:
138        $path = dirname( __DIR__ ) . '/function-schemata/data/definitions/';
139
140        // Get dependencies file
141        $dependenciesFile = file_get_contents( $path . 'dependencies.json' );
142        if ( $dependenciesFile === false ) {
143            $this->fatalError(
144                'Could not load dependencies file from function-schemata sub-repository of the WikiLambda extension.'
145                . ' Have you initiated & fetched it? Try `git submodule update --init --recursive`.'
146            );
147        }
148        $dependencies = json_decode( $dependenciesFile, true );
149        $tracker = [];
150
151        // Get data files
152        $initialDataToLoadListing = array_filter(
153            scandir( $path ),
154            static function ( $key ) use ( $from, $to ) {
155                if ( preg_match( '/^Z(\d+)\.json$/', $key, $match ) ) {
156                    if ( $match[1] >= $from && $match[1] <= $to ) {
157                        return true;
158                    }
159                }
160                return false;
161            }
162        );
163
164        // Naturally sort, so Z2 gets created before Z12 etc.
165        natsort( $initialDataToLoadListing );
166
167        $success = 0;
168        $failure = 0;
169        $skipped = 0;
170        foreach ( $initialDataToLoadListing as $filename ) {
171
172            $zid = substr( $filename, 0, -5 );
173
174            switch ( $this->makeEdit( $zid, $path, $titleFactory, $zObjectStore, $force, $dependencies, $tracker ) ) {
175                case 1:
176                    $success++;
177                    break;
178
179                case -1:
180                    $failure++;
181                    break;
182
183                case 0:
184                    $skipped++;
185                    break;
186
187                default:
188                    throw new RuntimeException( 'Unrecognised return value!' );
189            }
190        }
191
192        if ( $success > 0 ) {
193            $this->output( $success . ' objects creates or updates successes.' . "\n" );
194        }
195
196        if ( $skipped > 0 ) {
197            $this->output( $skipped . ' objects creates or updates skipped.' . "\n" );
198        }
199
200        if ( $failure > 0 ) {
201            $this->fatalError( $failure . ' objects creates or updates failed.' . "\n" );
202        }
203    }
204
205    // phpcs:disable MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate
206    private function makeEdit(
207        string $zid,
208        string $path,
209        TitleFactory $titleFactory,
210        ZObjectStore $zObjectStore,
211        bool $force,
212        array $dependencies,
213        array &$tracker
214    ) {
215        $data = file_get_contents( $path . $zid . '.json' );
216
217        if ( !$data ) {
218            $this->error( 'The ZObject "' . $zid . '" was not found in the definitions folder.' );
219            return -1;
220        }
221
222        $title = $titleFactory->newFromText( $zid, NS_MAIN );
223        if ( !( $title instanceof Title ) ) {
224            $this->error( 'The ZObject title "' . $zid . '" could not be loaded somehow; invalid name?' );
225            return -1;
226        }
227
228        if ( !$title->exists() ) {
229            $creating = true;
230        } else {
231            if ( !$force ) {
232                $this->error( 'The ZObject "' . $zid . '" already exists and --force was not passed.' );
233                return 0;
234            }
235            $creating = false;
236        }
237
238        $deps = $dependencies[ $zid ];
239        foreach ( $deps as $dep ) {
240            // Avoid circular dependencies
241            if ( !in_array( $dep, $tracker ) ) {
242                array_push( $tracker, $dep );
243                // Don't force dependencies if they are already inserted, set to false
244                $depSuccess = $this->makeEdit(
245                    $dep, $path, $titleFactory, $zObjectStore, false, $dependencies, $tracker );
246                switch ( $depSuccess ) {
247                    case 1:
248                        $this->output( "Dependency: $dep successfully inserted.\n" );
249                        break;
250                    case -1:
251                        $this->output( "Dependency: $dep failed.\n" );
252                        break;
253                    case 0:
254                        $this->output( "Dependency: $dep skipped.\n" );
255                        break;
256                    default:
257                        throw new RuntimeException( 'Unrecognised return value!' );
258                }
259            }
260        }
261
262        $editSummary = wfMessage(
263            $creating
264                ? 'wikilambda-bootstrapcreationeditsummary'
265                : 'wikilambda-bootstrapupdatingeditsummary'
266        )->inLanguage( 'en' )->text();
267
268        // And we create or update the ZObject
269        $response = $zObjectStore->updateZObjectAsSystemUser(
270            /* String zid */ $zid,
271            /* String content */ $data,
272            /* Edit summary */ $editSummary,
273            /* Update flags */ $creating ? EDIT_NEW : EDIT_UPDATE
274        );
275
276        if ( $response->isOK() ) {
277            $this->output( ( $creating ? 'Created ' : 'Updated ' ) . '"' . $zid . '".' . "\n" );
278            return 1;
279        } else {
280            $this->error( "Problem " . ( $creating ? 'creating' : 'updating' ) . ' "' . $zid . "\":\n" );
281            $this->error( $response->getErrors() );
282            $this->error( "\n" );
283            return -1;
284        }
285    }
286}
287
288$maintClass = LoadPreDefinedObject::class;
289require_once RUN_MAINTENANCE_IF_MAIN;