Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 119 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ReloadBuiltinData | |
0.00% |
0 / 113 |
|
0.00% |
0 / 6 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
reloadBuiltinSafe | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
20 | |||
reloadBuiltinForce | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
20 | |||
deleteZObjectUnsafe | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getAllBuiltinZids | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * WikiLambda reloadBuiltinData maintenance script |
4 | * |
5 | * Reloads all builtin data into the database. If the builtins have been updated manually, |
6 | * this script erases all the updates, restoring the data to their first state in the files. |
7 | * |
8 | * @file |
9 | * @ingroup Extensions |
10 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
11 | * @license MIT |
12 | */ |
13 | |
14 | use MediaWiki\Extension\WikiLambda\Hooks; |
15 | use MediaWiki\Extension\WikiLambda\ZObjectStore; |
16 | use MediaWiki\Logger\LoggerFactory; |
17 | use MediaWiki\MediaWikiServices; |
18 | use MediaWiki\Page\DeletePageFactory; |
19 | use MediaWiki\Page\WikiPageFactory; |
20 | use MediaWiki\Title\TitleFactory; |
21 | |
22 | $IP = getenv( 'MW_INSTALL_PATH' ); |
23 | if ( $IP === false ) { |
24 | $IP = __DIR__ . '/../../..'; |
25 | } |
26 | require_once "$IP/maintenance/Maintenance.php"; |
27 | |
28 | class ReloadBuiltinData extends Maintenance { |
29 | |
30 | // TODO (T335418): Magical future of having changes re-apply |
31 | |
32 | /** |
33 | * @var ZObjectStore |
34 | */ |
35 | private $zObjectStore = null; |
36 | |
37 | /** |
38 | * @var TitleFactory |
39 | */ |
40 | protected $titleFactory; |
41 | |
42 | /** |
43 | * @var WikiPageFactory |
44 | */ |
45 | protected $wikiPageFactory; |
46 | |
47 | /** |
48 | * @var DeletePageFactory |
49 | */ |
50 | protected $deletePageFactory; |
51 | |
52 | /** |
53 | * @inheritDoc |
54 | */ |
55 | public function __construct() { |
56 | parent::__construct(); |
57 | $this->requireExtension( 'WikiLambda' ); |
58 | $this->addDescription( 'Restores saved ZObjects with the updated builtin ZObjects from the data directory' ); |
59 | |
60 | $this->addOption( |
61 | 'force', |
62 | 'Forces the reload by clearing all secondary data and doing ' |
63 | . 'an orderly insertion in case a normal reload fails', |
64 | false, |
65 | false |
66 | ); |
67 | |
68 | $this->addOption( |
69 | 'clear', |
70 | 'Clears all the non built-in ZObjects. The "--clear" flag can only ' |
71 | . 'be used along with the "--force" flag.', |
72 | false, |
73 | false |
74 | ); |
75 | } |
76 | |
77 | /** |
78 | * @inheritDoc |
79 | */ |
80 | public function execute() { |
81 | // Construct the ZObjectStore, because ServiceWiring hasn't run |
82 | $services = MediaWikiServices::getInstance(); |
83 | $this->titleFactory = $services->getTitleFactory(); |
84 | $this->wikiPageFactory = $services->getWikiPageFactory(); |
85 | $this->deletePageFactory = $services->getDeletePageFactory(); |
86 | $this->zObjectStore = new ZObjectStore( |
87 | $services->getDBLoadBalancerFactory(), |
88 | $services->getTitleFactory(), |
89 | $services->getWikiPageFactory(), |
90 | $services->getRevisionStore(), |
91 | $services->getUserGroupManager(), |
92 | LoggerFactory::getInstance( 'WikiLambda' ) |
93 | ); |
94 | |
95 | $force = $this->getOption( 'force' ); |
96 | $clear = $this->getOption( 'clear' ); |
97 | |
98 | if ( $force ) { |
99 | $this->reloadBuiltinForce( $clear === 1 ); |
100 | } else { |
101 | if ( $clear ) { |
102 | // We could simply call reloadBuiltinForce( true ) if the user calls the script |
103 | // with the flag --clear, but this is just to make sure that the user is aware |
104 | // of the process occurring, as the force option means total secondary tables being |
105 | // wiped. |
106 | $this->output( 'The flag "--clear" should only be used along with the flag "--force"' ); |
107 | $this->output( "\n" ); |
108 | } else { |
109 | $this->reloadBuiltinSafe(); |
110 | } |
111 | } |
112 | } |
113 | |
114 | /** |
115 | * Re-inserts all the builtin ZObject available in data/definitions. |
116 | * This will not work as an initial data load, and might fail when there's |
117 | * complex migrations and some objects become not valid. In such case, |
118 | * one must run the script with the "--force" flag. |
119 | */ |
120 | private function reloadBuiltinSafe() { |
121 | $initialDataToLoadPath = dirname( __DIR__ ) . '/function-schemata/data/definitions/'; |
122 | $initialDataToLoadListing = array_filter( |
123 | scandir( $initialDataToLoadPath ), |
124 | static function ( $key ) { |
125 | return (bool)preg_match( '/^Z\d+\.json$/', $key ); |
126 | } |
127 | ); |
128 | |
129 | $creatingComment = wfMessage( 'wikilambda-bootstrapupdatingeditsummary' )->inLanguage( 'en' )->text(); |
130 | |
131 | // Naturally sort, so Z2 gets created before Z12 etc. |
132 | natsort( $initialDataToLoadListing ); |
133 | |
134 | foreach ( $initialDataToLoadListing as $filename ) { |
135 | $zid = substr( $filename, 0, -5 ); |
136 | $data = file_get_contents( $initialDataToLoadPath . $filename ); |
137 | |
138 | if ( !$data ) { |
139 | // Something went wrong, give up. |
140 | return; |
141 | } |
142 | |
143 | // And we update the data |
144 | $response = $this->zObjectStore->updateZObjectAsSystemUser( |
145 | /* String zid */ $zid, |
146 | /* String content */ $data, |
147 | /* Edit summary */ $creatingComment, |
148 | /* Update flags */ EDIT_UPDATE |
149 | ); |
150 | |
151 | if ( $response->isOK() ) { |
152 | $this->output( "Updated $zid \n" ); |
153 | } else { |
154 | $this->output( "Problem updating $zid \n" ); |
155 | $this->output( $response->getErrors() ); |
156 | $this->output( "\n" ); |
157 | } |
158 | } |
159 | } |
160 | |
161 | /** |
162 | * Re-inserts all the builtin ZObject available in data/definitions. |
163 | * This method will make sure that the insertion ends successfully, by |
164 | * taking drastic measures. It will first clear all the secondary tables |
165 | * to avoid possible conflicts, and then will call the createInitialContent |
166 | * hook so that the insertions happen in the right order. |
167 | * |
168 | * @param bool $clear Whether to also clear the non-builtin objects |
169 | */ |
170 | private function reloadBuiltinForce( $clear ) { |
171 | // 1. Get builtin and custom Zid arrays |
172 | $allZids = $this->zObjectStore->fetchAllZids(); |
173 | $builtinZids = self::getAllBuiltinZids(); |
174 | $customZids = array_filter( |
175 | $allZids, static function ( $zid ) use ( $builtinZids ) { |
176 | return !in_array( $zid, $builtinZids ); |
177 | } |
178 | ); |
179 | |
180 | if ( $clear ) { |
181 | $user = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] ); |
182 | |
183 | // 2.a. Delete all non-builtin ZObjects |
184 | foreach ( $customZids as $zid ) { |
185 | $status = $this->deleteZObjectUnsafe( |
186 | $zid, |
187 | $user, |
188 | 'Maintenance script reloadBuiltinData --force --clear' |
189 | ); |
190 | if ( $status->isOK() ) { |
191 | $this->output( "Deleted custom ZObject $zid \n" ); |
192 | } else { |
193 | $this->output( "Problem deleting custom ZObject $zid \n" ); |
194 | $this->output( var_export( $status->getErrors(), true ) ); |
195 | $this->output( "\n" ); |
196 | return; |
197 | } |
198 | } |
199 | // 2.b. Clear all secondary tables |
200 | $this->zObjectStore->clearLabelsSecondaryTables(); |
201 | $this->zObjectStore->clearFunctionsSecondaryTables(); |
202 | $this->zObjectStore->clearTesterResultsSecondaryTables(); |
203 | $this->zObjectStore->clearLanguageCacheSecondaryTables(); |
204 | } else { |
205 | // 2. Clear only builtin zids from secondary tables |
206 | $this->zObjectStore->deleteFromLabelsSecondaryTables( $builtinZids ); |
207 | $this->zObjectStore->deleteFromFunctionsSecondaryTables( $builtinZids ); |
208 | $this->zObjectStore->deleteFromTesterResultsSecondaryTables( $builtinZids ); |
209 | $this->zObjectStore->deleteFromLanguageCacheSecondaryTables( $builtinZids ); |
210 | } |
211 | |
212 | // 3. Call Hooks:createInitialContent |
213 | $updater = DatabaseUpdater::newForDB( $this->getDB( DB_PRIMARY ), true, $this ); |
214 | Hooks::createInitialContent( $updater, true ); |
215 | } |
216 | |
217 | /** |
218 | * Deletes a ZObject if the user is allowed to do so. This method is only |
219 | * for its use by reloadBuiltinData with the --force and --clear flags. |
220 | * |
221 | * @param string $zid |
222 | * @param User $user |
223 | * @param string $reason |
224 | * @return StatusValue |
225 | */ |
226 | private function deleteZObjectUnsafe( $zid, $user, $reason = '' ): StatusValue { |
227 | $title = $this->titleFactory->newFromText( $zid, NS_MAIN ); |
228 | if ( !( $title instanceof Title ) ) { |
229 | return StatusValue::newFatal( 'wikilambda-invalidzobjecttitle' ); |
230 | } |
231 | $page = $this->wikiPageFactory->newFromTitle( $title ); |
232 | $deletePage = $this->deletePageFactory->newDeletePage( $page, $user ); |
233 | return $deletePage->deleteUnsafe( $reason ); |
234 | } |
235 | |
236 | /** |
237 | * Returns the array of built-in ZIDS |
238 | * |
239 | * @return array |
240 | */ |
241 | private static function getAllBuiltinZids() { |
242 | $dataPath = dirname( __DIR__ ) . '/function-schemata/data/definitions/'; |
243 | $filenames = array_filter( |
244 | scandir( $dataPath ), |
245 | static function ( $key ) { |
246 | return (bool)preg_match( '/^Z\d+\.json$/', $key ); |
247 | } |
248 | ); |
249 | $zids = array_map( |
250 | static function ( $filename ) { |
251 | return substr( $filename, 0, -5 ); |
252 | }, |
253 | $filenames |
254 | ); |
255 | return $zids; |
256 | } |
257 | } |
258 | |
259 | $maintClass = ReloadBuiltinData::class; |
260 | require_once RUN_MAINTENANCE_IF_MAIN; |