MediaWiki master
Pingback.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Installer;
22
27use MWCryptRand;
28use Psr\Log\LoggerInterface;
32use Wikimedia\Timestamp\ConvertibleTimestamp;
33
47class Pingback {
48
52 private const LEGACY_EVENTLOGGING_SCHEMA = 'MediaWikiPingback';
53
60 private const EVENT_PLATFORM_SCHEMA_ID = '/analytics/legacy/mediawikipingback/1.0.0';
61
72 private const EVENT_PLATFORM_STREAM = 'eventlogging_MediaWikiPingback';
73
75 private const EVENT_PLATFORM_EVENT_INTAKE_SERVICE_URI =
76 'https://intake-analytics.wikimedia.org/v1/events?hasty=true';
77
79 protected $logger;
81 protected $config;
83 protected $dbProvider;
85 protected $cache;
87 protected $http;
89 protected $key;
91 protected $eventIntakeUri;
92
100 public function __construct(
105 LoggerInterface $logger,
106 string $eventIntakeUrl = self::EVENT_PLATFORM_EVENT_INTAKE_SERVICE_URI
107 ) {
108 $this->config = $config;
109 $this->dbProvider = $dbProvider;
110 $this->cache = $cache;
111 $this->http = $http;
112 $this->logger = $logger;
113 $this->key = 'Pingback-' . MW_VERSION;
114 $this->eventIntakeUri = $eventIntakeUrl;
115 }
116
123 public function run(): void {
124 if ( !$this->config->get( MainConfigNames::Pingback ) ) {
125 // disabled
126 return;
127 }
128 if ( $this->wasRecentlySent() ) {
129 // already sent recently
130 return;
131 }
132 if ( !$this->acquireLock() ) {
133 $this->logger->debug( __METHOD__ . ": couldn't acquire lock" );
134 return;
135 }
136
137 $data = $this->getData();
138 if ( !$this->postPingback( $data ) ) {
139 $this->logger->warning( __METHOD__ . ": failed to send; check 'http' log channel" );
140 return;
141 }
142
143 // Record the fact that we have sent a pingback for this MediaWiki version,
144 // so we don't submit data multiple times.
145 $dbw = $this->dbProvider->getPrimaryDatabase();
146 $timestamp = ConvertibleTimestamp::time();
147 $dbw->newInsertQueryBuilder()
148 ->insertInto( 'updatelog' )
149 ->row( [ 'ul_key' => $this->key, 'ul_value' => $timestamp ] )
150 ->onDuplicateKeyUpdate()
151 ->uniqueIndexFields( [ 'ul_key' ] )
152 ->set( [ 'ul_value' => $timestamp ] )
153 ->caller( __METHOD__ )->execute();
154 $this->logger->debug( __METHOD__ . ": pingback sent OK ({$this->key})" );
155 }
156
160 private function wasRecentlySent(): bool {
161 $timestamp = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
162 ->select( 'ul_value' )
163 ->from( 'updatelog' )
164 ->where( [ 'ul_key' => $this->key ] )
165 ->caller( __METHOD__ )->fetchField();
166 if ( $timestamp === false ) {
167 return false;
168 }
169 // send heartbeat ping if the last ping was over a month ago
170 if ( ConvertibleTimestamp::time() - (int)$timestamp > 60 * 60 * 24 * 30 ) {
171 return false;
172 }
173 return true;
174 }
175
184 private function acquireLock(): bool {
185 $cacheKey = $this->cache->makeKey( 'pingback', $this->key );
186 if ( !$this->cache->add( $cacheKey, 1, $this->cache::TTL_HOUR ) ) {
187 // throttled
188 return false;
189 }
190
191 $dbw = $this->dbProvider->getPrimaryDatabase();
192 if ( !$dbw->lock( $this->key, __METHOD__, 0 ) ) {
193 // already in progress
194 return false;
195 }
196
197 return true;
198 }
199
209 protected function getData(): array {
210 $wiki = $this->fetchOrInsertId();
211
212 return [
213 'event' => self::getSystemInfo( $this->config ),
214 'schema' => self::LEGACY_EVENTLOGGING_SCHEMA,
215 'wiki' => $wiki,
216
217 // This would be added by
218 // https://gerrit.wikimedia.org/g/mediawiki/extensions/EventLogging/+/d47dbc10455bcb6dbc98a49fa169f75d6131c3da/includes/EventLogging.php#274
219 // onwards.
220 '$schema' => self::EVENT_PLATFORM_SCHEMA_ID,
221 'client_dt' => ConvertibleTimestamp::now( TS_ISO_8601 ),
222
223 // This would be added by
224 // https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/EventLogging/+/d47dbc10455bcb6dbc98a49fa169f75d6131c3da/includes/EventSubmitter/EventBusEventSubmitter.php#81
225 // onwards.
226 'meta' => [
227 'stream' => self::EVENT_PLATFORM_STREAM,
228 ],
229 ];
230 }
231
244 public static function getSystemInfo( Config $config ): array {
245 $event = [
246 'database' => $config->get( MainConfigNames::DBtype ),
247 'MediaWiki' => MW_VERSION,
248 'PHP' => PHP_VERSION,
249 'OS' => PHP_OS . ' ' . php_uname( 'r' ),
250 'arch' => PHP_INT_SIZE === 8 ? 64 : 32,
251 'machine' => php_uname( 'm' ),
252 ];
253
254 if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
255 $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
256 }
257
258 $limit = ini_get( 'memory_limit' );
259 if ( $limit && $limit !== "-1" ) {
260 $event['memoryLimit'] = $limit;
261 }
262
263 return $event;
264 }
265
275 private function fetchOrInsertId(): string {
276 // We've already obtained a primary connection for the lock, and plan to do a write.
277 // But, still prefer reading this immutable value from a replica to reduce load.
278 $id = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
279 ->select( 'ul_value' )
280 ->from( 'updatelog' )
281 ->where( [ 'ul_key' => 'PingBack' ] )
282 ->caller( __METHOD__ )->fetchField();
283 if ( $id !== false ) {
284 return $id;
285 }
286
287 $dbw = $this->dbProvider->getPrimaryDatabase();
288 $id = $dbw->newSelectQueryBuilder()
289 ->select( 'ul_value' )
290 ->from( 'updatelog' )
291 ->where( [ 'ul_key' => 'PingBack' ] )
292 ->caller( __METHOD__ )->fetchField();
293 if ( $id !== false ) {
294 return $id;
295 }
296
297 $id = MWCryptRand::generateHex( 32 );
298 $dbw->newInsertQueryBuilder()
299 ->insertInto( 'updatelog' )
300 ->row( [ 'ul_key' => 'PingBack', 'ul_value' => $id ] )
301 ->caller( __METHOD__ )->execute();
302 return $id;
303 }
304
318 private function postPingback( array $data ): bool {
319 $request = $this->http->create( $this->eventIntakeUri, [
320 'method' => 'POST',
321 'postData' => FormatJson::encode( $data ),
322 ], __METHOD__ );
323 $request->setHeader( 'Content-Type', 'application/json' );
324
325 $result = $request->execute();
326
327 return $result->isGood();
328 }
329}
330
332class_alias( Pingback::class, 'Pingback' );
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:37
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Factory creating MWHttpRequest objects.
Send information about this MediaWiki instance to mediawiki.org.
Definition Pingback.php:47
run()
Maybe send a ping.
Definition Pingback.php:123
__construct(Config $config, IConnectionProvider $dbProvider, BagOStuff $cache, HttpRequestFactory $http, LoggerInterface $logger, string $eventIntakeUrl=self::EVENT_PLATFORM_EVENT_INTAKE_SERVICE_URI)
Definition Pingback.php:100
LoggerInterface $logger
Definition Pingback.php:79
getData()
Get the event to be sent to the server.
Definition Pingback.php:209
IConnectionProvider $dbProvider
Definition Pingback.php:83
HttpRequestFactory $http
Definition Pingback.php:87
string $key
updatelog key (also used as cache/db lock key)
Definition Pingback.php:89
static getSystemInfo(Config $config)
Collect basic data about this MediaWiki installation and return it as an associative array conforming...
Definition Pingback.php:244
JSON formatter wrapper class.
A class containing constants representing the names of configuration variables.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:88
Database error base class.
Definition DBError.php:36
Interface for configuration instances.
Definition Config.php:32
Provide primary and replica IDatabase connections.