Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
EditStashCache
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
5 / 5
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 store
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 seek
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 logCache
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 getStashKey
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter;
4
5use InvalidArgumentException;
6use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
7use MediaWiki\Extension\AbuseFilter\Variables\VariablesManager;
8use MediaWiki\Linker\LinkTarget;
9use Psr\Log\LoggerInterface;
10use Wikimedia\ObjectCache\BagOStuff;
11use Wikimedia\Stats\StatsFactory;
12
13/**
14 * Wrapper around cache for storing and retrieving data from edit stash
15 */
16class EditStashCache {
17
18    private const CACHE_VERSION = 'v5';
19
20    public function __construct(
21        private readonly BagOStuff $cache,
22        private readonly StatsFactory $statsFactory,
23        private readonly VariablesManager $variablesManager,
24        private readonly LoggerInterface $logger,
25        private readonly LinkTarget $target,
26        private readonly string $group
27    ) {
28    }
29
30    /**
31     * @param VariableHolder $vars For creating the key
32     * @param array $data Data to store
33     */
34    public function store( VariableHolder $vars, array $data ): void {
35        $key = $this->getStashKey( $vars );
36        $this->cache->set( $key, $data, BagOStuff::TTL_MINUTE );
37        $this->logCache( 'store', $key );
38    }
39
40    /**
41     * Search the cache to find data for a previous execution done for the current edit.
42     *
43     * @param VariableHolder $vars For creating the key
44     * @return false|array False on cache miss, the array with data otherwise
45     */
46    public function seek( VariableHolder $vars ) {
47        $key = $this->getStashKey( $vars );
48        $value = $this->cache->get( $key );
49        $status = $value !== false ? 'hit' : 'miss';
50        $this->logCache( $status, $key );
51        return $value;
52    }
53
54    /**
55     * Log cache operations related to stashed edits, i.e. store, hit and miss
56     *
57     * @param string $type Either 'store', 'hit' or 'miss'
58     * @param string $key The cache key used
59     * @throws InvalidArgumentException
60     */
61    private function logCache( string $type, string $key ): void {
62        if ( !in_array( $type, [ 'store', 'hit', 'miss' ] ) ) {
63            // @codeCoverageIgnoreStart
64            throw new InvalidArgumentException( '$type must be either "store", "hit" or "miss"' );
65            // @codeCoverageIgnoreEnd
66        }
67        $this->logger->debug(
68            __METHOD__ . ": cache {logtype} for '{target}' (key {key}).",
69            [ 'logtype' => $type, 'target' => $this->target, 'key' => $key ]
70        );
71        $this->statsFactory->withComponent( 'AbuseFilter' )
72            ->getCounter( 'check_stash_total' )
73            ->setLabel( 'result', $type )
74            ->increment();
75    }
76
77    /**
78     * Get the stash key for the current variables
79     *
80     * @param VariableHolder $vars
81     * @return string
82     */
83    private function getStashKey( VariableHolder $vars ): string {
84        $inputVars = $this->variablesManager->exportNonLazyVars( $vars );
85        // Exclude noisy fields that have superficial changes
86        $excludedVars = [
87            'old_html' => true,
88            'new_html' => true,
89            'user_age' => true,
90            'timestamp' => true,
91            'page_age' => true,
92            'page_last_edit_age' => true,
93        ];
94
95        $inputVars = array_diff_key( $inputVars, $excludedVars );
96        ksort( $inputVars );
97        $hash = md5( serialize( $inputVars ) );
98
99        return $this->cache->makeKey(
100            'abusefilter-checkstash',
101            $this->group,
102            $hash,
103            self::CACHE_VERSION
104        );
105    }
106
107}