Why: Sometimes, it is necessary to have different behavior for newly registered and existing users. For example, this happens in the Echo or GrowthExperiments extensions. As of now, this behavior is implemented by inserting user_properties rows in onLocalUserCreated. Over time, this results in a singificant amount of rows inserted, which contributes to the user_properties table bloat, which is already overly large (cf. T54777). This patch makes it possible to remove such rows by supporting conditional defaults for user properties. What: Add support for conditional defaults of user properties. This can be configured via `ConditionalUserOptions` config option. Bug: T321527 Change-Id: I1549c3137e66801c85e03e46427e27da333d68e2
134 lines
3.6 KiB
PHP
134 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\User\Options;
|
|
|
|
use InvalidArgumentException;
|
|
use MediaWiki\Config\ServiceOptions;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\User\Registration\UserRegistrationLookup;
|
|
use MediaWiki\User\UserIdentity;
|
|
use Wikimedia\Timestamp\ConvertibleTimestamp;
|
|
|
|
class ConditionalDefaultsLookup {
|
|
|
|
/**
|
|
* @internal Exposed for ServiceWiring only
|
|
*/
|
|
public const CONSTRUCTOR_OPTIONS = [
|
|
MainConfigNames::ConditionalUserOptions,
|
|
];
|
|
|
|
private ServiceOptions $options;
|
|
private UserRegistrationLookup $userRegistrationLookup;
|
|
|
|
public function __construct(
|
|
ServiceOptions $options,
|
|
UserRegistrationLookup $userRegistrationLookup
|
|
) {
|
|
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
|
|
|
$this->options = $options;
|
|
$this->userRegistrationLookup = $userRegistrationLookup;
|
|
}
|
|
|
|
/**
|
|
* Does the option support conditional defaults?
|
|
*
|
|
* @param string $option
|
|
* @return bool
|
|
*/
|
|
public function hasConditionalDefault( string $option ): bool {
|
|
return array_key_exists(
|
|
$option,
|
|
$this->options->get( MainConfigNames::ConditionalUserOptions )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get all conditionally default user options
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function getConditionallyDefaultOptions(): array {
|
|
return array_keys(
|
|
$this->options->get( MainConfigNames::ConditionalUserOptions )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the conditional default for user and option
|
|
*
|
|
* @param string $optionName
|
|
* @param UserIdentity $userIdentity
|
|
* @return mixed|null The default value if set, or null if it cannot be determined
|
|
* conditionally (default from DefaultOptionsLookup should be used in that case).
|
|
*/
|
|
public function getOptionDefaultForUser(
|
|
string $optionName,
|
|
UserIdentity $userIdentity
|
|
) {
|
|
$conditionalDefaults = $this->options
|
|
->get( MainConfigNames::ConditionalUserOptions )[$optionName] ?? [];
|
|
foreach ( $conditionalDefaults as $conditionalDefault ) {
|
|
// At the zeroth index of the conditional case, the intended value is found; the rest
|
|
// of the array are conditions, which are evaluated in checkConditionsForUser().
|
|
$value = array_shift( $conditionalDefault );
|
|
if ( $this->checkConditionsForUser( $userIdentity, $conditionalDefault ) ) {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Are ALL conditions satisfied for the given user?
|
|
*
|
|
* @param UserIdentity $userIdentity
|
|
* @param array $conditions
|
|
* @return bool
|
|
*/
|
|
private function checkConditionsForUser( UserIdentity $userIdentity, array $conditions ): bool {
|
|
foreach ( $conditions as $condition ) {
|
|
if ( !$this->checkConditionForUser( $userIdentity, $condition ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Is ONE condition satisfied for the given user?
|
|
*
|
|
* @param UserIdentity $userIdentity
|
|
* @param array|int $cond Either [ CUDCOND_*, args ] or CUDCOND_*, depending on whether the
|
|
* condition has any arguments.
|
|
* @return bool
|
|
*/
|
|
private function checkConditionForUser(
|
|
UserIdentity $userIdentity,
|
|
$cond
|
|
): bool {
|
|
if ( !is_array( $cond ) ) {
|
|
$cond = [ $cond ];
|
|
}
|
|
if ( $cond === [] ) {
|
|
throw new InvalidArgumentException( 'Empty condition' );
|
|
}
|
|
$condName = array_shift( $cond );
|
|
switch ( $condName ) {
|
|
case CUDCOND_AFTER:
|
|
$registration = $this->userRegistrationLookup->getRegistration( $userIdentity );
|
|
if ( $registration === null || $registration === false ) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
(int)ConvertibleTimestamp::convert( TS_UNIX, $registration ) -
|
|
(int)ConvertibleTimestamp::convert( TS_UNIX, $cond[0] )
|
|
) > 0;
|
|
default:
|
|
throw new InvalidArgumentException( 'Unsupported condition ' . $condName );
|
|
}
|
|
}
|
|
}
|