Difference between revisions of "SMWIssue4785"
Jump to navigation
Jump to search
Line 588: | Line 588: | ||
</source> | </source> | ||
= SetupFile.php = | = SetupFile.php = | ||
− | <source lang='php' line highlight='181 | + | <source lang='php' line highlight='181,516-519'> |
<?php | <?php | ||
Revision as of 08:29, 24 May 2021
Modified files for https://github.com/SemanticMediaWiki/SemanticMediaWiki/issues/4785
SetupCheck.php
1<?php
2
3namespace SMW;
4
5use SMW\Utils\TemplateEngine;
6use SMW\Utils\Logo;
7use SMW\Localizer\LocalMessageProvider;
8use SMW\Exception\FileNotReadableException;
9use SMW\Exception\JSONFileParseException;
10use RuntimeException;
11
12/**
13 * @private
14 *
15 * @license GNU GPL v2+
16 * @since 3.1
17 *
18 * @author mwjames
19 */
20class SetupCheck {
21
22 /**
23 * Semantic MediaWiki was loaded or accessed but not correctly enabled.
24 */
25 const ERROR_EXTENSION_LOAD = 'ERROR_EXTENSION_LOAD';
26
27 /**
28 * Semantic MediaWiki was loaded or accessed but not correctly enabled.
29 */
30 const ERROR_EXTENSION_INVALID_ACCESS = 'ERROR_EXTENSION_INVALID_ACCESS';
31
32 /**
33 * A user tried to use `wfLoadExtension( 'SemanticMediaWiki' )` and
34 * `enableSemantics` at the same causing the ExtensionRegistry to throw an
35 * "Uncaught Exception: It was attempted to load SemanticMediaWiki twice ..."
36 */
37 const ERROR_EXTENSION_REGISTRY = 'ERROR_EXTENSION_REGISTRY';
38
39 /**
40 * A dependency (extension, MediaWiki) causes an error
41 */
42 const ERROR_EXTENSION_DEPENDENCY = 'ERROR_EXTENSION_DEPENDENCY';
43
44 /**
45 * Multiple dependencies (extension, MediaWiki) caused an error
46 */
47 const ERROR_EXTENSION_DEPENDENCY_MULTIPLE = 'ERROR_EXTENSION_DEPENDENCY_MULTIPLE';
48
49 /**
50 * Extension doesn't match MediaWiki or the PHP requirement.
51 */
52 const ERROR_EXTENSION_INCOMPATIBLE = 'ERROR_EXTENSION_INCOMPATIBLE';
53
54 /**
55 * Extension doesn't match the DB requirement for Semantic MediaWiki.
56 */
57 const ERROR_DB_REQUIREMENT_INCOMPATIBLE = 'ERROR_DB_REQUIREMENT_INCOMPATIBLE';
58
59 /**
60 * The upgrade key has change causing the schema to be invalid
61 */
62 const ERROR_SCHEMA_INVALID_KEY = 'ERROR_SCHEMA_INVALID_KEY';
63
64 /**
65 * A selected default profile could not be loaded or is unknown.
66 */
67 const ERROR_CONFIG_PROFILE_UNKNOWN = 'ERROR_CONFIG_PROFILE_UNKNOWN';
68
69 /**
70 * The system is currently in a maintenance window
71 */
72 const MAINTENANCE_MODE = 'MAINTENANCE_MODE';
73
74 /**
75 * @var []
76 */
77 private $options = [];
78
79 /**
80 * @var SetupFile
81 */
82 private $setupFile;
83
84 /**
85 * @var TemplateEngine
86 */
87 private $templateEngine;
88
89 /**
90 * @var LocalMessageProvider
91 */
92 private $localMessageProvider;
93
94 /**
95 * @var []
96 */
97 private $definitions = [];
98
99 /**
100 * @var string
101 */
102 private $languageCode = 'en';
103
104 /**
105 * @var string
106 */
107 private $fallbackLanguageCode = 'en';
108
109 /**
110 * @var boolean
111 */
112 private $sentHeader = true;
113
114 /**
115 * @var string
116 */
117 private $errorType = '';
118
119 /**
120 * @var string
121 */
122 private $errorMessage = '';
123
124 /**
125 * @var string
126 */
127 private $traceString = '';
128
129 /**
130 * @since 3.1
131 *
132 * @param array $vars
133 * @param SetupFile|null $setupFile
134 */
135 public function __construct( array $options, SetupFile $setupFile = null ) {
136 $this->options = $options;
137 $this->setupFile = $setupFile;
138 $this->templateEngine = new TemplateEngine();
139 $this->localMessageProvider = new LocalMessageProvider( '/local/setupcheck.i18n.json' );
140
141 if ( $this->setupFile === null ) {
142 $this->setupFile = new SetupFile();
143 }
144 }
145
146 /**
147 * @since 3.2
148 *
149 * @param string $file
150 *
151 * @return array
152 * @throws RuntimeException
153 */
154 public static function readFromFile( string $file ) : array {
155
156 if ( !is_readable( $file ) ) {
157 throw new FileNotReadableException( $file );
158 }
159
160 $contents = json_decode(
161 file_get_contents( $file ),
162 true
163 );
164
165 if ( json_last_error() === JSON_ERROR_NONE ) {
166 return $contents;
167 }
168
169 throw new JSONFileParseException( $file );
170 }
171
172 /**
173 * @since 3.1
174 *
175 * @param SetupFile|null $setupFile
176 *
177 * @return SetupCheck
178 */
179 public static function newFromDefaults( SetupFile $setupFile = null ) {
180
181 if ( !defined( 'SMW_VERSION' ) ) {
182 $version = self::readFromFile( $GLOBALS['smwgIP'] . 'extension.json' )['version'];
183 } else {
184 $version = SMW_VERSION;
185 }
186
187 $setupCheck = new SetupCheck(
188 [
189 'SMW_VERSION' => $version,
190 'MW_VERSION' => $GLOBALS['wgVersion'], // MW_VERSION may not yet be defined!!
191 'wgLanguageCode' => $GLOBALS['wgLanguageCode'],
192 'smwgUpgradeKey' => $GLOBALS['smwgUpgradeKey']
193 ],
194 $setupFile
195 );
196
197 return $setupCheck;
198 }
199
200 /**
201 * @since 3.2
202 */
203 public function disableHeader() {
204 $this->sentHeader = false;
205 }
206
207 /**
208 * @since 3.1
209 *
210 * @return boolean
211 */
212 public function isCli() {
213 return PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg';
214 }
215
216 /**
217 * @since 3.1
218 *
219 * @param string $traceString
220 */
221 public function setTraceString( $traceString ) {
222 $this->traceString = $traceString;
223 }
224
225 /**
226 * @since 3.2
227 *
228 * @param string $errorMessage
229 */
230 public function setErrorMessage( string $errorMessage ) {
231 $this->errorMessage = $errorMessage;
232 }
233
234 /**
235 * @since 3.2
236 *
237 * @param string $errorType
238 */
239 public function setErrorType( string $errorType ) {
240 $this->errorType = $errorType;
241 }
242
243 /**
244 * @since 3.2
245 *
246 * @return boolean
247 */
248 public function isError( string $error ) : bool {
249 return $this->errorType === $error;
250 }
251
252 /**
253 * @since 3.1
254 *
255 * @return boolean
256 */
257 public function hasError() {
258
259 $this->errorType = '';
260
261 // When it is not a test run or run from the command line we expect that
262 // the extension is registered using `enableSemantics`
263 if ( !defined( 'SMW_EXTENSION_LOADED' ) && !$this->isCli() ) {
264 $this->errorType = self::ERROR_EXTENSION_LOAD;
265 } elseif ( $this->setupFile->inMaintenanceMode() ) {
266 $this->errorType = self::MAINTENANCE_MODE;
267 } elseif ( !$this->isCli() && !$this->setupFile->hasDatabaseMinRequirement() ) {
268 $this->errorType = self::ERROR_DB_REQUIREMENT_INCOMPATIBLE;
269 } elseif ( $this->setupFile->isGoodSchema() === false ) {
270 $this->errorType = self::ERROR_SCHEMA_INVALID_KEY;
271 }
272
273 return $this->errorType !== '';
274 }
275
276 /**
277 * @note Adding a new error type requires to:
278 *
279 * - Define a constant to clearly identify the type of error
280 * - Extend the `setupcheck.json` to add a definition for the new type and
281 * specify which information should be displayed
282 * - In case the existing HTML elements aren't sufficient, create a new
283 * zxy.ms file and define the HTML code
284 *
285 * The `TemplateEngine` will replace arguments defined in the HTML hereby
286 * absolving this class from any direct HTML manipulation.
287 *
288 * @since 3.1
289 *
290 * @param boolean $isCli
291 *
292 * @return string
293 */
294 public function getError( $isCli = false ) {
295
296 $error = [
297 'title' => '',
298 'content' => ''
299 ];
300
301 $this->languageCode = $_GET['uselang'] ?? $this->options['wgLanguageCode'] ?? 'en';
302
303 // Output forms for different error types are registered with a JSON file.
304 $this->definitions = $this->readFromFile(
305 $GLOBALS['smwgDir'] . '/data/template/setupcheck/setupcheck.json'
306 );
307
308 // Error messages are specified in a special i18n JSON file to avoid relying
309 // on the MW message system especially when SMW isn't fully registered
310 // and we are unable to access any `smw-...` message keys from the standard
311 // i18n files.
312 $this->localMessageProvider->setLanguageCode(
313 $this->languageCode
314 );
315
316 $this->localMessageProvider->loadMessages();
317
318 // HTML specific formatting is contained in the following files where
319 // a defined group of targets correspond to types used in the JSON
320 $this->templateEngine->bulkLoad(
321 [
322 '/setupcheck/setupcheck.ms' => 'setupcheck-html',
323 '/setupcheck/setupcheck.progress.ms' => 'setupcheck-progress',
324
325 // Target specific elements
326 '/setupcheck/setupcheck.section.ms' => 'section',
327 '/setupcheck/setupcheck.version.ms' => 'version',
328 '/setupcheck/setupcheck.paragraph.ms' => 'paragraph',
329 '/setupcheck/setupcheck.errorbox.ms' => 'errorbox',
330 '/setupcheck/setupcheck.db.requirement.ms' => 'db-requirement',
331 ]
332 );
333
334 if ( !isset( $this->definitions['error_types'][$this->errorType] ) ) {
335 throw new RuntimeException( "The `{$this->errorType}` type is not defined in the `setupcheck.json`!" );
336 }
337
338 $error = $this->createErrorContent( $this->errorType );
339
340 if ( $isCli === false ) {
341 $content = $this->buildHTML( $error );
342 $this->header( 'Content-Type: text/html; charset=UTF-8' );
343 $this->header( 'Content-Length: ' . strlen( $content ) );
344 $this->header( 'Cache-control: none' );
345 $this->header( 'Pragma: no-cache' );
346 } else {
347 $content = $error['title'] . "\n\n" . $error['content'];
348 $content = str_replace(
349 [ '<!-- ROW -->', '</h3>', '</h4>', '</p>', ' ' ],
350 [ "\n", "\n\n", "\n\n", "\n\n", ' ' ],
351 $content
352 );
353 $content = "\n" . wordwrap( strip_tags( trim( $content ) ), 73 );
354 }
355
356 return $content;
357 }
358
359 /**
360 * @since 3.1
361 *
362 * @param boolean $isCli
363 */
364 public function showErrorAndAbort( $isCli = false ) {
365
366 echo $this->getError( $isCli );
367
368 if ( ob_get_level() ) {
369 ob_flush();
370 flush();
371 ob_end_clean();
372 }
373
374 die();
375 }
376
377 private function header( $text ) {
378 if ( $this->sentHeader ) {
379 header( $text );
380 }
381 }
382
383 private function schemaError() {
384 // get trace
385 // https://stackoverflow.com/a/7039409/1497139
386 //
387 $e = new \Exception;
388 $content ='<pre>'.$e->getTraceAsString().'</pre>';
389 $content .='PHP_SAPI: '.PHP_SAPI."<br>";
390 $isCli=$this->isCli();
391 $schemaState=SetupFile::getSchemaState($isCli);
392 $content.='Schema state: '.$schemaState."<br>";
393 $upgradeKeyBase=SetupFile::makeKey($GLOBALS);
394 $content.='upgrade key base: '.$upgradeKeyBase;
395 return $content;
396 }
397
398 private function createErrorContent( $type ) {
399
400 $indicator_title = 'Error';
401 $template = $this->definitions['error_types'][$type];
402 $content = '';
403
404 if ($type==self::ERROR_SCHEMA_INVALID_KEY) {
405 $content.=$this->schemaError($this->isCli());
406 }
407
408 /**
409 * Actual output form
410 */
411 foreach ( $template['output_form'] as $value ) {
412 $content .= $this->createContent( $value, $type );
413 }
414
415 /**
416 * Special handling for the progress output
417 */
418 if ( isset( $template['progress'] ) ) {
419 foreach ( $template['progress'] as $value ) {
420 $text = $this->createCopy( $value['text'] );
421
422 if ( isset( $value['progress_keys'] ) ) {
423 $content .= $this->createProgressIndicator( $value );
424 }
425
426 $args = [
427 'text' => $text,
428 'template' => $value['type']
429 ];
430
431 $this->templateEngine->compile(
432 $value['type'],
433 $args
434 );
435
436 $content .= $this->templateEngine->publish( $value['type'] );
437 }
438 }
439
440 /**
441 * Special handling for the stack trace output
442 */
443 if ( isset( $template['stack_trace'] ) && $this->traceString !== '' ) {
444 foreach ( $template['stack_trace'] as $value ) {
445 $content .= $this->createContent( $value, $type );
446 }
447 }
448
449 if ( isset( $template['indicator_title'] ) ) {
450 $indicator_title = $this->createCopy( $template['indicator_title'] );
451 }
452
453 $error = [
454 'title' => 'Semantic MediaWiki',
455 'indicator_title' => $indicator_title,
456 'content' => $content,
457 'borderColor' => $template['indicator_color']
458 ];
459
460 return $error;
461 }
462
463 private function createContent( $value, $type ) {
464
465 if ( $value['text'] === 'ERROR_TEXT' ) {
466 $text = str_replace( "\n", '<br>', $this->errorMessage );
467 } elseif ( $value['text'] === 'ERROR_TEXT_MULTIPLE' ) {
468 $errors = explode( "\n", $this->errorMessage );
469 $text = '<ul><li>' . implode( '</li><li>', array_filter( $errors ) ) . '</li></ul>';
470 } elseif ( $value['text'] === 'TRACE_STRING' ) {
471 $text = $this->traceString;
472 } else {
473 $text = $this->createCopy( $value['text'] );
474 }
475
476 $args = [
477 'text' => $text,
478 'template' => $value['type']
479 ];
480
481 if ( $value['type'] === 'version' ) {
482 $args['version-title'] = $text;
483 $args['smw-title'] = 'Semantic MediaWiki';
484 $args['smw-version'] = $this->options['SMW_VERSION'] ?? 'n/a';
485 $args['smw-upgradekey'] = $this->options['smwgUpgradeKey'] ?? 'n/a';
486 $args['mw-title'] = 'MediaWiki';
487 $args['mw-version'] = $this->options['MW_VERSION'] ?? 'n/a';
488 $args['code-title'] = $this->createCopy( 'smw-setupcheck-code' );
489 $args['code-type'] = $type;
490 }
491
492 if ( $value['type'] === 'db-requirement' ) {
493 $requirements = $this->setupFile->get( SetupFile::DB_REQUIREMENTS );
494 $args['version-title'] = $text;
495 $args['db-title'] = $this->createCopy( 'smw-setupcheck-db-title' );
496 $args['db-type'] = $requirements['type'] ?? 'N/A';
497 $args['db-current-title'] = $this->createCopy( 'smw-setupcheck-db-current-title' );
498 $args['db-minimum-title'] = $this->createCopy( 'smw-setupcheck-db-minimum-title' );
499 $args['db-current-version'] = $requirements['latest_version'] ?? 'N/A';
500 $args['db-minimum-version'] = $requirements['minimum_version'] ?? 'N/A';
501 }
502
503 // The type is expected to match a defined target and in an event
504 // that those don't match an exception will be raised.
505 $this->templateEngine->compile(
506 $value['type'],
507 $args
508 );
509
510 return $this->templateEngine->publish( $value['type'] );
511 }
512
513 private function createProgressIndicator( $value ) {
514
515 $maintenanceMode = (array)$this->setupFile->getMaintenanceMode();
516 $content = '';
517
518 foreach ( $maintenanceMode as $key => $v ) {
519
520 $args = [
521 'label' => $key,
522 'value' => $v
523 ];
524
525 if ( isset( $value['progress_keys'][$key] ) ) {
526 $args['label'] = $this->createCopy( $value['progress_keys'][$key] );
527 }
528
529 $this->templateEngine->compile(
530 'setupcheck-progress',
531 $args
532 );
533
534 $content .= $this->templateEngine->publish( 'setupcheck-progress' );
535 }
536
537 return $content;
538 }
539
540 private function createCopy( $value, $default = 'n/a' ) {
541
542 if ( is_string( $value ) && $this->localMessageProvider->has( $value ) ) {
543 return $this->localMessageProvider->msg( $value );
544 }
545
546 return $default;
547 }
548
549 private function buildHTML( array $error ) {
550
551 $args = [
552 'logo' => Logo::get( 'small' ),
553 'title' => $error['title'] ?? '',
554 'indicator' => $error['indicator_title'] ?? '',
555 'content' => $error['content'] ?? '',
556 'borderColor' => $error['borderColor'] ?? '#fff',
557 'refresh' => $error['refresh'] ?? '30',
558 ];
559
560 $this->templateEngine->compile(
561 'setupcheck-html',
562 $args
563 );
564
565 $html = $this->templateEngine->publish( 'setupcheck-html' );
566
567 // Minify CSS rules, we keep them readable in the template to allow for
568 // better adaption
569 // @see http://manas.tungare.name/software/css-compression-in-php/
570 $html = preg_replace_callback( "/<style\\b[^>]*>(.*?)<\\/style>/s", function( $matches ) {
571 // Remove space after colons
572 $style = str_replace( ': ', ':', $matches[0] );
573
574 // Remove whitespace
575 return str_replace( [ "\r\n", "\r", "\n", "\t", ' ', ' ', ' '], '', $style );
576 },
577 $html
578 );
579
580 return $html;
581 }
582
583}
SetupFile.php
1<?php
2
3namespace SMW;
4
5use SMW\Exception\FileNotWritableException;
6use SMW\Utils\File;
7use SMW\SQLStore\Installer;
8
9/**
10 * @private
11 *
12 * @license GNU GPL v2+
13 * @since 3.1
14 *
15 * @author mwjames
16 */
17class SetupFile {
18
19 /**
20 * Describes the maintenance mode
21 */
22 const MAINTENANCE_MODE = 'maintenance_mode';
23
24 /**
25 * Describes the upgrade key
26 */
27 const UPGRADE_KEY = 'upgrade_key';
28
29 /**
30 * Describes the database requirements
31 */
32 const DB_REQUIREMENTS = 'db_requirements';
33
34 /**
35 * Describes the entity collection setting
36 */
37 const ENTITY_COLLATION = 'entity_collation';
38
39 /**
40 * Key that describes the date of the last table optimization run.
41 */
42 const LAST_OPTIMIZATION_RUN = 'last_optimization_run';
43
44 /**
45 * Describes the file name
46 */
47 const FILE_NAME = '.smw.json';
48
49 /**
50 * Describes incomplete tasks
51 */
52 const INCOMPLETE_TASKS = 'incomplete_tasks';
53
54 /**
55 * Versions
56 */
57 const LATEST_VERSION = 'latest_version';
58 const PREVIOUS_VERSION = 'previous_version';
59
60 /**
61 * @var File
62 */
63 private $file;
64
65 /**
66 * @since 3.1
67 *
68 * @param File|null $file
69 */
70 public function __construct( File $file = null ) {
71 $this->file = $file;
72
73 if ( $this->file === null ) {
74 $this->file = new File();
75 }
76 }
77
78 /**
79 * @since 3.1
80 *
81 * @param array $vars
82 */
83 public function loadSchema( &$vars = [] ) {
84
85 if ( $vars === [] ) {
86 $vars = $GLOBALS;
87 }
88
89 if ( isset( $vars['smw.json'] ) ) {
90 return;
91 }
92
93 // @see #3506
94 $file = File::dir( $vars['smwgConfigFileDir'] . '/' . self::FILE_NAME );
95
96 // Doesn't exist? The `Setup::init` will take care of it by trying to create
97 // a new file and if it fails or unable to do so wail raise an exception
98 // as we expect to have access to it.
99 if ( is_readable( $file ) ) {
100 $vars['smw.json'] = json_decode( file_get_contents( $file ), true );
101 }
102 }
103
104 /**
105 * @since 3.1
106 *
107 * @param boolean $isCli
108 *
109 * @return boolean
110 */
111 public static function isGoodSchema( $isCli = false ) {
112 // get the schema State as a human readable description
113 $schemaState=SetupFile::getSchemaState($isCli);
114 // check that it starts with "ok:" and not "error:"
115 $result=SetupFile::strStartsWith($schemaState,"ok:");
116 return $result;
117 }
118
119 /**
120 * @since 3.1.7
121 *
122 * see https://stackoverflow.com/a/6513929/1497139
123 *
124 * @param string $haystack
125 * @param string $needle
126 *
127 * return boolean
128 */
129 public static function strStartsWith($haystack, $needle) {
130 return (strpos($haystack, $needle) === 0);
131 }
132
133 /**
134 * @since 3.1.7
135 *
136 * @param boolean $isCli
137 *
138 * @return string
139 */
140 public static function getSchemaState( $isCli = false ) {
141
142 if ( $isCli && defined( 'MW_PHPUNIT_TEST' ) ) {
143 return "ok: CLI with PHP Unit Test active";
144 }
145
146 if ( $isCli === false && ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ) ) {
147 return "ok: isCli is true and PHP_SAPI cli/phpdbg=".PHP_SAPI;
148 }
149
150 // #3563, Use the specific wiki-id as identifier for the instance in use
151 $id = Site::id();
152
153 if ( !isset( $GLOBALS['smw.json'][$id]['upgrade_key'] ) ) {
154 global $smwgConfigFileDir;
155 return "error: smw.json for ".$id." upgrade key missing - you might want to check \$smwgConfigFileDir:".$smwgConfigFileDir;
156 }
157
158 $upgradeKey = self::makeUpgradeKey( $GLOBALS );
159 $expected =$GLOBALS['smw.json'][$id]['upgrade_key'];
160 if ( $upgradeKey === $expected )
161 $schemaState= "ok: found upgradeKey.".$upgradeKey;
162 else
163 $schemaState= "error: expected upgradeKey ".$expected." for ".$id." but found ".$upgradeKey;
164
165 if (
166 isset( $GLOBALS['smw.json'][$id][self::MAINTENANCE_MODE] ) &&
167 $GLOBALS['smw.json'][$id][self::MAINTENANCE_MODE] !== false ) {
168 $schemaState= "error: upgradeKey ".$upgradeKey." is ok but maintainance is active";
169 }
170
171 return $schemaState;
172 }
173
174 /**
175 * @since 3.1
176 *
177 * @param array $vars
178 *
179 * @return string
180 */
181 public static function makeUpgradeKey( $vars ) {
182 return sha1( self::makeKey( $vars ) );
183 }
184
185 /**
186 * @since 3.1
187 *
188 * @param array $vars
189 *
190 * @return boolean
191 */
192 public function inMaintenanceMode( $vars = [] ) {
193
194 if ( !defined( 'MW_PHPUNIT_TEST' ) && ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ) ) {
195 return false;
196 }
197
198 if ( $vars === [] ) {
199 $vars = $GLOBALS;
200 }
201
202 $id = Site::id();
203
204 if ( !isset( $vars['smw.json'][$id][self::MAINTENANCE_MODE] ) ) {
205 return false;
206 }
207
208 return $vars['smw.json'][$id][self::MAINTENANCE_MODE] !== false;
209 }
210
211 /**
212 * @since 3.1
213 *
214 * @param array $vars
215 *
216 * @return []
217 */
218 public function getMaintenanceMode( $vars = [] ) {
219
220 if ( $vars === [] ) {
221 $vars = $GLOBALS;
222 }
223
224 $id = Site::id();
225
226 if ( !isset( $vars['smw.json'][$id][self::MAINTENANCE_MODE] ) ) {
227 return [];
228 }
229
230 return $vars['smw.json'][$id][self::MAINTENANCE_MODE];
231 }
232
233 /**
234 * Tracking the latest and previous version, which allows us to decide whether
235 * current activties relate to an install (new) or upgrade.
236 *
237 * @since 3.2
238 *
239 * @param int $version
240 */
241 public function setLatestVersion( $version ) {
242
243 $latest = $this->get( SetupFile::LATEST_VERSION );
244 $previous = $this->get( SetupFile::PREVIOUS_VERSION );
245
246 if ( $latest === null && $previous === null ) {
247 $this->set(
248 [
249 SetupFile::LATEST_VERSION => $version
250 ]
251 );
252 } elseif ( $latest !== $version ) {
253 $this->set(
254 [
255 SetupFile::LATEST_VERSION => $version,
256 SetupFile::PREVIOUS_VERSION => $latest
257 ]
258 );
259 }
260 }
261
262 /**
263 * @since 3.2
264 *
265 * @param string $key
266 * @param array $args
267 */
268 public function addIncompleteTask( string $key, array $args = [] ) {
269
270 $incomplete_tasks = $this->get( self::INCOMPLETE_TASKS );
271
272 if ( $incomplete_tasks === null ) {
273 $incomplete_tasks = [];
274 }
275
276 $incomplete_tasks[$key] = $args === [] ? true : $args;
277
278 $this->set( [ self::INCOMPLETE_TASKS => $incomplete_tasks ] );
279 }
280
281 /**
282 * @since 3.2
283 *
284 * @param string $key
285 */
286 public function removeIncompleteTask( string $key ) {
287
288 $incomplete_tasks = $this->get( self::INCOMPLETE_TASKS );
289
290 if ( $incomplete_tasks === null ) {
291 $incomplete_tasks = [];
292 }
293
294 unset( $incomplete_tasks[$key] );
295
296 $this->set( [ self::INCOMPLETE_TASKS => $incomplete_tasks ] );
297 }
298
299 /**
300 * @since 3.2
301 *
302 * @param array $vars
303 *
304 * @return boolean
305 */
306 public function hasDatabaseMinRequirement( array $vars = [] ) : bool {
307
308 if ( $vars === [] ) {
309 $vars = $GLOBALS;
310 }
311
312 $id = Site::id();
313
314 // No record means, no issues!
315 if ( !isset( $vars['smw.json'][$id][self::DB_REQUIREMENTS] ) ) {
316 return true;
317 }
318
319 $requirements = $vars['smw.json'][$id][self::DB_REQUIREMENTS];
320
321 return version_compare( $requirements['latest_version'], $requirements['minimum_version'], 'ge' );
322 }
323
324 /**
325 * @since 3.1
326 *
327 * @param array $vars
328 *
329 * @return []
330 */
331 public function findIncompleteTasks( $vars = [] ) {
332
333 if ( $vars === [] ) {
334 $vars = $GLOBALS;
335 }
336
337 $id = Site::id();
338 $tasks = [];
339
340 // Key field => [ value that constitutes the `INCOMPLETE` state, error msg ]
341 $checks = [
342 \SMW\SQLStore\Installer::POPULATE_HASH_FIELD_COMPLETE => [ false, 'smw-install-incomplete-populate-hash-field' ],
343 \SMW\Elastic\ElasticStore::REBUILD_INDEX_RUN_COMPLETE => [ false, 'smw-install-incomplete-elasticstore-indexrebuild' ]
344 ];
345
346 foreach ( $checks as $key => $value ) {
347
348 if ( !isset( $vars['smw.json'][$id][$key] ) ) {
349 continue;
350 }
351
352 if ( $vars['smw.json'][$id][$key] === $value[0] ) {
353 $tasks[] = $value[1];
354 }
355 }
356
357 if ( isset( $vars['smw.json'][$id][self::INCOMPLETE_TASKS] ) ) {
358 foreach ( $vars['smw.json'][$id][self::INCOMPLETE_TASKS] as $key => $args ) {
359 if ( $args === true ) {
360 $tasks[] = $key;
361 } else {
362 $tasks[] = [ $key, $args ];
363 }
364 }
365 }
366
367 return $tasks;
368 }
369
370 /**
371 * @since 3.1
372 *
373 * @param mixed $maintenanceMode
374 */
375 public function setMaintenanceMode( $maintenanceMode, $vars = [] ) {
376
377 if ( $vars === [] ) {
378 $vars = $GLOBALS;
379 }
380
381 $this->write(
382 [
383 self::UPGRADE_KEY => self::makeUpgradeKey( $vars ),
384 self::MAINTENANCE_MODE => $maintenanceMode
385 ],
386 $vars
387 );
388 }
389
390 /**
391 * @since 3.1
392 *
393 * @param array $vars
394 */
395 public function finalize( $vars = [] ) {
396
397 if ( $vars === [] ) {
398 $vars = $GLOBALS;
399 }
400
401 // #3563, Use the specific wiki-id as identifier for the instance in use
402 $key = self::makeUpgradeKey( $vars );
403 $id = Site::id();
404
405 if (
406 isset( $vars['smw.json'][$id][self::UPGRADE_KEY] ) &&
407 $key === $vars['smw.json'][$id][self::UPGRADE_KEY] &&
408 $vars['smw.json'][$id][self::MAINTENANCE_MODE] === false ) {
409 return false;
410 }
411
412 $this->write(
413 [
414 self::UPGRADE_KEY => $key,
415 self::MAINTENANCE_MODE => false
416 ],
417 $vars
418 );
419 }
420
421 /**
422 * @since 3.1
423 *
424 * @param array $vars
425 */
426 public function reset( $vars = [] ) {
427
428 if ( $vars === [] ) {
429 $vars = $GLOBALS;
430 }
431
432 $id = Site::id();
433 $args = [];
434
435 if ( !isset( $vars['smw.json'][$id] ) ) {
436 return;
437 }
438
439 $vars['smw.json'][$id] = [];
440
441 $this->write( [], $vars );
442 }
443
444 /**
445 * @since 3.1
446 *
447 * @param array $args
448 */
449 public function set( array $args, $vars = [] ) {
450
451 if ( $vars === [] ) {
452 $vars = $GLOBALS;
453 }
454
455 $this->write( $args, $vars );
456 }
457
458 /**
459 * @since 3.1
460 *
461 * @param array $args
462 */
463 public function get( $key, $vars = [] ) {
464
465 if ( $vars === [] ) {
466 $vars = $GLOBALS;
467 }
468
469 $id = Site::id();
470
471 if ( isset( $vars['smw.json'][$id][$key] ) ) {
472 return $vars['smw.json'][$id][$key];
473 }
474
475 return null;
476 }
477
478 /**
479 * @since 3.1
480 *
481 * @param string $key
482 */
483 public function remove( $key, $vars = [] ) {
484
485 if ( $vars === [] ) {
486 $vars = $GLOBALS;
487 }
488
489 $this->write( [ $key => null ], $vars );
490 }
491
492 /**
493 * @since 3.1
494 *
495 * @param array $vars
496 * @param array $args
497 */
498 public function write( array $args, array $vars ) {
499
500 $configFile = File::dir( $vars['smwgConfigFileDir'] . '/' . self::FILE_NAME );
501 $id = Site::id();
502
503 if ( !isset( $vars['smw.json'] ) ) {
504 $vars['smw.json'] = [];
505 }
506
507 foreach ( $args as $key => $value ) {
508 // NULL means that the key key is removed
509 if ( $value === null ) {
510 unset( $vars['smw.json'][$id][$key] );
511 } else {
512 $vars['smw.json'][$id][$key] = $value;
513 }
514 }
515
516 // Log the base elements used for computing the key
517 $vars['smw.json'][$id]['upgrade_key_base'] = self::makeKey(
518 $vars
519 );
520
521 // Remove legacy
522 if ( isset( $vars['smw.json']['upgradeKey'] ) ) {
523 unset( $vars['smw.json']['upgradeKey'] );
524 }
525 if ( isset( $vars['smw.json'][$id]['in.maintenance_mode'] ) ) {
526 unset( $vars['smw.json'][$id]['in.maintenance_mode'] );
527 }
528
529 try {
530 $this->file->write(
531 $configFile,
532 json_encode( $vars['smw.json'], JSON_PRETTY_PRINT )
533 );
534 } catch( FileNotWritableException $e ) {
535 // Users may not have `wgShowExceptionDetails` enabled and would
536 // therefore not see the exception error message hence we fail hard
537 // and die
538 die(
539 "\n\nERROR: " . $e->getMessage() . "\n" .
540 "\n The \"smwgConfigFileDir\" setting should point to a" .
541 "\n directory that is persistent and writable!\n"
542 );
543 }
544 }
545
546 /**
547 * Listed keys will have a "global" impact of how data are stored, formatted,
548 * or represented in Semantic MediaWiki. In most cases it will require an action
549 * from an adminstrator when one of those keys are altered.
550 */
551 public static function makeKey( $vars ) {
552
553 // Only recognize those properties that require a fixed table
554 $pageSpecialProperties = array_intersect(
555 // Special properties enabled?
556 $vars['smwgPageSpecialProperties'],
557
558 // Any custom fixed properties require their own table?
559 TypesRegistry::getFixedProperties( 'custom_fixed' )
560 );
561
562 $pageSpecialProperties = array_unique( $pageSpecialProperties );
563
564 // Sort to ensure the key contains the same order
565 sort( $vars['smwgFixedProperties'] );
566 sort( $pageSpecialProperties );
567
568 // The following settings influence the "shape" of the tables required
569 // therefore use the content to compute a key that reflects any
570 // changes to them
571 $components = [
572 $vars['smwgUpgradeKey'],
573 $vars['smwgDefaultStore'],
574 $vars['smwgFixedProperties'],
575 $vars['smwgEnabledFulltextSearch'],
576 $pageSpecialProperties
577 ];
578
579 // Only add the key when it is different from the default setting
580 if ( $vars['smwgEntityCollation'] !== 'identity' ) {
581 $components += [ 'smwgEntityCollation' => $vars['smwgEntityCollation'] ];
582 }
583
584 if ( $vars['smwgFieldTypeFeatures'] !== false ) {
585 $components += [ 'smwgFieldTypeFeatures' => $vars['smwgFieldTypeFeatures'] ];
586 }
587
588 // Recognize when the version requirements change and force
589 // an update to be able to check the requirements
590 $components += Setup::MINIMUM_DB_VERSION;
591
592 return json_encode( $components );
593 }
594
595}