2013-02-22 18:50:54 +00:00
|
|
|
<?php
|
2021-11-25 21:35:46 +00:00
|
|
|
|
|
|
|
namespace MediaWiki\Extension\TemplateData;
|
|
|
|
|
2024-04-08 11:27:18 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2019-06-12 04:38:27 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2024-01-04 20:53:10 +00:00
|
|
|
use MediaWiki\Status\Status;
|
2021-11-25 21:35:46 +00:00
|
|
|
use stdClass;
|
2023-03-06 11:37:34 +00:00
|
|
|
use Wikimedia\Rdbms\IReadableDatabase;
|
2013-02-22 18:50:54 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents the information about a template,
|
|
|
|
* coming from the JSON blob in the <templatedata> tags
|
|
|
|
* on wiki pages.
|
2023-08-23 07:29:25 +00:00
|
|
|
* @license GPL-2.0-or-later
|
2013-02-22 18:50:54 +00:00
|
|
|
*/
|
|
|
|
class TemplateDataBlob {
|
2020-09-14 12:17:25 +00:00
|
|
|
|
2023-08-23 07:29:25 +00:00
|
|
|
protected string $json;
|
|
|
|
protected Status $status;
|
2013-02-22 18:50:54 +00:00
|
|
|
|
|
|
|
/**
|
2019-07-08 01:46:33 +00:00
|
|
|
* Parse and validate passed JSON and create a blob handling
|
|
|
|
* instance.
|
2013-07-23 13:33:45 +00:00
|
|
|
* Accepts and handles user-provided data.
|
|
|
|
*
|
2023-03-06 11:37:34 +00:00
|
|
|
* @param IReadableDatabase $db
|
2013-07-23 13:33:45 +00:00
|
|
|
* @param string $json
|
2020-09-03 10:06:10 +00:00
|
|
|
* @return TemplateDataBlob
|
2013-02-22 18:50:54 +00:00
|
|
|
*/
|
2023-03-06 11:37:34 +00:00
|
|
|
public static function newFromJSON( IReadableDatabase $db, string $json ): TemplateDataBlob {
|
2024-04-08 11:27:18 +00:00
|
|
|
$lang = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LanguageCode );
|
2019-07-08 01:46:33 +00:00
|
|
|
if ( $db->getType() === 'mysql' ) {
|
2024-04-08 11:27:18 +00:00
|
|
|
$tdb = new TemplateDataCompressedBlob( $json, $lang );
|
2019-07-08 01:46:33 +00:00
|
|
|
} else {
|
2024-04-08 11:27:18 +00:00
|
|
|
$tdb = new TemplateDataBlob( $json, $lang );
|
2019-07-08 01:46:33 +00:00
|
|
|
}
|
2013-04-22 19:56:28 +00:00
|
|
|
return $tdb;
|
2013-02-22 18:50:54 +00:00
|
|
|
}
|
|
|
|
|
2013-07-23 13:33:45 +00:00
|
|
|
/**
|
2019-07-08 01:46:33 +00:00
|
|
|
* Parse and validate passed JSON (possibly gzip-compressed) and create a blob handling
|
|
|
|
* instance.
|
2013-07-23 13:33:45 +00:00
|
|
|
*
|
2023-03-06 11:37:34 +00:00
|
|
|
* @param IReadableDatabase $db
|
2013-07-23 13:33:45 +00:00
|
|
|
* @param string $json
|
2020-09-03 10:06:10 +00:00
|
|
|
* @return TemplateDataBlob
|
2013-07-23 13:33:45 +00:00
|
|
|
*/
|
2023-03-06 11:37:34 +00:00
|
|
|
public static function newFromDatabase( IReadableDatabase $db, string $json ): TemplateDataBlob {
|
2013-07-23 13:33:45 +00:00
|
|
|
// Handle GZIP compression. \037\213 is the header for GZIP files.
|
|
|
|
if ( substr( $json, 0, 2 ) === "\037\213" ) {
|
|
|
|
$json = gzdecode( $json );
|
|
|
|
}
|
2019-07-08 01:46:33 +00:00
|
|
|
return self::newFromJSON( $db, $json );
|
2013-07-23 13:33:45 +00:00
|
|
|
}
|
|
|
|
|
2024-04-08 11:27:18 +00:00
|
|
|
protected function __construct( string $json, string $lang ) {
|
2021-12-23 15:08:01 +00:00
|
|
|
$deprecatedTypes = array_keys( TemplateDataNormalizer::DEPRECATED_PARAMETER_TYPES );
|
|
|
|
$validator = new TemplateDataValidator( $deprecatedTypes );
|
2020-08-26 11:25:10 +00:00
|
|
|
$this->status = $validator->validate( json_decode( $json ) );
|
2021-12-23 15:08:01 +00:00
|
|
|
|
2020-08-26 11:25:10 +00:00
|
|
|
// If data is invalid, replace with the minimal valid blob.
|
|
|
|
// This is to make sure that, if something forgets to check the status first,
|
|
|
|
// we don't end up with invalid data in the database.
|
|
|
|
$value = $this->status->getValue() ?? (object)[ 'params' => (object)[] ];
|
|
|
|
|
2024-04-08 11:27:18 +00:00
|
|
|
$normalizer = new TemplateDataNormalizer( $lang );
|
2020-08-26 11:25:10 +00:00
|
|
|
$normalizer->normalize( $value );
|
2021-12-23 15:08:01 +00:00
|
|
|
|
2020-08-26 11:25:10 +00:00
|
|
|
// Don't bother storing the decoded object, it will always be cloned anyway
|
|
|
|
$this->json = json_encode( $value );
|
2013-04-16 14:18:10 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 16:31:20 +00:00
|
|
|
/**
|
|
|
|
* Get a single localized string from an InterfaceText object.
|
|
|
|
*
|
|
|
|
* Uses the preferred language passed to this function, or one of its fallbacks,
|
|
|
|
* or the site content language, or its fallbacks.
|
|
|
|
*
|
|
|
|
* @param stdClass $text An InterfaceText object
|
|
|
|
* @param string $langCode Preferred language
|
|
|
|
* @return null|string Text value from the InterfaceText object or null if no suitable
|
|
|
|
* match was found
|
|
|
|
*/
|
2022-02-03 08:41:28 +00:00
|
|
|
private function getInterfaceTextInLanguage( stdClass $text, string $langCode ): ?string {
|
2013-10-06 16:31:20 +00:00
|
|
|
if ( isset( $text->$langCode ) ) {
|
|
|
|
return $text->$langCode;
|
|
|
|
}
|
|
|
|
|
2024-03-12 19:49:04 +00:00
|
|
|
[ $userlangs, $sitelangs ] = MediaWikiServices::getInstance()->getLanguageFallback()
|
2020-12-16 11:07:34 +00:00
|
|
|
->getAllIncludingSiteLanguage( $langCode );
|
2013-10-06 16:31:20 +00:00
|
|
|
|
|
|
|
foreach ( $userlangs as $lang ) {
|
|
|
|
if ( isset( $text->$lang ) ) {
|
|
|
|
return $text->$lang;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $sitelangs as $lang ) {
|
|
|
|
if ( isset( $text->$lang ) ) {
|
|
|
|
return $text->$lang;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If none of the languages are found fallback to null. Alternatively we could fallback to
|
|
|
|
// reset( $text ) which will return whatever key there is, but we should't give the user a
|
|
|
|
// "random" language with no context (e.g. could be RTL/Hebrew for an LTR/Japanese user).
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-07-23 22:51:45 +00:00
|
|
|
public function getStatus(): Status {
|
2013-02-22 18:50:54 +00:00
|
|
|
return $this->status;
|
|
|
|
}
|
|
|
|
|
2013-10-06 16:31:20 +00:00
|
|
|
/**
|
2020-08-26 11:25:10 +00:00
|
|
|
* @return stdClass
|
2013-10-06 16:31:20 +00:00
|
|
|
*/
|
2013-04-22 19:56:28 +00:00
|
|
|
public function getData() {
|
2016-01-27 04:36:45 +00:00
|
|
|
// Return deep clone so callers can't modify data. Needed for getDataInLanguage().
|
2020-08-26 11:25:10 +00:00
|
|
|
return json_decode( $this->json );
|
2013-04-22 19:56:28 +00:00
|
|
|
}
|
|
|
|
|
2013-10-06 16:31:20 +00:00
|
|
|
/**
|
|
|
|
* Get data with all InterfaceText objects resolved to a single string to the
|
|
|
|
* appropriate language.
|
|
|
|
*
|
|
|
|
* @param string $langCode Preferred language
|
2020-09-03 10:06:10 +00:00
|
|
|
* @return stdClass
|
2013-10-06 16:31:20 +00:00
|
|
|
*/
|
2021-10-02 08:44:30 +00:00
|
|
|
public function getDataInLanguage( string $langCode ): stdClass {
|
2016-01-27 04:36:45 +00:00
|
|
|
$data = $this->getData();
|
2013-10-06 16:31:20 +00:00
|
|
|
|
|
|
|
// Root.description
|
|
|
|
if ( $data->description !== null ) {
|
2022-02-03 08:41:28 +00:00
|
|
|
$data->description = $this->getInterfaceTextInLanguage( $data->description, $langCode );
|
2013-10-06 16:31:20 +00:00
|
|
|
}
|
|
|
|
|
2022-02-01 14:21:34 +00:00
|
|
|
foreach ( $data->params as $param ) {
|
2013-10-06 16:31:20 +00:00
|
|
|
// Param.label
|
2022-02-01 14:21:34 +00:00
|
|
|
if ( $param->label !== null ) {
|
2022-02-03 08:41:28 +00:00
|
|
|
$param->label = $this->getInterfaceTextInLanguage( $param->label, $langCode );
|
2013-10-06 16:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Param.description
|
2022-02-01 14:21:34 +00:00
|
|
|
if ( $param->description !== null ) {
|
2022-02-03 08:41:28 +00:00
|
|
|
$param->description = $this->getInterfaceTextInLanguage( $param->description, $langCode );
|
2013-10-06 16:31:20 +00:00
|
|
|
}
|
2015-03-09 21:45:04 +00:00
|
|
|
|
|
|
|
// Param.default
|
2022-02-01 14:21:34 +00:00
|
|
|
if ( $param->default !== null ) {
|
2022-02-03 08:41:28 +00:00
|
|
|
$param->default = $this->getInterfaceTextInLanguage( $param->default, $langCode );
|
2015-03-09 21:45:04 +00:00
|
|
|
}
|
2015-04-28 15:48:11 +00:00
|
|
|
|
|
|
|
// Param.example
|
2022-02-01 14:21:34 +00:00
|
|
|
if ( $param->example !== null ) {
|
2022-02-03 08:41:28 +00:00
|
|
|
$param->example = $this->getInterfaceTextInLanguage( $param->example, $langCode );
|
2015-04-28 15:48:11 +00:00
|
|
|
}
|
2013-10-06 16:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $data->sets as $setObj ) {
|
2022-02-03 08:41:28 +00:00
|
|
|
$label = $this->getInterfaceTextInLanguage( $setObj->label, $langCode );
|
2013-10-06 16:31:20 +00:00
|
|
|
if ( $label === null ) {
|
|
|
|
// Contrary to other InterfaceTexts, set label is not optional. If we're here it
|
|
|
|
// means the template data from the wiki doesn't contain either the user language,
|
|
|
|
// site language or any of its fallbacks. Wikis should fix data that is in this
|
|
|
|
// condition (TODO: Disallow during saving?). For now, fallback to whatever we can
|
|
|
|
// get that does exist in the text object.
|
2014-07-18 00:23:13 +00:00
|
|
|
$arr = (array)$setObj->label;
|
|
|
|
$label = reset( $arr );
|
2013-10-06 16:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$setObj->label = $label;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2013-07-23 13:33:45 +00:00
|
|
|
/**
|
2019-07-08 01:46:33 +00:00
|
|
|
* @return string JSON
|
2013-07-23 13:33:45 +00:00
|
|
|
*/
|
2021-07-23 22:51:45 +00:00
|
|
|
public function getJSONForDatabase(): string {
|
2020-08-26 11:25:10 +00:00
|
|
|
return $this->json;
|
2013-02-22 18:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|