Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 71 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 71 |
|
0.00% |
0 / 9 |
506 | |
0.00% |
0 / 1 |
onLinksUpdateComplete | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
onPageMoveComplete | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
onArticleDeleteComplete | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onFileDeleteComplete | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
onFileUndeleteComplete | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
onUploadComplete | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
fileUpdatesCreatePurgeJobs | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getGlobalUsage | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
onWgQueryPages | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * GlobalUsage hooks for updating globalimagelinks table. |
4 | * |
5 | * UI hooks in SpecialGlobalUsage. |
6 | */ |
7 | |
8 | namespace MediaWiki\Extension\GlobalUsage; |
9 | |
10 | use FileRepo; |
11 | use LocalFile; |
12 | use ManualLogEntry; |
13 | use MediaWiki\Content\Content; |
14 | use MediaWiki\Deferred\LinksUpdate\LinksUpdate; |
15 | use MediaWiki\Hook\FileDeleteCompleteHook; |
16 | use MediaWiki\Hook\FileUndeleteCompleteHook; |
17 | use MediaWiki\Hook\LinksUpdateCompleteHook; |
18 | use MediaWiki\Hook\PageMoveCompleteHook; |
19 | use MediaWiki\Hook\UploadCompleteHook; |
20 | use MediaWiki\Linker\LinkTarget; |
21 | use MediaWiki\MediaWikiServices; |
22 | use MediaWiki\Page\Hook\ArticleDeleteCompleteHook; |
23 | use MediaWiki\Revision\RevisionRecord; |
24 | use MediaWiki\SpecialPage\Hook\WgQueryPagesHook; |
25 | use MediaWiki\Title\Title; |
26 | use MediaWiki\User\User; |
27 | use MediaWiki\User\UserIdentity; |
28 | use MediaWiki\WikiMap\WikiMap; |
29 | use UploadBase; |
30 | use WikiFilePage; |
31 | use Wikimedia\Rdbms\IDBAccessObject; |
32 | use WikiPage; |
33 | |
34 | class Hooks implements |
35 | LinksUpdateCompleteHook, |
36 | ArticleDeleteCompleteHook, |
37 | FileDeleteCompleteHook, |
38 | FileUndeleteCompleteHook, |
39 | UploadCompleteHook, |
40 | PageMoveCompleteHook, |
41 | WgQueryPagesHook |
42 | { |
43 | /** |
44 | * Hook to LinksUpdateComplete |
45 | * Deletes old links from usage table and insert new ones. |
46 | * @param LinksUpdate $linksUpdater |
47 | * @param int|null $ticket |
48 | */ |
49 | public function onLinksUpdateComplete( $linksUpdater, $ticket ) { |
50 | $title = $linksUpdater->getTitle(); |
51 | |
52 | // Create a list of locally existing images (DB keys) |
53 | $images = array_keys( $linksUpdater->getImages() ); |
54 | |
55 | $localFiles = []; |
56 | $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo(); |
57 | $imagesInfo = $repo->findFiles( $images, FileRepo::NAME_AND_TIME_ONLY ); |
58 | foreach ( $imagesInfo as $dbKey => $info ) { |
59 | '@phan-var array $info'; |
60 | $localFiles[] = $dbKey; |
61 | if ( $dbKey !== $info['title'] ) { // redirect |
62 | $localFiles[] = $info['title']; |
63 | } |
64 | } |
65 | $localFiles = array_values( array_unique( $localFiles ) ); |
66 | |
67 | $missingFiles = array_diff( $images, $localFiles ); |
68 | |
69 | $gu = self::getGlobalUsage(); |
70 | $articleId = $title->getArticleID( IDBAccessObject::READ_NORMAL ); |
71 | $existing = $gu->getLinksFromPage( $articleId ); |
72 | |
73 | // Calculate changes |
74 | $added = array_diff( $missingFiles, $existing ); |
75 | $removed = array_diff( $existing, $missingFiles ); |
76 | |
77 | // Add new usages and delete removed |
78 | $gu->insertLinks( $title, $added, IDBAccessObject::READ_LATEST, $ticket ); |
79 | if ( $removed ) { |
80 | $gu->deleteLinksFromPage( $articleId, $removed, $ticket ); |
81 | } |
82 | } |
83 | |
84 | /** |
85 | * Hook to PageMoveComplete |
86 | * Sets the page title in usage table to the new name. |
87 | * For shared file moves, purges all pages in the wiki farm that use the files. |
88 | * @param LinkTarget $ot |
89 | * @param LinkTarget $nt |
90 | * @param UserIdentity $user |
91 | * @param int $pageid |
92 | * @param int $redirid |
93 | * @param string $reason |
94 | * @param RevisionRecord $revisionRecord |
95 | */ |
96 | public function onPageMoveComplete( |
97 | $ot, |
98 | $nt, |
99 | $user, |
100 | $pageid, |
101 | $redirid, |
102 | $reason, |
103 | $revisionRecord |
104 | ) { |
105 | $ot = Title::newFromLinkTarget( $ot ); |
106 | $nt = Title::newFromLinkTarget( $nt ); |
107 | |
108 | $gu = self::getGlobalUsage(); |
109 | $gu->moveTo( $pageid, $nt ); |
110 | |
111 | if ( self::fileUpdatesCreatePurgeJobs() ) { |
112 | $jobs = []; |
113 | if ( $ot->inNamespace( NS_FILE ) ) { |
114 | $jobs[] = new GlobalUsageCachePurgeJob( $ot, [] ); |
115 | } |
116 | if ( $nt->inNamespace( NS_FILE ) ) { |
117 | $jobs[] = new GlobalUsageCachePurgeJob( $nt, [] ); |
118 | } |
119 | // Push the jobs after DB commit but cancel on rollback |
120 | MediaWikiServices::getInstance() |
121 | ->getConnectionProvider() |
122 | ->getPrimaryDatabase() |
123 | ->onTransactionCommitOrIdle( static function () use ( $jobs ) { |
124 | MediaWikiServices::getInstance()->getJobQueueGroupFactory()->makeJobQueueGroup()->lazyPush( $jobs ); |
125 | }, __METHOD__ ); |
126 | } |
127 | } |
128 | |
129 | /** |
130 | * Hook to ArticleDeleteComplete |
131 | * Deletes entries from usage table. |
132 | * @param WikiPage $article |
133 | * @param User $user |
134 | * @param string $reason |
135 | * @param int $id |
136 | * @param Content|null $content |
137 | * @param ManualLogEntry $logEntry |
138 | * @param int $archivedRevisionCount |
139 | */ |
140 | public function onArticleDeleteComplete( $article, $user, $reason, $id, |
141 | $content, $logEntry, $archivedRevisionCount |
142 | ) { |
143 | $gu = self::getGlobalUsage(); |
144 | // @FIXME: avoid making DB replication lag |
145 | $gu->deleteLinksFromPage( $id ); |
146 | } |
147 | |
148 | /** |
149 | * Hook to FileDeleteComplete |
150 | * Copies the local link table to the global. |
151 | * Purges all pages in the wiki farm that use the file if it is a shared repo file. |
152 | * @param LocalFile $file |
153 | * @param string|null $oldimage |
154 | * @param WikiFilePage|null $article |
155 | * @param User $user |
156 | * @param string $reason |
157 | */ |
158 | public function onFileDeleteComplete( $file, $oldimage, $article, $user, $reason ) { |
159 | if ( !$oldimage ) { |
160 | if ( !GlobalUsage::onSharedRepo() ) { |
161 | $gu = self::getGlobalUsage(); |
162 | $gu->copyLocalImagelinks( |
163 | $file->getTitle(), |
164 | MediaWikiServices::getInstance() |
165 | ->getConnectionProvider() |
166 | ->getPrimaryDatabase() |
167 | ); |
168 | } |
169 | |
170 | if ( self::fileUpdatesCreatePurgeJobs() ) { |
171 | $job = new GlobalUsageCachePurgeJob( $file->getTitle(), [] ); |
172 | MediaWikiServices::getInstance()->getJobQueueGroupFactory()->makeJobQueueGroup()->push( $job ); |
173 | } |
174 | } |
175 | } |
176 | |
177 | /** |
178 | * Hook to FileUndeleteComplete |
179 | * Deletes the file from the global link table. |
180 | * Purges all pages in the wiki farm that use the file if it is a shared repo file. |
181 | * @param Title $title |
182 | * @param array $versions |
183 | * @param User $user |
184 | * @param string $reason |
185 | */ |
186 | public function onFileUndeleteComplete( $title, $versions, $user, $reason ) { |
187 | $gu = self::getGlobalUsage(); |
188 | $gu->deleteLinksToFile( $title ); |
189 | |
190 | if ( self::fileUpdatesCreatePurgeJobs() ) { |
191 | $job = new GlobalUsageCachePurgeJob( $title, [] ); |
192 | MediaWikiServices::getInstance()->getJobQueueGroupFactory()->makeJobQueueGroup()->push( $job ); |
193 | } |
194 | } |
195 | |
196 | /** |
197 | * Hook to UploadComplete |
198 | * Deletes the file from the global link table. |
199 | * Purges all pages in the wiki farm that use the file if it is a shared repo file. |
200 | * @param UploadBase $upload |
201 | */ |
202 | public function onUploadComplete( $upload ) { |
203 | $gu = self::getGlobalUsage(); |
204 | $gu->deleteLinksToFile( $upload->getTitle() ); |
205 | |
206 | if ( self::fileUpdatesCreatePurgeJobs() ) { |
207 | $job = new GlobalUsageCachePurgeJob( $upload->getTitle(), [] ); |
208 | MediaWikiServices::getInstance()->getJobQueueGroupFactory()->makeJobQueueGroup()->push( $job ); |
209 | } |
210 | } |
211 | |
212 | /** |
213 | * |
214 | * Check if file updates on this wiki should cause backlink page purge jobs |
215 | * |
216 | * @return bool |
217 | */ |
218 | private static function fileUpdatesCreatePurgeJobs() { |
219 | global $wgGlobalUsageSharedRepoWiki, $wgGlobalUsagePurgeBacklinks; |
220 | |
221 | return ( $wgGlobalUsagePurgeBacklinks && WikiMap::getCurrentWikiId() === $wgGlobalUsageSharedRepoWiki ); |
222 | } |
223 | |
224 | /** |
225 | * Initializes a GlobalUsage object for the current wiki. |
226 | * |
227 | * @return GlobalUsage |
228 | */ |
229 | private static function getGlobalUsage() { |
230 | return new GlobalUsage( |
231 | WikiMap::getCurrentWikiId(), |
232 | GlobalUsage::getGlobalDB( DB_PRIMARY ), |
233 | GlobalUsage::getGlobalDB( DB_REPLICA ) |
234 | ); |
235 | } |
236 | |
237 | public function onWgQueryPages( &$queryPages ) { |
238 | $queryPages[] = [ 'SpecialMostGloballyLinkedFiles', 'MostGloballyLinkedFiles' ]; |
239 | $queryPages[] = [ 'SpecialGloballyWantedFiles', 'GloballyWantedFiles' ]; |
240 | if ( GlobalUsage::onSharedRepo() ) { |
241 | $queryPages[] = [ 'SpecialGloballyUnusedFiles', 'GloballyUnusedFiles' ]; |
242 | } |
243 | } |
244 | } |