Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.61% covered (warning)
67.61%
48 / 71
36.84% covered (danger)
36.84%
7 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
CompositeBlock
67.61% covered (warning)
67.61%
48 / 71
36.84% covered (danger)
36.84%
7 / 19
90.71
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%
13 / 13
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->setHideBlock( $this->propHasValue( 'hideBlock', true ) );
72        $this->isHardblock( $this->propHasValue( 'isHardblock', true ) );
73        $this->isSitewide( $this->propHasValue( 'isSitewide', true ) );
74        $this->isEmailBlocked( $this->propHasValue( 'blockEmail', true ) );
75        $this->isCreateAccountBlocked( $this->propHasValue( 'blockCreateAccount', true ) );
76        $this->isUsertalkEditAllowed( !$this->propHasValue( 'allowUsertalk', false ) );
77    }
78
79    /**
80     * Determine whether any original blocks have a particular property set to a
81     * particular value.
82     *
83     * @param string $prop
84     * @param mixed $value
85     * @return bool At least one block has the property set to the value
86     */
87    private function propHasValue( $prop, $value ) {
88        foreach ( $this->originalBlocks as $block ) {
89            if ( $block->$prop == $value ) {
90                return true;
91            }
92        }
93        return false;
94    }
95
96    /**
97     * Determine whether any original blocks have a particular method returning a
98     * particular value.
99     *
100     * @param string $method
101     * @param mixed $value
102     * @param mixed ...$params
103     * @return bool At least one block has the method returning the value
104     */
105    private function methodReturnsValue( $method, $value, ...$params ) {
106        foreach ( $this->originalBlocks as $block ) {
107            if ( $block->$method( ...$params ) == $value ) {
108                return true;
109            }
110        }
111        return false;
112    }
113
114    /**
115     * Get the original blocks from which this block is composed
116     *
117     * @since 1.34
118     * @return AbstractBlock[]
119     */
120    public function getOriginalBlocks() {
121        return $this->originalBlocks;
122    }
123
124    /**
125     * Create a clone of the object with the original blocks array set to
126     * something else.
127     *
128     * @since 1.42
129     * @param AbstractBlock[] $blocks
130     * @return self
131     */
132    public function withOriginalBlocks( array $blocks ) {
133        $clone = clone $this;
134        $clone->originalBlocks = $blocks;
135        return $clone;
136    }
137
138    public function toArray(): array {
139        return $this->originalBlocks;
140    }
141
142    /**
143     * @inheritDoc
144     */
145    public function getTimestamp(): string {
146        $minStart = null;
147        foreach ( $this->originalBlocks as $block ) {
148            $startTime = $block->getTimestamp();
149            if ( $minStart === null || $startTime === '' || $startTime < $minStart ) {
150                $minStart = $startTime;
151            }
152        }
153        return $minStart ?? '';
154    }
155
156    /**
157     * @inheritDoc
158     */
159    public function getExpiry(): string {
160        $maxExpiry = null;
161        foreach ( $this->originalBlocks as $block ) {
162            $expiry = $block->getExpiry();
163            if ( $maxExpiry === null || $expiry === '' || $expiry > $maxExpiry ) {
164                $maxExpiry = $expiry;
165            }
166        }
167        return $maxExpiry ?? '';
168    }
169
170    /**
171     * @inheritDoc
172     */
173    public function getIdentifier( $wikiId = self::LOCAL ) {
174        $identifier = [];
175        foreach ( $this->originalBlocks as $block ) {
176            $identifier[] = $block->getIdentifier( $wikiId );
177        }
178        return $identifier;
179    }
180
181    /**
182     * @inheritDoc
183     *
184     * Determines whether the CompositeBlock applies to a right by checking
185     * whether the original blocks apply to that right. Each block can report
186     * true (applies), false (does not apply) or null (unsure). Then:
187     * - If any original blocks apply, this block applies
188     * - If no original blocks apply but any are unsure, this block is unsure
189     * - If all blocks do not apply, this block does not apply
190     */
191    public function appliesToRight( $right ) {
192        $isUnsure = false;
193
194        foreach ( $this->originalBlocks as $block ) {
195            $appliesToRight = $block->appliesToRight( $right );
196
197            if ( $appliesToRight ) {
198                return true;
199            } elseif ( $appliesToRight === null ) {
200                $isUnsure = true;
201            }
202        }
203
204        return $isUnsure ? null : false;
205    }
206
207    /**
208     * @inheritDoc
209     */
210    public function appliesToUsertalk( ?Title $usertalk = null ): bool {
211        return $this->methodReturnsValue( __FUNCTION__, true, $usertalk );
212    }
213
214    /**
215     * @inheritDoc
216     */
217    public function appliesToTitle( Title $title ) {
218        return $this->methodReturnsValue( __FUNCTION__, true, $title );
219    }
220
221    /**
222     * @inheritDoc
223     */
224    public function appliesToNamespace( $ns ) {
225        return $this->methodReturnsValue( __FUNCTION__, true, $ns );
226    }
227
228    /**
229     * @inheritDoc
230     */
231    public function appliesToPage( $pageId ) {
232        return $this->methodReturnsValue( __FUNCTION__, true, $pageId );
233    }
234
235    /**
236     * @inheritDoc
237     */
238    public function appliesToPasswordReset() {
239        return $this->methodReturnsValue( __FUNCTION__, true );
240    }
241
242    /**
243     * @inheritDoc
244     */
245    public function getBy( $wikiId = self::LOCAL ): int {
246        $this->assertWiki( $wikiId );
247        return 0;
248    }
249
250    /**
251     * @inheritDoc
252     */
253    public function getByName() {
254        return '';
255    }
256
257    /**
258     * @inheritDoc
259     */
260    public function getBlocker(): ?UserIdentity {
261        return null;
262    }
263}