Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.14% covered (warning)
67.14%
47 / 70
36.84% covered (danger)
36.84%
7 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
CompositeBlock
67.14% covered (warning)
67.14%
47 / 70
36.84% covered (danger)
36.84%
7 / 19
92.95
0.00% covered (danger)
0.00%
0 / 1
 createFromBlocks
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 propHasValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 methodReturnsValue
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getOriginalBlocks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withOriginalBlocks
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 toArray
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTimestamp
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getExpiry
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getIdentifier
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 appliesToRight
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 appliesToUsertalk
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 appliesToTitle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 appliesToNamespace
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appliesToPage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appliesToPasswordReset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBy
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getByName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBlocker
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Class for blocks composed from multiple blocks.
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Block;
10
11use InvalidArgumentException;
12use MediaWiki\Message\Message;
13use MediaWiki\Title\Title;
14use MediaWiki\User\UserIdentity;
15
16/**
17 * Multiple Block class.
18 *
19 * Multiple blocks exist to enforce restrictions from more than one block, if several
20 * blocks apply to a user/IP. Multiple blocks are created temporarily on enforcement.
21 *
22 * @since 1.34
23 */
24class CompositeBlock extends AbstractBlock {
25    /** @var AbstractBlock[] */
26    private $originalBlocks;
27
28    /**
29     * Helper method for merging multiple blocks into a composite block.
30     * @param AbstractBlock ...$blocks
31     * @return self
32     */
33    public static function createFromBlocks( AbstractBlock ...$blocks ): self {
34        $originalBlocks = [];
35        foreach ( $blocks as $block ) {
36            if ( $block instanceof self ) {
37                $originalBlocks = array_merge( $originalBlocks, $block->getOriginalBlocks() );
38            } else {
39                $originalBlocks[] = $block;
40            }
41        }
42        if ( !$originalBlocks ) {
43            throw new InvalidArgumentException( 'No blocks given' );
44        }
45        return new self( [
46            'target' => $originalBlocks[0]->target,
47            'reason' => new Message( 'blockedtext-composite-reason' ),
48            'originalBlocks' => $originalBlocks,
49        ] );
50    }
51
52    /**
53     * Create a new block with specified parameters on a user, IP or IP range.
54     *
55     * @param array $options Parameters of the block, with options supported by
56     *  `AbstractBlock::__construct`, and also:
57     *  - originalBlocks: (Block[]) Blocks that this block is composed from
58     */
59    public function __construct( array $options = [] ) {
60        parent::__construct( $options );
61
62        $defaults = [
63            'originalBlocks' => [],
64        ];
65
66        $options += $defaults;
67
68        $this->originalBlocks = $options[ 'originalBlocks' ];
69
70        $this->setHideName( $this->propHasValue( 'hideName', true ) );
71        $this->isHardblock( $this->propHasValue( 'isHardblock', true ) );
72        $this->isSitewide( $this->propHasValue( 'isSitewide', true ) );
73        $this->isEmailBlocked( $this->propHasValue( 'blockEmail', true ) );
74        $this->isCreateAccountBlocked( $this->propHasValue( 'blockCreateAccount', true ) );
75        $this->isUsertalkEditAllowed( !$this->propHasValue( 'allowUsertalk', false ) );
76    }
77
78    /**
79     * Determine whether any original blocks have a particular property set to a
80     * particular value.
81     *
82     * @param string $prop
83     * @param mixed $value
84     * @return bool At least one block has the property set to the value
85     */
86    private function propHasValue( $prop, $value ) {
87        foreach ( $this->originalBlocks as $block ) {
88            if ( $block->$prop == $value ) {
89                return true;
90            }
91        }
92        return false;
93    }
94
95    /**
96     * Determine whether any original blocks have a particular method returning a
97     * particular value.
98     *
99     * @param string $method
100     * @param mixed $value
101     * @param mixed ...$params
102     * @return bool At least one block has the method returning the value
103     */
104    private function methodReturnsValue( $method, $value, ...$params ) {
105        foreach ( $this->originalBlocks as $block ) {
106            if ( $block->$method( ...$params ) == $value ) {
107                return true;
108            }
109        }
110        return false;
111    }
112
113    /**
114     * Get the original blocks from which this block is composed
115     *
116     * @since 1.34
117     * @return AbstractBlock[]
118     */
119    public function getOriginalBlocks() {
120        return $this->originalBlocks;
121    }
122
123    /**
124     * Create a clone of the object with the original blocks array set to
125     * something else.
126     *
127     * @since 1.42
128     * @param AbstractBlock[] $blocks
129     * @return self
130     */
131    public function withOriginalBlocks( array $blocks ) {
132        $clone = clone $this;
133        $clone->originalBlocks = $blocks;
134        return $clone;
135    }
136
137    public function toArray(): array {
138        return $this->originalBlocks;
139    }
140
141    /**
142     * @inheritDoc
143     */
144    public function getTimestamp(): string {
145        $minStart = null;
146        foreach ( $this->originalBlocks as $block ) {
147            $startTime = $block->getTimestamp();
148            if ( $minStart === null || $startTime === '' || $startTime < $minStart ) {
149                $minStart = $startTime;
150            }
151        }
152        return $minStart ?? '';
153    }
154
155    /**
156     * @inheritDoc
157     */
158    public function getExpiry(): string {
159        $maxExpiry = null;
160        foreach ( $this->originalBlocks as $block ) {
161            $expiry = $block->getExpiry();
162            if ( $maxExpiry === null || $expiry === '' || $expiry > $maxExpiry ) {
163                $maxExpiry = $expiry;
164            }
165        }
166        return $maxExpiry ?? '';
167    }
168
169    /**
170     * @inheritDoc
171     */
172    public function getIdentifier( $wikiId = self::LOCAL ) {
173        $identifier = [];
174        foreach ( $this->originalBlocks as $block ) {
175            $identifier[] = $block->getIdentifier( $wikiId );
176        }
177        return $identifier;
178    }
179
180    /**
181     * @inheritDoc
182     *
183     * Determines whether the CompositeBlock applies to a right by checking
184     * whether the original blocks apply to that right. Each block can report
185     * true (applies), false (does not apply) or null (unsure). Then:
186     * - If any original blocks apply, this block applies
187     * - If no original blocks apply but any are unsure, this block is unsure
188     * - If all blocks do not apply, this block does not apply
189     */
190    public function appliesToRight( $right ) {
191        $isUnsure = false;
192
193        foreach ( $this->originalBlocks as $block ) {
194            $appliesToRight = $block->appliesToRight( $right );
195
196            if ( $appliesToRight ) {
197                return true;
198            } elseif ( $appliesToRight === null ) {
199                $isUnsure = true;
200            }
201        }
202
203        return $isUnsure ? null : false;
204    }
205
206    /**
207     * @inheritDoc
208     */
209    public function appliesToUsertalk( ?Title $usertalk = null ): bool {
210        return $this->methodReturnsValue( __FUNCTION__, true, $usertalk );
211    }
212
213    /**
214     * @inheritDoc
215     */
216    public function appliesToTitle( Title $title ) {
217        return $this->methodReturnsValue( __FUNCTION__, true, $title );
218    }
219
220    /**
221     * @inheritDoc
222     */
223    public function appliesToNamespace( $ns ) {
224        return $this->methodReturnsValue( __FUNCTION__, true, $ns );
225    }
226
227    /**
228     * @inheritDoc
229     */
230    public function appliesToPage( $pageId ) {
231        return $this->methodReturnsValue( __FUNCTION__, true, $pageId );
232    }
233
234    /**
235     * @inheritDoc
236     */
237    public function appliesToPasswordReset() {
238        return $this->methodReturnsValue( __FUNCTION__, true );
239    }
240
241    /**
242     * @inheritDoc
243     */
244    public function getBy( $wikiId = self::LOCAL ): int {
245        $this->assertWiki( $wikiId );
246        return 0;
247    }
248
249    /**
250     * @inheritDoc
251     */
252    public function getByName() {
253        return '';
254    }
255
256    /**
257     * @inheritDoc
258     */
259    public function getBlocker(): ?UserIdentity {
260        return null;
261    }
262}