Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
87.07% |
202 / 232 |
|
73.68% |
14 / 19 |
CRAP | |
0.00% |
0 / 1 |
| ZObjectContentHandler | |
87.07% |
202 / 232 |
|
73.68% |
14 / 19 |
48.19 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| canBeUsedOn | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| makeEmptyContent | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
1 | |||
| makeContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getContentClass | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| serializeContent | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
| unserializeContent | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| getExternalRepresentation | |
83.33% |
30 / 36 |
|
0.00% |
0 / 1 |
6.17 | |||
| getSecondaryDataUpdates | |
93.75% |
15 / 16 |
|
0.00% |
0 / 1 |
3.00 | |||
| getDeletionUpdates | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| supportsDirectEditing | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getActionOverrides | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| generateHTMLOnEdit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| fillParserOutput | |
78.35% |
76 / 97 |
|
0.00% |
0 / 1 |
11.01 | |||
| getZObjectViewPageHTMLTitle | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
| validateSave | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| preSaveTransform | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
| createDifferenceEngine | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| getSlotDiffRendererWithOptions | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * WikiLambda content handler for ZObjects |
| 4 | * |
| 5 | * @file |
| 6 | * @ingroup Extensions |
| 7 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
| 8 | * @license MIT |
| 9 | */ |
| 10 | |
| 11 | namespace MediaWiki\Extension\WikiLambda\ZObjectContent; |
| 12 | |
| 13 | use InvalidArgumentException; |
| 14 | use MediaWiki\Config\Config; |
| 15 | use MediaWiki\Content\Content; |
| 16 | use MediaWiki\Content\ContentHandler; |
| 17 | use MediaWiki\Content\ContentSerializationException; |
| 18 | use MediaWiki\Content\Renderer\ContentParseParams; |
| 19 | use MediaWiki\Content\Transform\PreSaveTransformParams; |
| 20 | use MediaWiki\Content\ValidationParams; |
| 21 | use MediaWiki\Context\IContextSource; |
| 22 | use MediaWiki\Context\RequestContext; |
| 23 | use MediaWiki\Diff\TextSlotDiffRenderer; |
| 24 | use MediaWiki\Extension\WikiLambda\Cache\MemcachedWrapper; |
| 25 | use MediaWiki\Extension\WikiLambda\PageTitle\PageTitleBuilder; |
| 26 | use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry; |
| 27 | use MediaWiki\Extension\WikiLambda\Registry\ZLangRegistry; |
| 28 | use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry; |
| 29 | use MediaWiki\Extension\WikiLambda\UIUtils; |
| 30 | use MediaWiki\Extension\WikiLambda\WikiLambdaServices; |
| 31 | use MediaWiki\Extension\WikiLambda\ZErrorException; |
| 32 | use MediaWiki\Extension\WikiLambda\ZErrorFactory; |
| 33 | use MediaWiki\Extension\WikiLambda\ZObjectStore; |
| 34 | use MediaWiki\Extension\WikiLambda\ZObjectUtils; |
| 35 | use MediaWiki\Html\Html; |
| 36 | use MediaWiki\Json\FormatJson; |
| 37 | use MediaWiki\Language\Language; |
| 38 | use MediaWiki\Logger\LoggerFactory; |
| 39 | use MediaWiki\MainConfigNames; |
| 40 | use MediaWiki\MediaWikiServices; |
| 41 | use MediaWiki\Parser\ParserOutput; |
| 42 | use MediaWiki\Revision\SlotRenderingProvider; |
| 43 | use MediaWiki\Title\Title; |
| 44 | use StatusValue; |
| 45 | |
| 46 | class ZObjectContentHandler extends ContentHandler { |
| 47 | use ZObjectEditingPageTrait; |
| 48 | |
| 49 | /** |
| 50 | * @param string $modelId |
| 51 | * @param Config $config |
| 52 | * @param ZObjectStore $zObjectStore |
| 53 | * @param MemcachedWrapper $zObjectCache |
| 54 | */ |
| 55 | public function __construct( |
| 56 | $modelId, |
| 57 | private readonly Config $config, |
| 58 | private readonly ZObjectStore $zObjectStore, |
| 59 | private readonly MemcachedWrapper $zObjectCache |
| 60 | ) { |
| 61 | if ( $modelId !== CONTENT_MODEL_ZOBJECT ) { |
| 62 | throw new InvalidArgumentException( __CLASS__ . " initialised for invalid content model" ); |
| 63 | } |
| 64 | |
| 65 | // Triggers use of message content-model-zobject |
| 66 | parent::__construct( CONTENT_MODEL_ZOBJECT, [ CONTENT_FORMAT_TEXT ] ); |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * @param Title $title Page to check |
| 71 | * @return bool |
| 72 | */ |
| 73 | public function canBeUsedOn( Title $title ) { |
| 74 | return $title->inNamespace( NS_MAIN ); |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * @return ZObjectContent |
| 79 | */ |
| 80 | public function makeEmptyContent() { |
| 81 | $class = $this->getContentClass(); |
| 82 | return new $class( |
| 83 | '{' . "\n" |
| 84 | . '"' . ZTypeRegistry::Z_OBJECT_TYPE . '": "' . ZTypeRegistry::Z_PERSISTENTOBJECT . '",' . "\n" |
| 85 | . '"' . ZTypeRegistry::Z_PERSISTENTOBJECT_ID . '": {' . "\n" |
| 86 | . '"' . ZTypeRegistry::Z_OBJECT_TYPE . '": "' . ZTypeRegistry::Z_STRING . '",' . "\n" |
| 87 | . '"' . ZTypeRegistry::Z_STRING_VALUE . '": "' . ZTypeRegistry::Z_NULL_REFERENCE . '"},' . "\n" |
| 88 | . '"' . ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE . '": "",' . "\n" |
| 89 | . '"' . ZTypeRegistry::Z_PERSISTENTOBJECT_LABEL . '": {' . "\n" |
| 90 | . '"' . ZTypeRegistry::Z_OBJECT_TYPE . '": "' . ZTypeRegistry::Z_MULTILINGUALSTRING . '",' . "\n" |
| 91 | . '"' . ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE . '":' |
| 92 | . '["' . ZTypeRegistry::Z_MONOLINGUALSTRING . '"]' . "\n" |
| 93 | . '},' . "\n" |
| 94 | . '"' . ZTypeRegistry::Z_PERSISTENTOBJECT_ALIASES . '": {' . "\n" |
| 95 | . '"' . ZTypeRegistry::Z_OBJECT_TYPE . '": "' . ZTypeRegistry::Z_MULTILINGUALSTRINGSET . '",' . "\n" |
| 96 | . '"' . ZTypeRegistry::Z_MULTILINGUALSTRINGSET_VALUE . '":' |
| 97 | . '["' . ZTypeRegistry::Z_MONOLINGUALSTRINGSET . '"]' . "\n" |
| 98 | . '},' . "\n" |
| 99 | . '"' . ZTypeRegistry::Z_PERSISTENTOBJECT_DESCRIPTION . '": {' . "\n" |
| 100 | . '"' . ZTypeRegistry::Z_OBJECT_TYPE . '": "' . ZTypeRegistry::Z_MULTILINGUALSTRING . '",' . "\n" |
| 101 | . '"' . ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE . '":' |
| 102 | . '["' . ZTypeRegistry::Z_MONOLINGUALSTRING . '"]' . "\n" |
| 103 | . '}' . "\n" |
| 104 | . '}' |
| 105 | ); |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * @param string $data |
| 110 | * @param Title|null $title |
| 111 | * @param string|null $modelId |
| 112 | * @param string|null $format |
| 113 | * @return ZObjectContent |
| 114 | */ |
| 115 | public static function makeContent( $data, ?Title $title = null, $modelId = null, $format = null ) { |
| 116 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType |
| 117 | return parent::makeContent( $data, $title, $modelId, $format ); |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * @return string |
| 122 | */ |
| 123 | protected function getContentClass() { |
| 124 | return ZObjectContent::class; |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * @param Content $content |
| 129 | * @param string|null $format |
| 130 | * @return string |
| 131 | */ |
| 132 | public function serializeContent( Content $content, $format = null ) { |
| 133 | $this->checkFormat( $format ); |
| 134 | |
| 135 | if ( !( $content instanceof ZObjectContent ) ) { |
| 136 | // Throw? |
| 137 | return ''; |
| 138 | } |
| 139 | |
| 140 | return $content->getText(); |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * @param string $text |
| 145 | * @param string|null $format |
| 146 | * @return ZObjectContent |
| 147 | * @throws ContentSerializationException if input causes an error |
| 148 | */ |
| 149 | public function unserializeContent( $text, $format = null ) { |
| 150 | $this->checkFormat( $format ); |
| 151 | |
| 152 | $class = $this->getContentClass(); |
| 153 | try { |
| 154 | return new $class( $text ); |
| 155 | } catch ( ZErrorException $zerror ) { |
| 156 | // (T381115) If the passed user input isn't valid, we're expected to throw this particular MW error |
| 157 | throw new ContentSerializationException( $zerror->getZError()->getMessage() ); |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * @param Title $zObjectTitle The page to fetch. |
| 163 | * @param string|null $languageCode The language in which to return results. If unset, all results are returned. |
| 164 | * @param int|null $revision The revision ID of the page to fetch. If unset, the latest is returned. |
| 165 | * @return string The external JSON form of the given title. |
| 166 | * @throws ZErrorException |
| 167 | */ |
| 168 | public static function getExternalRepresentation( |
| 169 | Title $zObjectTitle, ?string $languageCode = null, ?int $revision = null |
| 170 | ): string { |
| 171 | if ( $zObjectTitle->getNamespace() !== NS_MAIN ) { |
| 172 | throw new ZErrorException( |
| 173 | ZErrorFactory::createZErrorInstance( |
| 174 | ZErrorTypeRegistry::Z_ERROR_WRONG_NAMESPACE, |
| 175 | [ 'title' => (string)$zObjectTitle ] |
| 176 | ) |
| 177 | ); |
| 178 | } |
| 179 | |
| 180 | if ( $zObjectTitle->getContentModel() !== CONTENT_MODEL_ZOBJECT ) { |
| 181 | throw new ZErrorException( |
| 182 | ZErrorFactory::createZErrorInstance( |
| 183 | ZErrorTypeRegistry::Z_ERROR_WRONG_CONTENT_TYPE, |
| 184 | [ 'title' => (string)$zObjectTitle ] |
| 185 | ) |
| 186 | ); |
| 187 | } |
| 188 | |
| 189 | $zObjectStore = WikiLambdaServices::getZObjectStore(); |
| 190 | $zObject = $zObjectStore->fetchZObjectByTitle( $zObjectTitle, $revision ); |
| 191 | |
| 192 | if ( $zObject === false ) { |
| 193 | throw new ZErrorException( |
| 194 | ZErrorFactory::createZErrorInstance( |
| 195 | ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND, |
| 196 | [ 'data' => (string)$zObjectTitle ] |
| 197 | ) |
| 198 | ); |
| 199 | } |
| 200 | |
| 201 | // Cautionary canonicalization: all objects should be fully canonicalized in the DB |
| 202 | $object = ZObjectUtils::canonicalize( $zObject->getObject() ); |
| 203 | |
| 204 | if ( $languageCode ) { |
| 205 | // TODO (T362246): Dependency-inject |
| 206 | $services = MediaWikiServices::getInstance(); |
| 207 | |
| 208 | // If language code is not valid, throws ZErrorException of Z540/Invalid language code |
| 209 | if ( !$services->getLanguageNameUtils()->isValidCode( $languageCode ) ) { |
| 210 | throw new ZErrorException( |
| 211 | ZErrorFactory::createZErrorInstance( |
| 212 | ZErrorTypeRegistry::Z_ERROR_INVALID_LANG_CODE, |
| 213 | [ 'lang' => $languageCode ] |
| 214 | ) |
| 215 | ); |
| 216 | } |
| 217 | |
| 218 | // If language doesn't have a Zid, throws ZErrorException of Z541/Language code not found |
| 219 | $languageZid = ZLangRegistry::singleton()->getLanguageZidFromCode( $languageCode ); |
| 220 | |
| 221 | // Filter all Multilingual Strings and Stringsets if language is present and valid |
| 222 | $object = ZObjectUtils::filterZMultilingualStringsToLanguage( $object, [ $languageZid ] ); |
| 223 | } |
| 224 | |
| 225 | return FormatJson::encode( $object, true, FormatJson::UTF8_OK ); |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * @inheritDoc |
| 230 | */ |
| 231 | public function getSecondaryDataUpdates( |
| 232 | Title $title, |
| 233 | Content $content, |
| 234 | $role, |
| 235 | SlotRenderingProvider $slotOutput |
| 236 | ) { |
| 237 | $orchestrator = null; |
| 238 | |
| 239 | if ( $this->config->get( 'WikiLambdaPersistBackendCache' ) ) { |
| 240 | $orchestrator = WikiLambdaServices::getOrchestratorRequest(); |
| 241 | } |
| 242 | |
| 243 | $ourUpdate = []; |
| 244 | if ( ( $content instanceof ZObjectContent ) ) { |
| 245 | $ourUpdate[] = new ZObjectSecondaryDataUpdate( |
| 246 | $title, |
| 247 | $content, |
| 248 | $this->zObjectStore, |
| 249 | $this->zObjectCache, |
| 250 | $orchestrator |
| 251 | ); |
| 252 | } |
| 253 | |
| 254 | return array_merge( |
| 255 | parent::getSecondaryDataUpdates( $title, $content, $role, $slotOutput ), |
| 256 | $ourUpdate |
| 257 | ); |
| 258 | } |
| 259 | |
| 260 | /** |
| 261 | * @inheritDoc |
| 262 | */ |
| 263 | public function getDeletionUpdates( Title $title, $role ) { |
| 264 | return array_merge( |
| 265 | parent::getDeletionUpdates( $title, $role ), |
| 266 | [ new ZObjectSecondaryDataRemoval( |
| 267 | $title, |
| 268 | $this->zObjectStore, |
| 269 | $this->zObjectCache |
| 270 | ) ] |
| 271 | ); |
| 272 | } |
| 273 | |
| 274 | /** |
| 275 | * @inheritDoc |
| 276 | */ |
| 277 | public function supportsDirectEditing() { |
| 278 | return false; |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * @inheritDoc |
| 283 | */ |
| 284 | public function getActionOverrides() { |
| 285 | return [ |
| 286 | 'edit' => ZObjectEditAction::class, |
| 287 | 'history' => ZObjectHistoryAction::class |
| 288 | ]; |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * Do not render HTML on edit (T285987) |
| 293 | * |
| 294 | * @return bool |
| 295 | */ |
| 296 | public function generateHTMLOnEdit(): bool { |
| 297 | return false; |
| 298 | } |
| 299 | |
| 300 | /** |
| 301 | * Set the HTML and add the appropriate styles. |
| 302 | * |
| 303 | * @inheritDoc |
| 304 | * @param Content $content |
| 305 | * @param ContentParseParams $cpoParams |
| 306 | * @param ParserOutput &$parserOutput The output object to fill (reference). |
| 307 | */ |
| 308 | protected function fillParserOutput( |
| 309 | Content $content, |
| 310 | ContentParseParams $cpoParams, |
| 311 | ParserOutput &$parserOutput |
| 312 | ) { |
| 313 | $userLang = RequestContext::getMain()->getLanguage(); |
| 314 | $logger = LoggerFactory::getInstance( 'WikiLambda' ); |
| 315 | |
| 316 | // Ensure the stored content is a valid ZObject; this also populates $this->getZObject() for us |
| 317 | if ( !( $content instanceof ZObjectContent ) || !$content->isValid() ) { |
| 318 | $parserOutput->setContentHolderText( |
| 319 | Html::element( |
| 320 | 'div', |
| 321 | [ |
| 322 | 'class' => [ 'ext-wikilambda-view-invalidcontent', 'warning' ], |
| 323 | ], |
| 324 | wfMessage( 'wikilambda-invalidzobject' )->inLanguage( $userLang )->text() |
| 325 | ) |
| 326 | ); |
| 327 | // Exit early, as the rest of the code relies on the stored content being well-formed and valid. |
| 328 | return; |
| 329 | } |
| 330 | |
| 331 | // Add the ZObject's labels in each language as a page property, for cheaper reading |
| 332 | $zLangRegistry = ZLangRegistry::singleton(); |
| 333 | $labels = $content->getLabels()->getValueAsList(); |
| 334 | foreach ( $labels as $langZid => $label ) { |
| 335 | if ( !$langZid ) { |
| 336 | // (T402670) Something's wrong with this label entry; skip it |
| 337 | $logger->debug( |
| 338 | 'Skipping setting page property for label in blank language ZID when displaying "{page}"', |
| 339 | [ |
| 340 | 'page' => $content->getZObject()->getZid() |
| 341 | ] |
| 342 | ); |
| 343 | continue; |
| 344 | } |
| 345 | |
| 346 | try { |
| 347 | $lang = $zLangRegistry->getLanguageCodeFromZid( $langZid ); |
| 348 | $parserOutput->setPageProperty( "wikilambda-label-$lang", $label ); |
| 349 | } catch ( ZErrorException ) { |
| 350 | // The language code is somehow not recognised; don't set a property, but log it for review |
| 351 | $logger->warning( |
| 352 | 'Skipping setting page property for label in unknown language "{langZid}" when displaying "{page}"', |
| 353 | [ |
| 354 | 'langZid' => $langZid, |
| 355 | 'page' => $content->getZObject()->getZid() |
| 356 | ] |
| 357 | ); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | // Add links to other ZObjects |
| 362 | // (T361701) Do this ahead of the early return, as LinkUpdater asks for the non-HTML version |
| 363 | foreach ( $content->getInnerZObject()->getLinkedZObjects() as $link ) { |
| 364 | if ( !$link ) { |
| 365 | // (T402670) Something's wrong with this link entry; skip it |
| 366 | $logger->debug( |
| 367 | 'Skipping setting page property for link to blank page when displaying "{page}"', |
| 368 | [ |
| 369 | 'page' => $content->getZObject()->getZid() |
| 370 | ] |
| 371 | ); |
| 372 | continue; |
| 373 | } |
| 374 | |
| 375 | $title = Title::newFromText( $link, NS_MAIN ); |
| 376 | |
| 377 | // (T400521) Don't try to write a null Title, but log that it happened |
| 378 | if ( !$title ) { |
| 379 | $logger->warning( |
| 380 | 'Skipping setting page property for link to unknown page "{link}" when displaying "{page}"', |
| 381 | [ |
| 382 | 'link' => $link, |
| 383 | 'page' => $content->getZObject()->getZid(), |
| 384 | ] |
| 385 | ); |
| 386 | } else { |
| 387 | $parserOutput->addLink( $title, $title->getArticleID() ); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | // Don't do further work if the requester doesn't want the HTML version generated. |
| 392 | if ( !$cpoParams->getGenerateHtml() ) { |
| 393 | $parserOutput->setContentHolderText( '' ); |
| 394 | return; |
| 395 | } |
| 396 | |
| 397 | $pageIdentity = $cpoParams->getPage(); |
| 398 | |
| 399 | // TODO (T362245): Re-work our code to use PageReferences rather than Titles |
| 400 | $title = Title::castFromPageReference( $pageIdentity ); |
| 401 | '@phan-var Title $title'; |
| 402 | |
| 403 | $parserOutput->addModuleStyles( [ 'ext.wikilambda.viewpage.styles' ] ); |
| 404 | $parserOutput->addModules( [ 'ext.wikilambda.app' ] ); |
| 405 | |
| 406 | $userLangCode = $userLang->getCode(); |
| 407 | |
| 408 | // If the userLang isn't recognised (e.g. it's qqx, or a language we don't support yet, or it's |
| 409 | // nonsense), then fall back to English. |
| 410 | $userLangZid = $zLangRegistry->getLanguageZidFromCode( $userLangCode, true ); |
| 411 | // Normalise our used language code from what the Language object says |
| 412 | $userLangCode = $zLangRegistry->getLanguageCodeFromZid( $userLangZid ); |
| 413 | |
| 414 | // Add the canonical page link to /view/<lang>/<zid> |
| 415 | $output = RequestContext::getMain()->getOutput(); |
| 416 | |
| 417 | // Set page header |
| 418 | $pageTitle = PageTitleBuilder::createZObjectViewPageTitle( $content, $title, $userLang ); |
| 419 | $output->setPageTitle( $pageTitle ); |
| 420 | |
| 421 | // (T360169) Set page title meta tag |
| 422 | $htmlTitle = static::getZObjectViewPageHTMLTitle( $content, $title, $userLang ); |
| 423 | $output->setHTMLTitle( $htmlTitle ); |
| 424 | |
| 425 | $output->addLink( [ |
| 426 | 'rel' => 'canonical', |
| 427 | 'hreflang' => $userLangCode, |
| 428 | 'href' => "/view/$userLangCode/" . $title->getDBkey(), |
| 429 | ] ); |
| 430 | |
| 431 | $editingData = [ |
| 432 | // The following paramether may be the same now, |
| 433 | // but will surely change in the future as we remove the Zds from the UI |
| 434 | 'title' => $title->getBaseText(), |
| 435 | 'zId' => $title->getBaseText(), |
| 436 | 'page' => $title->getPrefixedDBkey(), |
| 437 | 'zlang' => $userLangCode, |
| 438 | 'zlangZid' => $userLangZid, |
| 439 | 'createNewPage' => false, |
| 440 | 'viewmode' => true |
| 441 | ]; |
| 442 | |
| 443 | $parserOutput->setJsConfigVar( 'wgWikiLambda', $editingData ); |
| 444 | |
| 445 | $loadingMessage = wfMessage( 'wikilambda-loading' )->inLanguage( $userLang )->text(); |
| 446 | $parserOutput->setContentHolderText( |
| 447 | // Placeholder div for the Vue template with Codex progress indicator. |
| 448 | Html::rawElement( |
| 449 | 'div', |
| 450 | [ 'id' => 'ext-wikilambda-app' ], |
| 451 | UIUtils::createCodexProgressIndicator( $loadingMessage ) |
| 452 | ) |
| 453 | // Fallback message for users without JavaScript. |
| 454 | . Html::rawElement( |
| 455 | 'noscript', |
| 456 | [], |
| 457 | wfMessage( 'wikilambda-nojs' )->inLanguage( $userLang )->parse() |
| 458 | ) |
| 459 | ); |
| 460 | } |
| 461 | |
| 462 | /** |
| 463 | * Generate the HTML "title" tag for the view page |
| 464 | * |
| 465 | * @param ZObjectContent $content |
| 466 | * @param Title $title |
| 467 | * @param Language $userLang |
| 468 | * @return string |
| 469 | */ |
| 470 | public static function getZObjectViewPageHTMLTitle( |
| 471 | ZObjectContent $content, Title $title, Language $userLang |
| 472 | ): string { |
| 473 | $zobject = $content->getZObject(); |
| 474 | if ( !$zobject || !$zobject->isValid() ) { |
| 475 | // Something's bad, let's give up. |
| 476 | return ''; |
| 477 | } |
| 478 | |
| 479 | $sitename = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::Sitename ); |
| 480 | |
| 481 | // Get label, english fallback, or zid if no available option |
| 482 | $label = $zobject->getLabels() |
| 483 | ->buildStringForLanguage( $userLang ) |
| 484 | ->fallbackWithEnglish() |
| 485 | ->getString(); |
| 486 | |
| 487 | // Return Label/Zid - Sitename |
| 488 | return ( $label ?: $title->getBaseText() ) . ' - ' . $sitename; |
| 489 | } |
| 490 | |
| 491 | /** |
| 492 | * @param Content $content |
| 493 | * @param ValidationParams $validationParams |
| 494 | * @return StatusValue |
| 495 | */ |
| 496 | public function validateSave( $content, $validationParams ) { |
| 497 | if ( $content->isValid() ) { |
| 498 | return StatusValue::newGood(); |
| 499 | } |
| 500 | return StatusValue::newFatal( "wikilambda-invalidzobject" ); |
| 501 | } |
| 502 | |
| 503 | /** |
| 504 | * @inheritDoc |
| 505 | */ |
| 506 | public function preSaveTransform( Content $content, PreSaveTransformParams $pstParams ): Content { |
| 507 | '@phan-var ZObjectContent $content'; |
| 508 | |
| 509 | if ( !$content->isValid() ) { |
| 510 | return $content; |
| 511 | } |
| 512 | |
| 513 | $json = ZObjectUtils::canonicalize( $content->getObject() ); |
| 514 | $encoded = FormatJson::encode( $json, true, FormatJson::UTF8_OK ); |
| 515 | $encodedCleanedWhitespace = str_replace( [ "\r\n", "\r" ], "\n", rtrim( $encoded ) ); |
| 516 | |
| 517 | if ( $content->getText() !== $encodedCleanedWhitespace ) { |
| 518 | $contentClass = $this->getContentClass(); |
| 519 | return new $contentClass( $encodedCleanedWhitespace ); |
| 520 | } |
| 521 | |
| 522 | return $content; |
| 523 | } |
| 524 | |
| 525 | /** |
| 526 | * @inheritDoc |
| 527 | */ |
| 528 | public function createDifferenceEngine( |
| 529 | IContextSource $context, |
| 530 | $oldContentRevisionId = 0, |
| 531 | $newContentRevisionId = 0, |
| 532 | $recentChangesId = 0, |
| 533 | $refreshCache = false, |
| 534 | $unhide = false |
| 535 | ) { |
| 536 | return new ZObjectContentDifferenceEngine( |
| 537 | $context, $oldContentRevisionId, $newContentRevisionId, $recentChangesId, $refreshCache, $unhide |
| 538 | ); |
| 539 | } |
| 540 | |
| 541 | /** |
| 542 | * @inheritDoc |
| 543 | * |
| 544 | * Access level widened to public for use in ZObjectContentDifferenceEngine |
| 545 | */ |
| 546 | public function getSlotDiffRendererWithOptions( IContextSource $context, $options = [] ) { |
| 547 | // NOTE: We intentionally avoid injecting ContentHandlerFactory here. |
| 548 | // Accessing MediaWikiServices during early service construction can |
| 549 | // trigger premature initialization of ContentHandlerFactory, which may |
| 550 | // prevent other extensions (e.g. Wikibase) from registering their |
| 551 | // content models correctly. |
| 552 | $slotDiffRenderer = MediaWikiServices::getInstance() |
| 553 | ->getContentHandlerFactory() |
| 554 | ->getContentHandler( CONTENT_MODEL_TEXT ) |
| 555 | ->getSlotDiffRenderer( $context ); |
| 556 | '@phan-var TextSlotDiffRenderer $slotDiffRenderer'; |
| 557 | return $slotDiffRenderer; |
| 558 | } |
| 559 | |
| 560 | } |