Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ConsequencesLookup
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
3 / 3
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getConsequencesForFilters
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 loadConsequencesFromDB
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Consequences;
4
5use MediaWiki\Extension\AbuseFilter\CentralDBManager;
6use MediaWiki\Extension\AbuseFilter\GlobalNameUtils;
7use Psr\Log\LoggerInterface;
8use Wikimedia\Rdbms\IReadableDatabase;
9use Wikimedia\Rdbms\LBFactory;
10
11/**
12 * Class for retrieving actions and parameters from the database
13 * @todo Can we better integrate this with FilterLookup?
14 */
15class ConsequencesLookup {
16    public const SERVICE_NAME = 'AbuseFilterConsequencesLookup';
17
18    /** @var LBFactory */
19    private $lbFactory;
20    /** @var CentralDBManager */
21    private $centralDBManager;
22    /** @var ConsequencesRegistry */
23    private $consequencesRegistry;
24    /** @var LoggerInterface */
25    private $logger;
26
27    /**
28     * @param LBFactory $lbFactory
29     * @param CentralDBManager $centralDBManager
30     * @param ConsequencesRegistry $consequencesRegistry
31     * @param LoggerInterface $logger
32     */
33    public function __construct(
34        LBFactory $lbFactory,
35        CentralDBManager $centralDBManager,
36        ConsequencesRegistry $consequencesRegistry,
37        LoggerInterface $logger
38    ) {
39        $this->lbFactory = $lbFactory;
40        $this->centralDBManager = $centralDBManager;
41        $this->consequencesRegistry = $consequencesRegistry;
42        $this->logger = $logger;
43    }
44
45    /**
46     * @param array<int|string> $filters
47     * @return array[][]
48     */
49    public function getConsequencesForFilters( array $filters ): array {
50        $globalFilters = [];
51        $localFilters = [];
52
53        foreach ( $filters as $filter ) {
54            [ $filterID, $global ] = GlobalNameUtils::splitGlobalName( $filter );
55
56            if ( $global ) {
57                $globalFilters[] = $filterID;
58            } else {
59                $localFilters[] = (int)$filter;
60            }
61        }
62
63        // Load local filter info
64        $dbr = $this->lbFactory->getReplicaDatabase();
65        // Retrieve the consequences.
66        $consequences = [];
67
68        if ( count( $localFilters ) ) {
69            $consequences = $this->loadConsequencesFromDB( $dbr, $localFilters );
70        }
71
72        if ( count( $globalFilters ) ) {
73            $consequences += $this->loadConsequencesFromDB(
74                $this->centralDBManager->getConnection( DB_REPLICA ),
75                $globalFilters,
76                GlobalNameUtils::GLOBAL_FILTER_PREFIX
77            );
78        }
79
80        return $consequences;
81    }
82
83    /**
84     * @param IReadableDatabase $dbr
85     * @param int[] $filters
86     * @param string $prefix
87     * @return array[][]
88     */
89    private function loadConsequencesFromDB( IReadableDatabase $dbr, array $filters, string $prefix = '' ): array {
90        $actionsByFilter = [];
91        foreach ( $filters as $filter ) {
92            $actionsByFilter[$prefix . $filter] = [];
93        }
94
95        $res = $dbr->select(
96            [ 'abuse_filter_action', 'abuse_filter' ],
97            '*',
98            [ 'af_id' => $filters ],
99            __METHOD__,
100            [],
101            [ 'abuse_filter_action' => [ 'LEFT JOIN', 'afa_filter=af_id' ] ]
102        );
103
104        $dangerousActions = $this->consequencesRegistry->getDangerousActionNames();
105        // Categorise consequences by filter.
106        foreach ( $res as $row ) {
107            if ( $row->af_throttled
108                && in_array( $row->afa_consequence, $dangerousActions )
109            ) {
110                // Don't do the action, just log
111                $this->logger->info(
112                    'Filter {filter_id} is throttled, skipping action: {action}',
113                    [
114                        'filter_id' => $row->af_id,
115                        'action' => $row->afa_consequence
116                    ]
117                );
118            } elseif ( $row->afa_filter !== $row->af_id ) {
119                // We probably got a NULL, as it's a LEFT JOIN. Don't add it.
120                continue;
121            } else {
122                $actionsByFilter[$prefix . $row->afa_filter][$row->afa_consequence] =
123                    $row->afa_parameters !== '' ? explode( "\n", $row->afa_parameters ) : [];
124            }
125        }
126
127        return $actionsByFilter;
128    }
129
130}