Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
42 / 42 |
|
100.00% |
6 / 6 |
CRAP | |
100.00% |
1 / 1 |
| EditRevUpdater | |
100.00% |
42 / 42 |
|
100.00% |
6 / 6 |
16 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setLastEditPage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| clearLastEditPage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setLogIdsForTarget | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
| updateRev | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
9 | |||
| getCacheKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace MediaWiki\Extension\AbuseFilter; |
| 4 | |
| 5 | use InvalidArgumentException; |
| 6 | use MediaWiki\Linker\LinkTarget; |
| 7 | use MediaWiki\Page\WikiPage; |
| 8 | use MediaWiki\Revision\RevisionLookup; |
| 9 | use MediaWiki\Revision\RevisionRecord; |
| 10 | use Wikimedia\Rdbms\LBFactory; |
| 11 | |
| 12 | /** |
| 13 | * This service allows "linking" the edit filter hook and the page save hook |
| 14 | */ |
| 15 | class EditRevUpdater { |
| 16 | public const SERVICE_NAME = 'AbuseFilterEditRevUpdater'; |
| 17 | |
| 18 | /** @var WikiPage|null */ |
| 19 | private $wikiPage; |
| 20 | /** |
| 21 | * @var array<string,array{local:int[],global:int[]}> IDs of logged filters |
| 22 | * like [ page title => [ 'local' => [ids], 'global' => [ids] ] ]. |
| 23 | */ |
| 24 | private $logIds = []; |
| 25 | |
| 26 | public function __construct( |
| 27 | private readonly CentralDBManager $centralDBManager, |
| 28 | private readonly RevisionLookup $revisionLookup, |
| 29 | private readonly LBFactory $lbFactory, |
| 30 | private readonly string $wikiID |
| 31 | ) { |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * Set the WikiPage object used for the ongoing edit |
| 36 | */ |
| 37 | public function setLastEditPage( WikiPage $page ): void { |
| 38 | $this->wikiPage = $page; |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Clear the WikiPage object used for the ongoing edit |
| 43 | */ |
| 44 | public function clearLastEditPage(): void { |
| 45 | $this->wikiPage = null; |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * @param LinkTarget $target |
| 50 | * @param array{local:int[],global:int[]} $logIds |
| 51 | */ |
| 52 | public function setLogIdsForTarget( LinkTarget $target, array $logIds ): void { |
| 53 | if ( count( $logIds ) !== 2 || array_diff( array_keys( $logIds ), [ 'local', 'global' ] ) ) { |
| 54 | throw new InvalidArgumentException( 'Wrong keys; got: ' . implode( ', ', array_keys( $logIds ) ) ); |
| 55 | } |
| 56 | $key = $this->getCacheKey( $target ); |
| 57 | $this->logIds[$key] = $logIds; |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * @param WikiPage $wikiPage |
| 62 | * @param RevisionRecord $revisionRecord |
| 63 | * @return bool Whether the DB was updated |
| 64 | */ |
| 65 | public function updateRev( WikiPage $wikiPage, RevisionRecord $revisionRecord ): bool { |
| 66 | $key = $this->getCacheKey( $wikiPage->getTitle() ); |
| 67 | if ( |
| 68 | !isset( $this->logIds[ $key ] ) || |
| 69 | $this->wikiPage === null || |
| 70 | !$this->wikiPage->isSamePageAs( $wikiPage ) |
| 71 | ) { |
| 72 | // This isn't the edit $this->logIds was set for |
| 73 | $this->logIds = []; |
| 74 | return false; |
| 75 | } |
| 76 | |
| 77 | // Ignore null edit. |
| 78 | $parentRevId = $revisionRecord->getParentId(); |
| 79 | if ( $parentRevId !== null ) { |
| 80 | $parentRev = $this->revisionLookup->getRevisionById( $parentRevId ); |
| 81 | if ( $parentRev && $revisionRecord->hasSameContent( $parentRev ) ) { |
| 82 | $this->logIds = []; |
| 83 | return false; |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | $this->clearLastEditPage(); |
| 88 | |
| 89 | $ret = false; |
| 90 | $logs = $this->logIds[ $key ]; |
| 91 | if ( $logs[ 'local' ] ) { |
| 92 | $dbw = $this->lbFactory->getPrimaryDatabase(); |
| 93 | $dbw->newUpdateQueryBuilder() |
| 94 | ->update( 'abuse_filter_log' ) |
| 95 | ->set( [ 'afl_rev_id' => $revisionRecord->getId() ] ) |
| 96 | ->where( [ 'afl_id' => $logs['local'] ] ) |
| 97 | ->caller( __METHOD__ ) |
| 98 | ->execute(); |
| 99 | $ret = true; |
| 100 | } |
| 101 | |
| 102 | if ( $logs[ 'global' ] ) { |
| 103 | $fdb = $this->centralDBManager->getConnection( DB_PRIMARY ); |
| 104 | $fdb->newUpdateQueryBuilder() |
| 105 | ->update( 'abuse_filter_log' ) |
| 106 | ->set( [ 'afl_rev_id' => $revisionRecord->getId() ] ) |
| 107 | ->where( [ 'afl_id' => $logs['global'], 'afl_wiki' => $this->wikiID ] ) |
| 108 | ->caller( __METHOD__ ) |
| 109 | ->execute(); |
| 110 | $ret = true; |
| 111 | } |
| 112 | return $ret; |
| 113 | } |
| 114 | |
| 115 | private function getCacheKey( LinkTarget $target ): string { |
| 116 | return $target->getNamespace() . '|' . $target->getText(); |
| 117 | } |
| 118 | } |