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
162 private function wasRecentlySent(): bool {
163 $timestamp = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
164 ->select( 'ul_value' )
165 ->from( 'updatelog' )
166 ->where( [ 'ul_key' => $this->key ] )
167 ->caller( __METHOD__ )->fetchField();
168 if ( $timestamp === false ) {
169 return false;
170 }
171 // send heartbeat ping if the last ping was over a month ago
172 if ( ConvertibleTimestamp::time() - (int)$timestamp > 60 * 60 * 24 * 30 ) {
173 return false;
174 }
175 return true;
176 }
177
186 private function acquireLock(): bool {
187 $cacheKey = $this->cache->makeKey( 'pingback', $this->key );
188 if ( !$this->cache->add( $cacheKey, 1, $this->cache::TTL_HOUR ) ) {
189 // throttled
190 return false;
191 }
192
193 $dbw = $this->dbProvider->getPrimaryDatabase();
194 if ( !$dbw->lock( $this->key, __METHOD__, 0 ) ) {
195 // already in progress
196 return false;
197 }
198
199 return true;
200 }
201
211 protected function getData(): array {
212 $wiki = $this->fetchOrInsertId();
213
214 return [
215 'event' => self::getSystemInfo( $this->config ),
216 'schema' => self::LEGACY_EVENTLOGGING_SCHEMA,
217 'wiki' => $wiki,
218
219 // This would be added by
220 // https://gerrit.wikimedia.org/g/mediawiki/extensions/EventLogging/+/d47dbc10455bcb6dbc98a49fa169f75d6131c3da/includes/EventLogging.php#274
221 // onwards.
222 '$schema' => self::EVENT_PLATFORM_SCHEMA_ID,
223 'client_dt' => ConvertibleTimestamp::now( TS_ISO_8601 ),
224
225 // This would be added by
226 // https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/EventLogging/+/d47dbc10455bcb6dbc98a49fa169f75d6131c3da/includes/EventSubmitter/EventBusEventSubmitter.php#81
227 // onwards.
228 'meta' => [
229 'stream' => self::EVENT_PLATFORM_STREAM,
230 ],
231 ];
232 }
233
246 public static function getSystemInfo( Config $config ): array {
247 $event = [
248 'database' => $config->get( MainConfigNames::DBtype ),
249 'MediaWiki' => MW_VERSION,
250 'PHP' => PHP_VERSION,
251 'OS' => PHP_OS . ' ' . php_uname( 'r' ),
252 'arch' => PHP_INT_SIZE === 8 ? 64 : 32,
253 'machine' => php_uname( 'm' ),
254 ];
255
256 if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
257 $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
258 }
259
260 $limit = ini_get( 'memory_limit' );
261 if ( $limit && $limit !== "-1" ) {
262 $event['memoryLimit'] = $limit;
263 }
264
265 return $event;
266 }
267
277 private function fetchOrInsertId(): string {
278 // We've already obtained a primary connection for the lock, and plan to do a write.
279 // But, still prefer reading this immutable value from a replica to reduce load.
280 $id = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
281 ->select( 'ul_value' )
282 ->from( 'updatelog' )
283 ->where( [ 'ul_key' => 'PingBack' ] )
284 ->caller( __METHOD__ )->fetchField();
285 if ( $id !== false ) {
286 return $id;
287 }
288
289 $dbw = $this->dbProvider->getPrimaryDatabase();
290 $id = $dbw->newSelectQueryBuilder()
291 ->select( 'ul_value' )
292 ->from( 'updatelog' )
293 ->where( [ 'ul_key' => 'PingBack' ] )
294 ->caller( __METHOD__ )->fetchField();
295 if ( $id !== false ) {
296 return $id;
297 }
298
299 $id = MWCryptRand::generateHex( 32 );
300 $dbw->newInsertQueryBuilder()
301 ->insertInto( 'updatelog' )
302 ->row( [ 'ul_key' => 'PingBack', 'ul_value' => $id ] )
303 ->caller( __METHOD__ )->execute();
304 return $id;
305 }
306
320 private function postPingback( array $data ): bool {
321 $request = $this->http->create( $this->eventIntakeUri, [
322 'method' => 'POST',
323 'postData' => FormatJson::encode( $data ),
324 ] );
325 $request->setHeader( 'Content-Type', 'application/json' );
326
327 $result = $request->execute();
328
329 return $result->isGood();
330 }
331}
332
334class_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:211
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:246
JSON formatter wrapper class.
A class containing constants representing the names of configuration variables.
Class representing a cache/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.