MediaWiki  master
Pingback.php
Go to the documentation of this file.
1 <?php
26 use Psr\Log\LoggerInterface;
29 use Wikimedia\Timestamp\ConvertibleTimestamp;
30 
44 class Pingback {
49  private const SCHEMA_REV = 15781718;
50 
52  protected $logger;
54  protected $config;
56  protected $lb;
58  protected $cache;
60  protected $http;
62  protected $key;
63 
71  public function __construct(
76  LoggerInterface $logger
77  ) {
78  $this->config = $config;
79  $this->lb = $lb;
80  $this->cache = $cache;
81  $this->http = $http;
82  $this->logger = $logger;
83  $this->key = 'Pingback-' . MW_VERSION;
84  }
85 
92  public function run() : void {
93  if ( !$this->config->get( 'Pingback' ) ) {
94  // disabled
95  return;
96  }
97  if ( $this->wasRecentlySent() ) {
98  // already sent recently
99  return;
100  }
101  if ( !$this->acquireLock() ) {
102  $this->logger->debug( __METHOD__ . ": couldn't acquire lock" );
103  return;
104  }
105 
106  $data = $this->getData();
107  if ( !$this->postPingback( $data ) ) {
108  $this->logger->warning( __METHOD__ . ": failed to send; check 'http' log channel" );
109  return;
110  }
111 
112  $this->markSent();
113  $this->logger->debug( __METHOD__ . ": pingback sent OK ({$this->key})" );
114  }
115 
121  private function wasRecentlySent() : bool {
122  $dbr = $this->lb->getConnectionRef( DB_REPLICA );
123  $timestamp = $dbr->selectField(
124  'updatelog',
125  'ul_value',
126  [ 'ul_key' => $this->key ],
127  __METHOD__
128  );
129  if ( $timestamp === false ) {
130  return false;
131  }
132  // send heartbeat ping if last ping was over a month ago
133  if ( ConvertibleTimestamp::time() - (int)$timestamp > 60 * 60 * 24 * 30 ) {
134  return false;
135  }
136  return true;
137  }
138 
147  private function acquireLock() : bool {
148  $cacheKey = $this->cache->makeKey( 'pingback', $this->key );
149  if ( !$this->cache->add( $cacheKey, 1, $this->cache::TTL_HOUR ) ) {
150  // throttled
151  return false;
152  }
153 
154  $dbw = $this->lb->getConnectionRef( DB_MASTER );
155  if ( !$dbw->lock( $this->key, __METHOD__, 0 ) ) {
156  // already in progress
157  return false;
158  }
159 
160  return true;
161  }
162 
169  protected function getData() : array {
170  return [
171  'schema' => 'MediaWikiPingback',
172  'revision' => self::SCHEMA_REV,
173  'wiki' => $this->fetchOrInsertId(),
174  'event' => self::getSystemInfo( $this->config ),
175  ];
176  }
177 
190  public static function getSystemInfo( Config $config ) : array {
191  $event = [
192  'database' => $config->get( 'DBtype' ),
193  'MediaWiki' => MW_VERSION,
194  'PHP' => PHP_VERSION,
195  'OS' => PHP_OS . ' ' . php_uname( 'r' ),
196  'arch' => PHP_INT_SIZE === 8 ? 64 : 32,
197  'machine' => php_uname( 'm' ),
198  ];
199 
200  if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
201  $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
202  }
203 
204  $limit = ini_get( 'memory_limit' );
205  if ( $limit && $limit !== "-1" ) {
206  $event['memoryLimit'] = $limit;
207  }
208 
209  return $event;
210  }
211 
221  private function fetchOrInsertId() : string {
222  // We've already obtained a master connection for the lock, and plan to do a write.
223  // But, still prefer reading this immutable value from a replica to reduce load.
224  $dbr = $this->lb->getConnectionRef( DB_REPLICA );
225  $id = $dbr->selectField( 'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ], __METHOD__ );
226  if ( $id !== false ) {
227  return $id;
228  }
229 
230  $dbw = $this->lb->getConnectionRef( DB_MASTER );
231  $id = $dbw->selectField( 'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ], __METHOD__ );
232  if ( $id !== false ) {
233  return $id;
234  }
235 
236  $id = MWCryptRand::generateHex( 32 );
237  $dbw->insert(
238  'updatelog',
239  [ 'ul_key' => 'PingBack', 'ul_value' => $id ],
240  __METHOD__
241  );
242  return $id;
243  }
244 
261  private function postPingback( array $data ) : bool {
262  $json = FormatJson::encode( $data );
263  $queryString = rawurlencode( str_replace( ' ', '\u0020', $json ) ) . ';';
264  $url = 'https://www.mediawiki.org/beacon/event?' . $queryString;
265  return $this->http->post( $url, [], __METHOD__ ) !== null;
266  }
267 
274  private function markSent() : void {
275  $dbw = $this->lb->getConnectionRef( DB_MASTER );
276  $timestamp = ConvertibleTimestamp::time();
277  $dbw->upsert(
278  'updatelog',
279  [ 'ul_key' => $this->key, 'ul_value' => $timestamp ],
280  'ul_key',
281  [ 'ul_value' => $timestamp ],
282  __METHOD__
283  );
284  }
285 
290  public static function schedulePingback() : void {
291  $config = MediaWikiServices::getInstance()->getMainConfig();
292  if ( !$config->get( 'Pingback' ) ) {
293  // Fault tolerance:
294  // Pingback is unusual. On a plain install of MediaWiki, it is likely the only
295  // feature making use of DeferredUpdates and DB_MASTER on most page views.
296  // In order for the wiki to remain available and readable even if DeferredUpdates
297  // or DB_MASTER have issues, allow this to be turned off completely. (T269516)
298  return;
299  }
300  DeferredUpdates::addCallableUpdate( static function () {
301  // Avoid re-use of $config as that would hold the same object from
302  // the outer call via Setup.php, all the way here through post-send.
303  $instance = new Pingback(
304  MediaWikiServices::getInstance()->getMainConfig(),
305  MediaWikiServices::getInstance()->getDBLoadBalancer(),
307  MediaWikiServices::getInstance()->getHttpRequestFactory(),
308  LoggerFactory::getInstance( 'Pingback' )
309  );
310  $instance->run();
311  } );
312  }
313 }
Pingback\$logger
LoggerInterface $logger
Definition: Pingback.php:45
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:272
Pingback\$lb
ILoadBalancer $lb
Definition: Pingback.php:56
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:173
Pingback\$config
Config $config
Definition: Pingback.php:54
MediaWiki\Http\HttpRequestFactory
Factory creating MWHttpRequest objects.
Definition: HttpRequestFactory.php:39
MW_VERSION
const MW_VERSION
The running version of MediaWiki.
Definition: Defines.php:36
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:86
Wikimedia\Rdbms\DBError
Database error base class @newable.
Definition: DBError.php:32
$dbr
$dbr
Definition: testCompression.php:54
Pingback\__construct
__construct(Config $config, ILoadBalancer $lb, BagOStuff $cache, HttpRequestFactory $http, LoggerInterface $logger)
Definition: Pingback.php:71
Config
Interface for configuration instances.
Definition: Config.php:30
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:115
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
Pingback
Send information about this MediaWiki instance to mediawiki.org.
Definition: Pingback.php:44
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Pingback\acquireLock
acquireLock()
Acquire lock for sending a pingback.
Definition: Pingback.php:147
Pingback\markSent
markSent()
Record the fact that we have sent a pingback for this MediaWiki version, to ensure we don't submit da...
Definition: Pingback.php:274
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
DB_MASTER
const DB_MASTER
Definition: defines.php:26
Pingback\wasRecentlySent
wasRecentlySent()
Was a pingback sent in the last month for this MediaWiki version?
Definition: Pingback.php:121
Pingback\getData
getData()
Get the EventLogging packet to be sent to the server.
Definition: Pingback.php:169
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
Pingback\getSystemInfo
static getSystemInfo(Config $config)
Collect basic data about this MediaWiki installation and return it as an associative array conforming...
Definition: Pingback.php:190
Pingback\$key
string $key
updatelog key (also used as cache/db lock key)
Definition: Pingback.php:62
Pingback\$http
HttpRequestFactory $http
Definition: Pingback.php:60
Pingback\postPingback
postPingback(array $data)
Serialize pingback data and send it to mediawiki.org via a POST request to its EventLogging beacon en...
Definition: Pingback.php:261
Pingback\$cache
BagOStuff $cache
Definition: Pingback.php:58
Pingback\schedulePingback
static schedulePingback()
Schedule a deferred callable that will check if a pingback should be sent and (if so) proceed to send...
Definition: Pingback.php:290
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Definition: DeferredUpdates.php:145
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
Pingback\run
run()
Maybe send a ping.
Definition: Pingback.php:92
Pingback\fetchOrInsertId
fetchOrInsertId()
Get a unique, stable identifier for this wiki.
Definition: Pingback.php:221