Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
85.82% |
121 / 141 |
|
87.10% |
27 / 31 |
CRAP | |
0.00% |
0 / 1 |
| Context | |
85.82% |
121 / 141 |
|
87.10% |
27 / 31 |
74.33 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
5 | |||
| debugFromString | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
| newDummyContext | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| getResourceLoader | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRequest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getLogger | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getModules | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getLanguage | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
| getLanguageCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getDirection | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| getSkin | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getUser | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| msg | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| getUserIdentity | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
| getUserObj | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| getDebug | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getOnly | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getVersion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRaw | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isSourceMap | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getImage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getVariant | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getFormat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getImageObj | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
| getContentOverrideCallback | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| shouldIncludeScripts | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| shouldIncludeStyles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| shouldIncludeMessages | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getHash | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
| getReqBase | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
4.10 | |||
| encodeJson | |
90.00% |
18 / 20 |
|
0.00% |
0 / 1 |
7.05 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @license GPL-2.0-or-later |
| 4 | * @file |
| 5 | * @author Trevor Parscal |
| 6 | * @author Roan Kattouw |
| 7 | */ |
| 8 | |
| 9 | namespace MediaWiki\ResourceLoader; |
| 10 | |
| 11 | use MediaWiki\Language\LanguageCode; |
| 12 | use MediaWiki\Language\LocalizationContext; |
| 13 | use MediaWiki\Logger\LoggerFactory; |
| 14 | use MediaWiki\MediaWikiServices; |
| 15 | use MediaWiki\Message\Message; |
| 16 | use MediaWiki\Page\PageReferenceValue; |
| 17 | use MediaWiki\Request\FauxRequest; |
| 18 | use MediaWiki\Request\WebRequest; |
| 19 | use MediaWiki\User\User; |
| 20 | use MediaWiki\User\UserIdentity; |
| 21 | use MediaWiki\User\UserRigorOptions; |
| 22 | use Psr\Log\LoggerInterface; |
| 23 | use Wikimedia\Bcp47Code\Bcp47Code; |
| 24 | use Wikimedia\Bcp47Code\Bcp47CodeValue; |
| 25 | use Wikimedia\Message\MessageParam; |
| 26 | use Wikimedia\Message\MessageSpecifier; |
| 27 | |
| 28 | /** |
| 29 | * Context object that contains information about the state of a specific |
| 30 | * ResourceLoader web request. Passed around to Module methods. |
| 31 | * |
| 32 | * @ingroup ResourceLoader |
| 33 | * @since 1.17 |
| 34 | */ |
| 35 | class Context implements LocalizationContext { |
| 36 | public const DEFAULT_LANG = 'qqx'; |
| 37 | public const DEFAULT_SKIN = 'fallback'; |
| 38 | |
| 39 | /** @internal For use in ResourceLoader classes. */ |
| 40 | public const DEBUG_OFF = 0; |
| 41 | private const DEBUG_MAIN = 2; |
| 42 | |
| 43 | /** @var ResourceLoader */ |
| 44 | protected $resourceLoader; |
| 45 | /** @var WebRequest */ |
| 46 | protected $request; |
| 47 | /** @var LoggerInterface */ |
| 48 | protected $logger; |
| 49 | |
| 50 | // Module content vary |
| 51 | /** @var string */ |
| 52 | protected $skin; |
| 53 | /** @var string */ |
| 54 | protected $language; |
| 55 | /** @var int */ |
| 56 | protected $debug; |
| 57 | /** @var string|null */ |
| 58 | protected $user; |
| 59 | |
| 60 | // Request vary (in addition to cache vary) |
| 61 | /** @var string[] */ |
| 62 | protected $modules; |
| 63 | /** @var string|null */ |
| 64 | protected $only; |
| 65 | /** @var string|null */ |
| 66 | protected $version; |
| 67 | /** @var bool */ |
| 68 | protected $raw; |
| 69 | /** @var bool */ |
| 70 | protected $sourcemap; |
| 71 | /** @var string|null */ |
| 72 | protected $image; |
| 73 | /** @var string|null */ |
| 74 | protected $variant; |
| 75 | /** @var string|null */ |
| 76 | protected $format; |
| 77 | |
| 78 | /** @var string|null */ |
| 79 | protected $direction; |
| 80 | /** @var string|null */ |
| 81 | protected $hash; |
| 82 | /** @var User|null */ |
| 83 | protected $userObj; |
| 84 | /** @var UserIdentity|null|false */ |
| 85 | protected $userIdentity = false; |
| 86 | /** @var Image|false */ |
| 87 | protected $imageObj; |
| 88 | |
| 89 | /** |
| 90 | * @param ResourceLoader $resourceLoader |
| 91 | * @param WebRequest $request |
| 92 | * @param string[]|null $validSkins List of valid skin names. If not passed, |
| 93 | * any skin name is considered valid. Invalid skins are replaced by the default. |
| 94 | */ |
| 95 | public function __construct( |
| 96 | ResourceLoader $resourceLoader, WebRequest $request, $validSkins = null |
| 97 | ) { |
| 98 | $this->resourceLoader = $resourceLoader; |
| 99 | $this->request = $request; |
| 100 | $this->logger = $resourceLoader->getLogger(); |
| 101 | |
| 102 | // Optimisation: Use WebRequest::getRawVal() instead of getVal(). We don't |
| 103 | // need the slow Language+UTF logic meant for user input here. (f303bb9360) |
| 104 | |
| 105 | // List of modules |
| 106 | $modules = $request->getRawVal( 'modules' ); |
| 107 | $this->modules = $modules ? ResourceLoader::expandModuleNames( $modules ) : []; |
| 108 | |
| 109 | // Various parameters |
| 110 | $this->user = $request->getRawVal( 'user' ); |
| 111 | $this->debug = self::debugFromString( $request->getRawVal( 'debug' ) ); |
| 112 | $this->only = $request->getRawVal( 'only' ); |
| 113 | $this->version = $request->getRawVal( 'version' ); |
| 114 | $this->raw = $request->getFuzzyBool( 'raw' ); |
| 115 | $this->sourcemap = $request->getFuzzyBool( 'sourcemap' ); |
| 116 | |
| 117 | // Image requests |
| 118 | $this->image = $request->getRawVal( 'image' ); |
| 119 | $this->variant = $request->getRawVal( 'variant' ); |
| 120 | $this->format = $request->getRawVal( 'format' ); |
| 121 | |
| 122 | $skin = $request->getRawVal( 'skin' ); |
| 123 | if ( |
| 124 | $skin === null |
| 125 | || ( is_array( $validSkins ) && !in_array( $skin, $validSkins ) ) |
| 126 | ) { |
| 127 | // For requests without a known skin specified, |
| 128 | // use MediaWiki's 'fallback' skin for any skin-specific decisions. |
| 129 | $skin = self::DEFAULT_SKIN; |
| 130 | } |
| 131 | $this->skin = $skin; |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * @internal For use in ResourceLoader::inDebugMode |
| 136 | * @param string|null $debug |
| 137 | * @return int |
| 138 | */ |
| 139 | public static function debugFromString( ?string $debug ): int { |
| 140 | // The canonical way to enable debug mode is via debug=true |
| 141 | // Support debug=1 as alias for debug=true for consistency with MediaWiki (T367441). |
| 142 | if ( $debug === 'true' || $debug === '1' || $debug === '2' ) { |
| 143 | $ret = self::DEBUG_MAIN; |
| 144 | } else { |
| 145 | $ret = self::DEBUG_OFF; |
| 146 | } |
| 147 | |
| 148 | return $ret; |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Return a dummy Context object suitable for passing into |
| 153 | * things that don't "really" need a context. |
| 154 | * |
| 155 | * Use cases: |
| 156 | * - Unit tests (deprecated, create empty instance directly or use RLTestCase). |
| 157 | */ |
| 158 | public static function newDummyContext(): Context { |
| 159 | // This currently creates a non-empty instance of ResourceLoader (all modules registered), |
| 160 | // but that's probably not needed. So once that moves into ServiceWiring, this'll |
| 161 | // become more like the EmptyResourceLoader class we have in PHPUnit tests, which |
| 162 | // is what this should've had originally. If this turns out to be untrue, change to: |
| 163 | // `MediaWikiServices::getInstance()->getResourceLoader()` instead. |
| 164 | return new self( new ResourceLoader( |
| 165 | MediaWikiServices::getInstance()->getMainConfig(), |
| 166 | LoggerFactory::getInstance( 'resourceloader' ) |
| 167 | ), new FauxRequest( [] ) ); |
| 168 | } |
| 169 | |
| 170 | public function getResourceLoader(): ResourceLoader { |
| 171 | return $this->resourceLoader; |
| 172 | } |
| 173 | |
| 174 | public function getRequest(): WebRequest { |
| 175 | return $this->request; |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * @deprecated since 1.34 Use Module::getLogger instead |
| 180 | * inside module methods. Use ResourceLoader::getLogger elsewhere. |
| 181 | * @since 1.27 |
| 182 | * @return LoggerInterface |
| 183 | */ |
| 184 | public function getLogger(): LoggerInterface { |
| 185 | return $this->logger; |
| 186 | } |
| 187 | |
| 188 | public function getModules(): array { |
| 189 | return $this->modules; |
| 190 | } |
| 191 | |
| 192 | public function getLanguage(): string { |
| 193 | if ( $this->language === null ) { |
| 194 | // Must be a valid language code after this point (T64849) |
| 195 | // Only support uselang values that follow built-in conventions (T102058) |
| 196 | $lang = $this->getRequest()->getRawVal( 'lang' ) ?? ''; |
| 197 | // Stricter version of RequestContext::sanitizeLangCode() |
| 198 | $validBuiltinCode = MediaWikiServices::getInstance()->getLanguageNameUtils() |
| 199 | ->isValidBuiltInCode( $lang ); |
| 200 | if ( !$validBuiltinCode ) { |
| 201 | // The 'lang' parameter is required. (Not yet enforced.) |
| 202 | // If omitted, localise with the dummy language code. |
| 203 | $lang = self::DEFAULT_LANG; |
| 204 | } |
| 205 | $this->language = $lang; |
| 206 | } |
| 207 | return $this->language; |
| 208 | } |
| 209 | |
| 210 | /** @inheritDoc */ |
| 211 | public function getLanguageCode(): Bcp47Code { |
| 212 | return new Bcp47CodeValue( LanguageCode::bcp47( $this->getLanguage() ) ); |
| 213 | } |
| 214 | |
| 215 | public function getDirection(): string { |
| 216 | if ( $this->direction === null ) { |
| 217 | // Determine directionality based on user language (T8100) |
| 218 | $this->direction = MediaWikiServices::getInstance()->getLanguageFactory() |
| 219 | ->getLanguage( $this->getLanguage() )->getDir(); |
| 220 | } |
| 221 | return $this->direction; |
| 222 | } |
| 223 | |
| 224 | public function getSkin(): string { |
| 225 | return $this->skin; |
| 226 | } |
| 227 | |
| 228 | public function getUser(): ?string { |
| 229 | return $this->user; |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * Get a Message object with context set. See wfMessage for parameters. |
| 234 | * |
| 235 | * @since 1.27 |
| 236 | * @param string|string[]|MessageSpecifier $key Message key, or array of keys, |
| 237 | * or a MessageSpecifier. |
| 238 | * @phpcs:ignore Generic.Files.LineLength |
| 239 | * @param MessageParam|MessageSpecifier|string|int|float|list<MessageParam|MessageSpecifier|string|int|float> ...$params |
| 240 | * See Message::params() |
| 241 | * @return Message |
| 242 | */ |
| 243 | public function msg( $key, ...$params ): Message { |
| 244 | return wfMessage( $key, ...$params ) |
| 245 | // Do not use MediaWiki user language from session. Use the provided one instead. |
| 246 | ->inLanguage( $this->getLanguage() ) |
| 247 | // inLanguage() clears the interface flag, so we need re-enable it. (T291601) |
| 248 | ->setInterfaceMessageFlag( true ) |
| 249 | // Use a dummy title because there is no real title for this endpoint, and the cache won't |
| 250 | // vary on it anyways. |
| 251 | ->page( PageReferenceValue::localReference( NS_SPECIAL, 'Badtitle/ResourceLoaderContext' ) ); |
| 252 | } |
| 253 | |
| 254 | /** |
| 255 | * Get the possibly-cached UserIdentity object for the specified username |
| 256 | * |
| 257 | * This will be null on most requests, |
| 258 | * except for load.php requests that have a 'user' parameter set. |
| 259 | * |
| 260 | * @since 1.38 |
| 261 | * @return UserIdentity|null |
| 262 | */ |
| 263 | public function getUserIdentity(): ?UserIdentity { |
| 264 | if ( $this->userIdentity === false ) { |
| 265 | $username = $this->getUser(); |
| 266 | if ( $username === null ) { |
| 267 | // Anonymous user |
| 268 | $this->userIdentity = null; |
| 269 | } else { |
| 270 | // Use provided username if valid |
| 271 | $this->userIdentity = MediaWikiServices::getInstance() |
| 272 | ->getUserFactory() |
| 273 | ->newFromName( $username, UserRigorOptions::RIGOR_VALID ); |
| 274 | } |
| 275 | } |
| 276 | return $this->userIdentity; |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * Get the possibly-cached User object for the specified username |
| 281 | * |
| 282 | * @since 1.25 |
| 283 | * @return User |
| 284 | */ |
| 285 | public function getUserObj(): User { |
| 286 | if ( $this->userObj === null ) { |
| 287 | $username = $this->getUser(); |
| 288 | $userFactory = MediaWikiServices::getInstance()->getUserFactory(); |
| 289 | if ( $username ) { |
| 290 | // Use provided username if valid, fallback to anonymous user |
| 291 | $this->userObj = $userFactory->newFromName( $username, UserRigorOptions::RIGOR_VALID ); |
| 292 | } |
| 293 | // Anonymous user |
| 294 | $this->userObj ??= $userFactory->newAnonymous(); |
| 295 | } |
| 296 | |
| 297 | return $this->userObj; |
| 298 | } |
| 299 | |
| 300 | public function getDebug(): int { |
| 301 | return $this->debug; |
| 302 | } |
| 303 | |
| 304 | public function getOnly(): ?string { |
| 305 | return $this->only; |
| 306 | } |
| 307 | |
| 308 | /** |
| 309 | * @see Module::getVersionHash |
| 310 | * @see ClientHtml::makeLoad |
| 311 | * @return string|null |
| 312 | */ |
| 313 | public function getVersion(): ?string { |
| 314 | return $this->version; |
| 315 | } |
| 316 | |
| 317 | public function getRaw(): bool { |
| 318 | return $this->raw; |
| 319 | } |
| 320 | |
| 321 | /** |
| 322 | * @since 1.41 |
| 323 | * @return bool |
| 324 | */ |
| 325 | public function isSourceMap(): bool { |
| 326 | return $this->sourcemap; |
| 327 | } |
| 328 | |
| 329 | public function getImage(): ?string { |
| 330 | return $this->image; |
| 331 | } |
| 332 | |
| 333 | public function getVariant(): ?string { |
| 334 | return $this->variant; |
| 335 | } |
| 336 | |
| 337 | public function getFormat(): ?string { |
| 338 | return $this->format; |
| 339 | } |
| 340 | |
| 341 | /** |
| 342 | * If this is a request for an image, get the Image object. |
| 343 | * |
| 344 | * @since 1.25 |
| 345 | * @return Image|false false if a valid object cannot be created |
| 346 | */ |
| 347 | public function getImageObj() { |
| 348 | if ( $this->imageObj === null ) { |
| 349 | $this->imageObj = false; |
| 350 | |
| 351 | if ( !$this->image ) { |
| 352 | return $this->imageObj; |
| 353 | } |
| 354 | |
| 355 | $modules = $this->getModules(); |
| 356 | if ( count( $modules ) !== 1 ) { |
| 357 | return $this->imageObj; |
| 358 | } |
| 359 | |
| 360 | $module = $this->getResourceLoader()->getModule( $modules[0] ); |
| 361 | if ( !$module || !$module instanceof ImageModule ) { |
| 362 | return $this->imageObj; |
| 363 | } |
| 364 | |
| 365 | $image = $module->getImage( $this->image, $this ); |
| 366 | if ( !$image ) { |
| 367 | return $this->imageObj; |
| 368 | } |
| 369 | |
| 370 | $this->imageObj = $image; |
| 371 | } |
| 372 | |
| 373 | return $this->imageObj; |
| 374 | } |
| 375 | |
| 376 | /** |
| 377 | * Return the replaced-content mapping callback |
| 378 | * |
| 379 | * When editing a page that's used to generate the scripts or styles of a |
| 380 | * WikiModule, a preview should use the to-be-saved version of |
| 381 | * the page rather than the current version in the database. A context |
| 382 | * supporting such previews should return a callback to return these |
| 383 | * mappings here. |
| 384 | * |
| 385 | * @since 1.32 |
| 386 | * @return callable|null Signature is `Content|null func( Title $t )` |
| 387 | */ |
| 388 | public function getContentOverrideCallback() { |
| 389 | return null; |
| 390 | } |
| 391 | |
| 392 | public function shouldIncludeScripts(): bool { |
| 393 | return $this->getOnly() === null || $this->getOnly() === 'scripts'; |
| 394 | } |
| 395 | |
| 396 | public function shouldIncludeStyles(): bool { |
| 397 | return $this->getOnly() === null || $this->getOnly() === 'styles'; |
| 398 | } |
| 399 | |
| 400 | public function shouldIncludeMessages(): bool { |
| 401 | return $this->getOnly() === null; |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * All factors that uniquely identify this request, except 'modules'. |
| 406 | * |
| 407 | * The list of modules is excluded here for legacy reasons as most callers already |
| 408 | * split up handling of individual modules. Including it here would massively fragment |
| 409 | * the cache and decrease its usefulness. |
| 410 | * |
| 411 | * E.g. Used by RequestFileCache to form a cache key for storing the response output. |
| 412 | */ |
| 413 | public function getHash(): string { |
| 414 | if ( $this->hash === null ) { |
| 415 | $this->hash = implode( '|', [ |
| 416 | // Module content vary |
| 417 | $this->getLanguage(), |
| 418 | $this->getSkin(), |
| 419 | (string)$this->getDebug(), |
| 420 | $this->getUser() ?? '', |
| 421 | // Request vary |
| 422 | $this->getOnly() ?? '', |
| 423 | $this->getVersion() ?? '', |
| 424 | (string)$this->getRaw(), |
| 425 | $this->getImage() ?? '', |
| 426 | $this->getVariant() ?? '', |
| 427 | $this->getFormat() ?? '', |
| 428 | ] ); |
| 429 | } |
| 430 | return $this->hash; |
| 431 | } |
| 432 | |
| 433 | /** |
| 434 | * Get the request base parameters, omitting any defaults. |
| 435 | * |
| 436 | * @internal For use by StartUpModule only |
| 437 | * @return string[] |
| 438 | */ |
| 439 | public function getReqBase(): array { |
| 440 | $reqBase = []; |
| 441 | $lang = $this->getLanguage(); |
| 442 | if ( $lang !== self::DEFAULT_LANG ) { |
| 443 | $reqBase['lang'] = $lang; |
| 444 | } |
| 445 | $skin = $this->getSkin(); |
| 446 | if ( $skin !== self::DEFAULT_SKIN ) { |
| 447 | $reqBase['skin'] = $skin; |
| 448 | } |
| 449 | $debug = $this->getDebug(); |
| 450 | if ( $debug !== self::DEBUG_OFF ) { |
| 451 | $reqBase['debug'] = strval( $debug ); |
| 452 | } |
| 453 | return $reqBase; |
| 454 | } |
| 455 | |
| 456 | /** |
| 457 | * Wrapper around json_encode that avoids needless escapes, |
| 458 | * and pretty-prints in debug mode. |
| 459 | * |
| 460 | * @since 1.34 |
| 461 | * @param mixed $data |
| 462 | * @return string|false JSON string, false on error |
| 463 | */ |
| 464 | public function encodeJson( $data ) { |
| 465 | // Keep output as small as possible by disabling needless escape modes |
| 466 | // that PHP uses by default. |
| 467 | // However, while most module scripts are only served on HTTP responses |
| 468 | // for JavaScript, some modules can also be embedded in the HTML as inline |
| 469 | // scripts. This, and the fact that we sometimes need to export strings |
| 470 | // containing user-generated content and labels that may genuinely contain |
| 471 | // a sequences like "</script>", we need to encode either '/' or '<'. |
| 472 | // By default PHP escapes '/'. Let's escape '<' instead which is less common |
| 473 | // and allows URLs to mostly remain readable. |
| 474 | $jsonFlags = JSON_UNESCAPED_SLASHES | |
| 475 | JSON_UNESCAPED_UNICODE | |
| 476 | JSON_PARTIAL_OUTPUT_ON_ERROR | |
| 477 | JSON_HEX_TAG | |
| 478 | JSON_HEX_AMP; |
| 479 | if ( $this->getDebug() ) { |
| 480 | $jsonFlags |= JSON_PRETTY_PRINT; |
| 481 | } |
| 482 | $json = json_encode( $data, $jsonFlags ); |
| 483 | if ( json_last_error() !== JSON_ERROR_NONE ) { |
| 484 | // When we log this warning, the stack trace will not show which component added |
| 485 | // the bad value to our array, because the array was made earlier in the process. |
| 486 | // To ease error triage, indicate which key contains malformed UTF-8. |
| 487 | $jsonErr = json_last_error_msg(); |
| 488 | $badKey = null; |
| 489 | if ( is_array( $data ) ) { |
| 490 | foreach ( $data as $key => $value ) { |
| 491 | if ( json_encode( $value ) === false ) { |
| 492 | $badKey = $key; |
| 493 | break; |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | if ( $badKey !== null ) { |
| 498 | trigger_error( "Failed to JSON encode $badKey: $jsonErr", E_USER_WARNING ); |
| 499 | } else { |
| 500 | trigger_error( "Partially failed to JSON encode: $jsonErr", E_USER_WARNING ); |
| 501 | } |
| 502 | } |
| 503 | return $json; |
| 504 | } |
| 505 | } |