Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.24% covered (warning)
88.24%
60 / 68
50.00% covered (danger)
50.00%
8 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
RealTempUserConfig
88.24% covered (warning)
88.24%
60 / 68
50.00% covered (danger)
50.00%
8 / 16
46.01
0.00% covered (danger)
0.00%
0 / 1
 __construct
95.00% covered (success)
95.00%
19 / 20
0.00% covered (danger)
0.00%
0 / 1
9
 isEnabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isKnown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isAutoCreateAction
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 shouldAutoCreate
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 isTempName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 isReservedName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 getPlaceholderName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getMatchPattern
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getMatchPatterns
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getMatchCondition
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 getExpireAfterDays
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNotifyBeforeExpirationDays
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGeneratorPattern
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getSerialProviderConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSerialMappingConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\User\TempUser;
4
5use BadMethodCallException;
6use InvalidArgumentException;
7use MediaWiki\Permissions\Authority;
8use MediaWiki\Utils\MWTimestamp;
9use Wikimedia\Rdbms\IExpression;
10use Wikimedia\Rdbms\IReadableDatabase;
11
12/**
13 * The real TempUserConfig including internal methods used by TempUserCreator.
14 *
15 * @since 1.39
16 */
17class RealTempUserConfig implements TempUserConfig {
18    /** @var bool */
19    private $known = false;
20
21    /** @var bool */
22    private $enabled = false;
23
24    /** @var array */
25    private $serialProviderConfig = [];
26
27    /** @var array */
28    private $serialMappingConfig = [];
29
30    /** @var string[] */
31    private $autoCreateActions;
32
33    /** @var Pattern|null */
34    private $genPattern;
35
36    /** @var Pattern[]|null */
37    private $matchPatterns;
38
39    /** @var Pattern|null */
40    private $reservedPattern;
41
42    /** @var int|null */
43    private $expireAfterDays;
44
45    /** @var int|null */
46    private $notifyBeforeExpirationDays;
47
48    /**
49     * @param array $config See the documentation of $wgAutoCreateTempUser.
50     *   - known: bool
51     *   - enabled: bool
52     *   - actions: array
53     *   - genPattern: string
54     *   - matchPattern: string|string[], optional
55     *   - reservedPattern: string, optional
56     *   - serialProvider: array
57     *   - serialMapping: array
58     *   - expireAfterDays: int, optional
59     *   - notifyBeforeExpirationDays: int, optional
60     */
61    public function __construct( $config ) {
62        $this->enabled = $config['enabled'] ?? false;
63        $this->known = $this->enabled || ( $config['known'] ?? false );
64
65        // Configuration related to creating new temporary accounts for some actions
66        if ( $this->enabled ) {
67            $this->autoCreateActions = $config['actions'];
68            $this->serialProviderConfig = $config['serialProvider'];
69            $this->serialMappingConfig = $config['serialMapping'];
70        }
71
72        // Configuration related to managing and identifying existing temporary accounts,
73        // regardless of whether new temp accounts are being actively created via the
74        // 'enabled' config flag.
75        if ( $this->known || $this->enabled ) {
76            $this->genPattern = new Pattern( 'genPattern', $config['genPattern'] );
77            $this->expireAfterDays = $config['expireAfterDays'] ?? null;
78            $this->notifyBeforeExpirationDays = $config['notifyBeforeExpirationDays'] ?? null;
79            if ( isset( $config['matchPattern'] ) ) {
80                $matchPatterns = $config['matchPattern'];
81                if ( !is_array( $config['matchPattern'] ) ) {
82                    $matchPatterns = [ $matchPatterns ];
83                }
84                foreach ( $matchPatterns as &$pattern ) {
85                    $pattern = new Pattern( 'matchPattern', $pattern );
86                }
87                $this->matchPatterns = $matchPatterns;
88            } else {
89                $this->matchPatterns = [ $this->genPattern ];
90            }
91        }
92
93        // Configuration that is set regardless of whether the feature is enabled or known.
94        if ( isset( $config['reservedPattern'] ) ) {
95            $this->reservedPattern = new Pattern( 'reservedPattern', $config['reservedPattern'] );
96        }
97    }
98
99    public function isEnabled() {
100        return $this->enabled;
101    }
102
103    public function isKnown() {
104        return $this->known;
105    }
106
107    public function isAutoCreateAction( string $action ) {
108        if ( $action === 'create' ) {
109            $action = 'edit';
110        }
111        return $this->isEnabled()
112            && in_array( $action, $this->autoCreateActions, true );
113    }
114
115    public function shouldAutoCreate( Authority $authority, string $action ) {
116        return $this->isAutoCreateAction( $action )
117            && !$authority->isRegistered()
118            && $authority->isAllowed( 'createaccount' );
119    }
120
121    public function isTempName( string $name ) {
122        if ( !$this->isKnown() ) {
123            return false;
124        }
125        foreach ( $this->matchPatterns as $pattern ) {
126            if ( $pattern->isMatch( $name ) ) {
127                return true;
128            }
129        }
130        return false;
131    }
132
133    public function isReservedName( string $name ) {
134        return $this->isTempName( $name ) || ( $this->reservedPattern && $this->reservedPattern->isMatch( $name ) );
135    }
136
137    public function getPlaceholderName(): string {
138        $year = null;
139        if ( $this->serialProviderConfig['useYear'] ?? false ) {
140            $year = MWTimestamp::getInstance()->format( 'Y' );
141        }
142        if ( $this->isEnabled() ) {
143            return $this->genPattern->generate( '*', $year );
144        } else {
145            throw new BadMethodCallException( __METHOD__ . ' is disabled' );
146        }
147    }
148
149    /**
150     * @deprecated since 1.42.
151     */
152    public function getMatchPattern(): Pattern {
153        wfDeprecated( __METHOD__, '1.42' );
154        if ( $this->isKnown() ) {
155            // This method is deprecated to allow time for callers to update.
156            // This method only returns one Pattern, so just return the first one.
157            return $this->getMatchPatterns()[0];
158        } else {
159            throw new BadMethodCallException( __METHOD__ . ' is disabled' );
160        }
161    }
162
163    public function getMatchPatterns(): array {
164        if ( $this->isKnown() ) {
165            return $this->matchPatterns;
166        } else {
167            throw new BadMethodCallException( __METHOD__ . ' is disabled' );
168        }
169    }
170
171    public function getMatchCondition( IReadableDatabase $db, string $field, string $op ): IExpression {
172        if ( $this->isKnown() ) {
173            $exprs = [];
174            foreach ( $this->getMatchPatterns() as $pattern ) {
175                $exprs[] = $db->expr( $field, $op, $pattern->toLikeValue( $db ) );
176            }
177            if ( count( $exprs ) === 1 ) {
178                return $exprs[0];
179            }
180            if ( $op === IExpression::LIKE ) {
181                return $db->orExpr( $exprs );
182            } elseif ( $op === IExpression::NOT_LIKE ) {
183                return $db->andExpr( $exprs );
184            } else {
185                throw new InvalidArgumentException( "Invalid operator $op" );
186            }
187        } else {
188            throw new BadMethodCallException( __METHOD__ . ' is disabled' );
189        }
190    }
191
192    public function getExpireAfterDays(): ?int {
193        return $this->expireAfterDays;
194    }
195
196    public function getNotifyBeforeExpirationDays(): ?int {
197        return $this->notifyBeforeExpirationDays;
198    }
199
200    /**
201     * @internal For TempUserCreator only
202     * @return Pattern
203     */
204    public function getGeneratorPattern(): Pattern {
205        if ( $this->isEnabled() ) {
206            return $this->genPattern;
207        } else {
208            throw new BadMethodCallException( __METHOD__ . ' is disabled' );
209        }
210    }
211
212    /**
213     * @internal For TempUserCreator only
214     * @return array
215     */
216    public function getSerialProviderConfig(): array {
217        return $this->serialProviderConfig;
218    }
219
220    /**
221     * @internal For TempUserCreator only
222     * @return array
223     */
224    public function getSerialMappingConfig(): array {
225        return $this->serialMappingConfig;
226    }
227}