Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
GlobalBlockingBlockPurger
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
2 / 2
6
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 purgeExpiredBlocks
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3namespace MediaWiki\Extension\GlobalBlocking\Services;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\MainConfigNames;
7use Wikimedia\Rdbms\IConnectionProvider;
8use Wikimedia\Rdbms\ReadOnlyMode;
9
10/**
11 * Purges expired block rows from the globalblocks and global_block_whitelist tables.
12 *
13 * @since 1.42
14 */
15class GlobalBlockingBlockPurger {
16
17    public const CONSTRUCTOR_OPTIONS = [
18        MainConfigNames::UpdateRowsPerQuery,
19    ];
20
21    private ServiceOptions $options;
22    private GlobalBlockingConnectionProvider $globalBlockingConnectionProvider;
23    private IConnectionProvider $connectionProvider;
24    private ReadOnlyMode $readOnlyMode;
25
26    public function __construct(
27        ServiceOptions $options,
28        GlobalBlockingConnectionProvider $globalBlockingConnectionProvider,
29        IConnectionProvider $connectionProvider,
30        ReadOnlyMode $readOnlyMode
31    ) {
32        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
33        $this->options = $options;
34        $this->globalBlockingConnectionProvider = $globalBlockingConnectionProvider;
35        $this->connectionProvider = $connectionProvider;
36        $this->readOnlyMode = $readOnlyMode;
37    }
38
39    /**
40     * Purge stale block rows.
41     *
42     * This should only be called on a request that performs writes to the database,
43     * such as creating a block, as this is an expensive operation.
44     *
45     * This acts similarly to DatabaseBlockStore::purgeExpiredBlocks, but the purge is not performed
46     * on POSTSEND.
47     */
48    public function purgeExpiredBlocks() {
49        $globaldbw = $this->globalBlockingConnectionProvider->getPrimaryGlobalBlockingDatabase();
50        if ( !$this->readOnlyMode->isReadOnly( $globaldbw->getDomainID() ) ) {
51            $deleteIds = $globaldbw->newSelectQueryBuilder()
52                ->select( 'gb_id' )
53                ->from( 'globalblocks' )
54                ->where( $globaldbw->expr( 'gb_expiry', '<=', $globaldbw->timestamp() ) )
55                ->limit( $this->options->get( MainConfigNames::UpdateRowsPerQuery ) )
56                ->caller( __METHOD__ )
57                ->fetchFieldValues();
58            if ( $deleteIds !== [] ) {
59                $deleteIds = array_map( 'intval', $deleteIds );
60                $globaldbw->newDeleteQueryBuilder()
61                    ->deleteFrom( 'globalblocks' )
62                    ->where( [ 'gb_id' => $deleteIds ] )
63                    ->caller( __METHOD__ )
64                    ->execute();
65            }
66        }
67
68        $dbw = $this->connectionProvider->getPrimaryDatabase();
69        if ( !$this->readOnlyMode->isReadOnly() ) {
70            // Purge the global_block_whitelist table.
71            // We can't be perfect about this without an expensive check on the primary database
72            // for every single global block. However, we can be clever about it and store
73            // the expiry of global blocks in the global_block_whitelist table.
74            // That way, most blocks will fall out of the table naturally when they expire.
75            $deleteWhitelistIds = $dbw->newSelectQueryBuilder()
76                ->select( 'gbw_id' )
77                ->from( 'global_block_whitelist' )
78                ->where( $dbw->expr( 'gbw_expiry', '<=', $dbw->timestamp() ) )
79                ->limit( $this->options->get( MainConfigNames::UpdateRowsPerQuery ) )
80                ->caller( __METHOD__ )
81                ->fetchFieldValues();
82            if ( $deleteWhitelistIds !== [] ) {
83                $deleteWhitelistIds = array_map( 'intval', $deleteWhitelistIds );
84                $dbw->newDeleteQueryBuilder()
85                    ->deleteFrom( 'global_block_whitelist' )
86                    ->where( [ 'gbw_id' => $deleteWhitelistIds ] )
87                    ->caller( __METHOD__ )
88                    ->execute();
89            }
90        }
91    }
92}