Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 20 |
CRAP | |
0.00% |
0 / 79 |
StubMetadataCollector | |
0.00% |
0 / 1 |
|
0.00% |
0 / 20 |
1560 | |
0.00% |
0 / 79 |
__construct | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 2 |
|||
addCategory | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
addWarningMsg | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
addExternalLink | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
setOutputFlag | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
setPageProperty | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
setExtensionData | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
setJsConfigVar | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
appendExtensionData | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
appendJsConfigVar | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
addModules | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
addModuleStyles | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
setLimitReportData | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
collect | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 29 |
|||
get | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 11 |
|||
getModules | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getModuleStyles | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getJsConfigVars | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
getCategories | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getPageProperty | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 1 |
<?php | |
declare( strict_types = 1 ); | |
namespace Wikimedia\Parsoid\Config; | |
use Psr\Log\LoggerInterface; | |
use Psr\Log\LogLevel; | |
use Psr\Log\NullLogger; | |
use Wikimedia\Parsoid\Core\ContentMetadataCollector; | |
use Wikimedia\Parsoid\Core\ContentMetadataCollectorCompat; | |
/** | |
* Minimal implementation of a ContentMetadataCollector which just | |
* records all metadata in an array. Used for testing or operation | |
* in API mode. | |
*/ | |
class StubMetadataCollector implements ContentMetadataCollector { | |
use ContentMetadataCollectorCompat; | |
/** @var LoggerInterface */ | |
private $logger; | |
/** @var array<string,array> */ | |
private $mWarningMsgs = []; | |
/** @var array */ | |
private $storage = []; | |
/** @var string */ | |
private const MERGE_STRATEGY_KEY = '_parsoid-strategy_'; | |
/** | |
* Non-standard merge strategy to use for properties which are *not* | |
* accumulators: "write-once" means that the property should be set | |
* once (although subsequently resetting it to the same value is ok) | |
* and an error will be thrown if there is an attempt to combine | |
* multiple values. | |
* | |
* This strategy is internal to the StubMetadataCollector for now; | |
* ParserOutput implements similar semantics for many of its properties, | |
* but not (yet) in a principled or uniform way. | |
*/ | |
private const MERGE_STRATEGY_WRITE_ONCE = 'write-once'; | |
/** | |
* @param ?LoggerInterface $logger Optional logger to log warnings | |
* for unsafe metadata updates | |
*/ | |
public function __construct( ?LoggerInterface $logger = null ) { | |
$this->logger = $logger ?? new NullLogger; | |
} | |
/** @inheritDoc */ | |
public function addCategory( $c, $sort = '' ): void { | |
$this->collect( 'categories', $c, $sort, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
/** @inheritDoc */ | |
public function addWarningMsg( string $msg, ...$args ): void { | |
$this->mWarningMsgs[$msg] = $args; | |
} | |
/** @inheritDoc */ | |
public function addExternalLink( string $url ): void { | |
$this->collect( 'externallinks', '', $url ); | |
} | |
/** @inheritDoc */ | |
public function setOutputFlag( string $name, bool $value = true ): void { | |
$this->collect( 'outputflags', $name, (string)$value, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
/** @inheritDoc */ | |
public function setPageProperty( string $name, $value ): void { | |
$this->collect( 'properties', $name, $value, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
/** @inheritDoc */ | |
public function setExtensionData( string $key, $value ): void { | |
$this->collect( 'extensiondata', $key, $value, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
/** @inheritDoc */ | |
public function setJsConfigVar( string $key, $value ): void { | |
$this->collect( 'jsconfigvars', $key, $value, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
/** @inheritDoc */ | |
public function appendExtensionData( | |
string $key, | |
$value, | |
string $strategy = self::MERGE_STRATEGY_UNION | |
): void { | |
$this->collect( 'extensiondata', $key, $value, $strategy ); | |
} | |
/** @inheritDoc */ | |
public function appendJsConfigVar( | |
string $key, | |
string $value, | |
string $strategy = self::MERGE_STRATEGY_UNION | |
): void { | |
$this->collect( 'jsconfigvars', $key, $value, $strategy ); | |
} | |
/** @inheritDoc */ | |
public function addModules( array $modules ): void { | |
foreach ( $modules as $module ) { | |
$this->collect( 'modules', '', $module ); | |
} | |
} | |
/** @inheritDoc */ | |
public function addModuleStyles( array $moduleStyles ): void { | |
foreach ( $moduleStyles as $style ) { | |
$this->collect( 'modulestyles', '', $style ); | |
} | |
} | |
/** @inheritDoc */ | |
public function setLimitReportData( string $key, $value ): void { | |
// XXX maybe need to JSON-encode $value | |
$this->collect( 'limitreportdata', $key, $value, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
/** | |
* Unified internal implementation of metadata collection. | |
* @param string $which Internal string identifying the type of metadata. | |
* @param string $key Key for storage (or '' if this is not relevant) | |
* @param mixed $value Value to store | |
* @param string $strategy "union" or "write-once" | |
*/ | |
private function collect( | |
string $which, string $key, $value, | |
string $strategy = self::MERGE_STRATEGY_UNION | |
): void { | |
if ( !array_key_exists( $which, $this->storage ) ) { | |
$this->storage[$which] = []; | |
} | |
if ( !array_key_exists( $key, $this->storage[$which] ) ) { | |
$this->storage[$which][$key] = [ self::MERGE_STRATEGY_KEY => $strategy ]; | |
if ( $strategy === self::MERGE_STRATEGY_WRITE_ONCE ) { | |
$this->storage[$which][$key]['value'] = $value; | |
return; | |
} | |
} | |
if ( $this->storage[$which][$key][self::MERGE_STRATEGY_KEY] !== $strategy ) { | |
$this->logger->log( | |
LogLevel::WARNING, | |
"Conflicting strategies for $which $key" | |
); | |
// Destructive update for compatibility; this is deprecated! | |
unset( $this->storage[$which][$key] ); | |
$this->collect( $which, $key, $value, $strategy ); | |
return; | |
} | |
if ( $strategy === self::MERGE_STRATEGY_WRITE_ONCE ) { | |
if ( ( $this->storage[$which][$key]['value'] ?? null ) === $value ) { | |
return; // already exists with the desired value | |
} | |
$this->logger->log( | |
LogLevel::WARNING, | |
"Multiple writes to a write-once: $which $key" | |
); | |
// Destructive update for compatibility; this is deprecated! | |
unset( $this->storage[$which][$key] ); | |
$this->collect( $which, $key, $value, $strategy ); | |
return; | |
} elseif ( $strategy === self::MERGE_STRATEGY_UNION ) { | |
if ( !( is_string( $value ) || is_int( $value ) ) ) { | |
throw new \Exception( "Bad value type for $key: " . gettype( $value ) ); | |
} | |
$this->storage[$which][$key][$value] = true; | |
return; | |
} else { | |
throw new \Exception( "Unknown strategy: $strategy" ); | |
} | |
} | |
/** | |
* Retrieve values from the collector. | |
* @param string $which Internal string identifying the type of metadata. | |
* @param string|null $key Key for storage (or '' if this is not relevant) | |
* @param string $defaultStrategy Determines whether to return an empty | |
* array or null for a missing $key | |
* @return mixed | |
*/ | |
private function get( string $which, ?string $key = null, string $defaultStrategy = self::MERGE_STRATEGY_UNION ) { | |
if ( $key !== null ) { | |
$result = ( $this->storage[$which] ?? [] )[$key] ?? []; | |
$strategy = $result[self::MERGE_STRATEGY_KEY] ?? $defaultStrategy; | |
unset( $result[self::MERGE_STRATEGY_KEY] ); | |
if ( $strategy === self::MERGE_STRATEGY_WRITE_ONCE ) { | |
return $result['value'] ?? null; | |
} else { | |
return array_keys( $result ); | |
} | |
} | |
$result = []; | |
foreach ( ( $this->storage[$which] ?? [] ) as $key => $ignore ) { | |
$result[$key] = $this->get( $which, $key ); | |
} | |
return $result; | |
} | |
// @internal introspection methods | |
/** @return string[] */ | |
public function getModules(): array { | |
return $this->get( 'modules', '' ); | |
} | |
/** @return string[] */ | |
public function getModuleStyles(): array { | |
return $this->get( 'modulestyles', '' ); | |
} | |
/** @return string[] */ | |
public function getJsConfigVars(): array { | |
// This is somewhat unusual, in that we expose the 'set' represenation | |
// as $key => true, instead of just returning array_keys(). | |
$result = $this->storage['jsconfigvars'] ?? []; | |
foreach ( $result as $key => &$value ) { | |
$strategy = $value[self::MERGE_STRATEGY_KEY] ?? null; | |
unset( $value[self::MERGE_STRATEGY_KEY] ); | |
if ( $strategy === self::MERGE_STRATEGY_WRITE_ONCE ) { | |
$value = array_keys( $value )[0]; | |
} | |
} | |
return $result; | |
} | |
/** @return array<string,string> */ | |
public function getCategories(): array { | |
return $this->get( 'categories' ); | |
} | |
/** | |
* @param string $name | |
* @return ?string | |
*/ | |
public function getPageProperty( string $name ): ?string { | |
// Note that core returns `false` (instead of null) for a | |
// missing key, which is something we should probably fix | |
// before 1.38 is released. | |
return $this->get( 'properties', $name, self::MERGE_STRATEGY_WRITE_ONCE ); | |
} | |
} |