Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
UserRegistrationLookup
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
6 / 6
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isRegistered
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProvider
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getRegistration
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFirstRegistration
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 getFirstRegistrationBatch
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3namespace MediaWiki\User\Registration;
4
5use InvalidArgumentException;
6use MediaWiki\Config\ServiceOptions;
7use MediaWiki\MainConfigNames;
8use MediaWiki\MainConfigSchema;
9use MediaWiki\User\UserIdentity;
10use Wikimedia\ObjectFactory\ObjectFactory;
11
12/**
13 * @since 1.41
14 */
15class UserRegistrationLookup {
16
17    /**
18     * @internal for use in ServiceWiring
19     * @var string[] Config names to require
20     */
21    public const CONSTRUCTOR_OPTIONS = [
22        MainConfigNames::UserRegistrationProviders,
23    ];
24
25    /** @var array ObjectFactory specs indexed by provider name */
26    private array $providersSpecs;
27
28    /** @var IUserRegistrationProvider[] Constructed registration providers indexed by name */
29    private array $providers = [];
30
31    private ObjectFactory $objectFactory;
32
33    public function __construct(
34        ServiceOptions $options,
35        ObjectFactory $objectFactory
36    ) {
37        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
38        $this->providersSpecs = $options->get( MainConfigNames::UserRegistrationProviders );
39        $this->objectFactory = $objectFactory;
40    }
41
42    /**
43     * Is a registration provider registered?
44     *
45     * @see MainConfigSchema::UserRegistrationLookupProviders
46     * @param string $type
47     * @return bool
48     */
49    public function isRegistered( string $type ): bool {
50        return array_key_exists( $type, $this->providersSpecs );
51    }
52
53    /**
54     * Construct a registration provider, if needed
55     *
56     * @param string $type
57     * @return IUserRegistrationProvider
58     */
59    private function getProvider( string $type ): IUserRegistrationProvider {
60        if ( !$this->isRegistered( $type ) ) {
61            throw new InvalidArgumentException( 'Registration provider ' . $type . ' is not registered' );
62        }
63        if ( !array_key_exists( $type, $this->providers ) ) {
64            $this->providers[$type] = $this->objectFactory->createObject(
65                $this->providersSpecs[$type],
66                [ 'assertClass' => IUserRegistrationProvider::class ]
67            );
68        }
69        return $this->providers[$type];
70    }
71
72    /**
73     * @param UserIdentity $user User for which registration should be fetched.
74     * @param string $type Name of a registered registration provider
75     * @return string|null|false Registration timestamp (TS_MW), null if not available or false if it
76     * cannot be fetched (anonymous users, for example).
77     */
78    public function getRegistration(
79        UserIdentity $user,
80        string $type = LocalUserRegistrationProvider::TYPE
81    ) {
82        return $this->getProvider( $type )->fetchRegistration( $user );
83    }
84
85    /**
86     * Find the first registration timestamp for a given user
87     *
88     * Note this invokes _all_ registered providers.
89     *
90     * @param UserIdentity $user
91     * @return string|null Earliest registration timestamp (TS_MW), null if not available.
92     */
93    public function getFirstRegistration( UserIdentity $user ): ?string {
94        $firstRegistrationTimestamp = null;
95        foreach ( $this->providersSpecs as $providerKey => $_ ) {
96            $registrationTimestamp = $this->getRegistration( $user, $providerKey );
97            if ( $registrationTimestamp === null || $registrationTimestamp === false ) {
98                // Provider was unable to return a registration timestamp for $providerKey, skip
99                // them.
100                continue;
101            }
102            if ( $firstRegistrationTimestamp === null ||
103                $registrationTimestamp < $firstRegistrationTimestamp
104            ) {
105                $firstRegistrationTimestamp = $registrationTimestamp;
106            }
107        }
108
109        return $firstRegistrationTimestamp;
110    }
111
112    /**
113     * Get the first registration timestamp for a batch of users.
114     * This invokes all registered providers.
115     *
116     * @param iterable<UserIdentity> $users
117     * @return string[]|null[] Map of registration timestamps in MediaWiki format keyed by user ID.
118     * The timestamp may be `null` for users without a stored registration timestamp and for anonymous users.
119     */
120    public function getFirstRegistrationBatch( iterable $users ): array {
121        $earliestTimestampsById = [];
122
123        foreach ( $users as $user ) {
124            $earliestTimestampsById[$user->getId()] = null;
125        }
126
127        foreach ( $this->providersSpecs as $providerKey => $_ ) {
128            $timestampsById = $this->getProvider( $providerKey )->fetchRegistrationBatch( $users );
129
130            foreach ( $timestampsById as $userId => $timestamp ) {
131                $curValue = $earliestTimestampsById[$userId];
132
133                if ( $timestamp !== null && ( $curValue === null || $timestamp < $curValue ) ) {
134                    $earliestTimestampsById[$userId] = $timestamp;
135                }
136            }
137        }
138
139        return $earliestTimestampsById;
140    }
141}