MediaWiki  1.28.0
PHPSessionHandler.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Session;
25 
28 
34 class PHPSessionHandler implements \SessionHandlerInterface {
36  protected static $instance = null;
37 
39  protected $enable = false;
40  protected $warn = true;
41 
43  protected $manager;
44 
46  protected $store;
47 
49  protected $logger;
50 
52  protected $sessionFieldCache = [];
53 
54  protected function __construct( SessionManager $manager ) {
55  $this->setEnableFlags(
56  \RequestContext::getMain()->getConfig()->get( 'PHPSessionHandling' )
57  );
58  $manager->setupPHPSessionHandler( $this );
59  }
60 
69  private function setEnableFlags( $PHPSessionHandling ) {
70  switch ( $PHPSessionHandling ) {
71  case 'enable':
72  $this->enable = true;
73  $this->warn = false;
74  break;
75 
76  case 'warn':
77  $this->enable = true;
78  $this->warn = true;
79  break;
80 
81  case 'disable':
82  $this->enable = false;
83  $this->warn = false;
84  break;
85  }
86  }
87 
92  public static function isInstalled() {
93  return (bool)self::$instance;
94  }
95 
100  public static function isEnabled() {
101  return self::$instance && self::$instance->enable;
102  }
103 
108  public static function install( SessionManager $manager ) {
109  if ( self::$instance ) {
110  $manager->setupPHPSessionHandler( self::$instance );
111  return;
112  }
113 
114  // @codeCoverageIgnoreStart
115  if ( defined( 'MW_NO_SESSION_HANDLER' ) ) {
116  throw new \BadMethodCallException( 'MW_NO_SESSION_HANDLER is defined' );
117  }
118  // @codeCoverageIgnoreEnd
119 
120  self::$instance = new self( $manager );
121 
122  // Close any auto-started session, before we replace it
123  session_write_close();
124 
125  // Tell PHP not to mess with cookies itself
126  ini_set( 'session.use_cookies', 0 );
127  ini_set( 'session.use_trans_sid', 0 );
128 
129  // T124510: Disable automatic PHP session related cache headers.
130  // MediaWiki adds it's own headers and the default PHP behavior may
131  // set headers such as 'Pragma: no-cache' that cause problems with
132  // some user agents.
133  session_cache_limiter( '' );
134 
135  // Also set a sane serialization handler
136  \Wikimedia\PhpSessionSerializer::setSerializeHandler();
137 
138  // Register this as the save handler, and register an appropriate
139  // shutdown function.
140  session_set_save_handler( self::$instance, true );
141  }
142 
150  public function setManager(
152  ) {
153  if ( $this->manager !== $manager ) {
154  // Close any existing session before we change stores
155  if ( $this->manager ) {
156  session_write_close();
157  }
158  $this->manager = $manager;
159  $this->store = $store;
160  $this->logger = $logger;
161  \Wikimedia\PhpSessionSerializer::setLogger( $this->logger );
162  }
163  }
164 
172  public function open( $save_path, $session_name ) {
173  if ( self::$instance !== $this ) {
174  throw new \UnexpectedValueException( __METHOD__ . ': Wrong instance called!' );
175  }
176  if ( !$this->enable ) {
177  throw new \BadMethodCallException( 'Attempt to use PHP session management' );
178  }
179  return true;
180  }
181 
187  public function close() {
188  if ( self::$instance !== $this ) {
189  throw new \UnexpectedValueException( __METHOD__ . ': Wrong instance called!' );
190  }
191  $this->sessionFieldCache = [];
192  return true;
193  }
194 
201  public function read( $id ) {
202  if ( self::$instance !== $this ) {
203  throw new \UnexpectedValueException( __METHOD__ . ': Wrong instance called!' );
204  }
205  if ( !$this->enable ) {
206  throw new \BadMethodCallException( 'Attempt to use PHP session management' );
207  }
208 
209  $session = $this->manager->getSessionById( $id, false );
210  if ( !$session ) {
211  return '';
212  }
213  $session->persist();
214 
215  $data = iterator_to_array( $session );
216  $this->sessionFieldCache[$id] = $data;
217  return (string)\Wikimedia\PhpSessionSerializer::encode( $data );
218  }
219 
229  public function write( $id, $dataStr ) {
230  if ( self::$instance !== $this ) {
231  throw new \UnexpectedValueException( __METHOD__ . ': Wrong instance called!' );
232  }
233  if ( !$this->enable ) {
234  throw new \BadMethodCallException( 'Attempt to use PHP session management' );
235  }
236 
237  $session = $this->manager->getSessionById( $id, true );
238  if ( !$session ) {
239  // This can happen under normal circumstances, if the session exists but is
240  // invalid. Let's emit a log warning instead of a PHP warning.
241  $this->logger->warning(
242  __METHOD__ . ': Session "{session}" cannot be loaded, skipping write.',
243  [
244  'session' => $id,
245  ] );
246  return true;
247  }
248 
249  // First, decode the string PHP handed us
250  $data = \Wikimedia\PhpSessionSerializer::decode( $dataStr );
251  if ( $data === null ) {
252  // @codeCoverageIgnoreStart
253  return false;
254  // @codeCoverageIgnoreEnd
255  }
256 
257  // Now merge the data into the Session object.
258  $changed = false;
259  $cache = isset( $this->sessionFieldCache[$id] ) ? $this->sessionFieldCache[$id] : [];
260  foreach ( $data as $key => $value ) {
261  if ( !array_key_exists( $key, $cache ) ) {
262  if ( $session->exists( $key ) ) {
263  // New in both, so ignore and log
264  $this->logger->warning(
265  __METHOD__ . ": Key \"$key\" added in both Session and \$_SESSION!"
266  );
267  } else {
268  // New in $_SESSION, keep it
269  $session->set( $key, $value );
270  $changed = true;
271  }
272  } elseif ( $cache[$key] === $value ) {
273  // Unchanged in $_SESSION, so ignore it
274  } elseif ( !$session->exists( $key ) ) {
275  // Deleted in Session, keep but log
276  $this->logger->warning(
277  __METHOD__ . ": Key \"$key\" deleted in Session and changed in \$_SESSION!"
278  );
279  $session->set( $key, $value );
280  $changed = true;
281  } elseif ( $cache[$key] === $session->get( $key ) ) {
282  // Unchanged in Session, so keep it
283  $session->set( $key, $value );
284  $changed = true;
285  } else {
286  // Changed in both, so ignore and log
287  $this->logger->warning(
288  __METHOD__ . ": Key \"$key\" changed in both Session and \$_SESSION!"
289  );
290  }
291  }
292  // Anything deleted in $_SESSION and unchanged in Session should be deleted too
293  // (but not if $_SESSION can't represent it at all)
294  \Wikimedia\PhpSessionSerializer::setLogger( new \Psr\Log\NullLogger() );
295  foreach ( $cache as $key => $value ) {
296  if ( !array_key_exists( $key, $data ) && $session->exists( $key ) &&
297  \Wikimedia\PhpSessionSerializer::encode( [ $key => true ] )
298  ) {
299  if ( $cache[$key] === $session->get( $key ) ) {
300  // Unchanged in Session, delete it
301  $session->remove( $key );
302  $changed = true;
303  } else {
304  // Changed in Session, ignore deletion and log
305  $this->logger->warning(
306  __METHOD__ . ": Key \"$key\" changed in Session and deleted in \$_SESSION!"
307  );
308  }
309  }
310  }
311  \Wikimedia\PhpSessionSerializer::setLogger( $this->logger );
312 
313  // Save and update cache if anything changed
314  if ( $changed ) {
315  if ( $this->warn ) {
316  wfDeprecated( '$_SESSION', '1.27' );
317  $this->logger->warning( 'Something wrote to $_SESSION!' );
318  }
319 
320  $session->save();
321  $this->sessionFieldCache[$id] = iterator_to_array( $session );
322  }
323 
324  $session->persist();
325 
326  return true;
327  }
328 
335  public function destroy( $id ) {
336  if ( self::$instance !== $this ) {
337  throw new \UnexpectedValueException( __METHOD__ . ': Wrong instance called!' );
338  }
339  if ( !$this->enable ) {
340  throw new \BadMethodCallException( 'Attempt to use PHP session management' );
341  }
342  $session = $this->manager->getSessionById( $id, false );
343  if ( $session ) {
344  $session->clear();
345  }
346  return true;
347  }
348 
355  public function gc( $maxlifetime ) {
356  if ( self::$instance !== $this ) {
357  throw new \UnexpectedValueException( __METHOD__ . ': Wrong instance called!' );
358  }
359  $before = date( 'YmdHis', time() );
360  $this->store->deleteObjectsExpiringBefore( $before );
361  return true;
362  }
363 }
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$value
setManager(SessionManager $manager, BagOStuff $store, LoggerInterface $logger)
Set the manager, store, and logger.
setupPHPSessionHandler(PHPSessionHandler $handler)
Call setters on a PHPSessionHandler.
MediaWiki s SiteStore can be cached and stored in a flat in a json format If the SiteStore is frequently the file cache may provide a performance benefit over a database store
Definition: sitescache.txt:1
__construct(SessionManager $manager)
close()
Close the session (handler)
setEnableFlags($PHPSessionHandling)
Set $this->enable and $this->warn.
write($id, $dataStr)
Write session data.
static getMain()
Static methods.
static install(SessionManager $manager)
Install a session handler for the current web request.
static PHPSessionHandler $instance
$cache
Definition: mcc.php:33
bool $enable
Whether PHP session handling is enabled.
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static isInstalled()
Test whether the handler is installed.
Adapter for PHP's session handling.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static isEnabled()
Test whether the handler is installed and enabled.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
gc($maxlifetime)
Execute garbage collection.
This serves as the entry point to the MediaWiki session handling system.
array $sessionFieldCache
Track original session fields for later modification check.
open($save_path, $session_name)
Initialize the session (handler)