wikimedia/mediawiki-core

View on GitHub
includes/user/Options/ConditionalDefaultsLookup.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace MediaWiki\User\Options;

use InvalidArgumentException;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\MainConfigNames;
use MediaWiki\User\Registration\UserRegistrationLookup;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityUtils;
use Wikimedia\Timestamp\ConvertibleTimestamp;

class ConditionalDefaultsLookup {

    /**
     * @internal Exposed for ServiceWiring only
     */
    public const CONSTRUCTOR_OPTIONS = [
        MainConfigNames::ConditionalUserOptions,
    ];

    private ServiceOptions $options;
    private UserRegistrationLookup $userRegistrationLookup;
    private UserIdentityUtils $userIdentityUtils;

    public function __construct(
        ServiceOptions $options,
        UserRegistrationLookup $userRegistrationLookup,
        UserIdentityUtils $userIdentityUtils
    ) {
        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );

        $this->options = $options;
        $this->userRegistrationLookup = $userRegistrationLookup;
        $this->userIdentityUtils = $userIdentityUtils;
    }

    /**
     * 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;
            case CUDCOND_ANON:
                return !$userIdentity->isRegistered();
            case CUDCOND_NAMED:
                return $this->userIdentityUtils->isNamed( $userIdentity );
            default:
                throw new InvalidArgumentException( 'Unsupported condition ' . $condName );
        }
    }
}