File: //scripts/env.php
<?php
ini_set( 'display_errors', '0' );
define( 'WP_DEBUG_DISPLAY', false );
// Maintain existing PHP behavior for >= 8.1
if ( function_exists( 'mysqli_report' ) ) {
mysqli_report( MYSQLI_REPORT_OFF );
}
// s2member fix see: https://wordpress.org/support/topic/error-s2member-unable-to-locate-wordpress-directory
$_SERVER['WP_DIR'] = dirname( __FILE__ ) . '/../srv/htdocs/__wp__/';
function __atomic_env_define( $constant_name, $env_var=null, $default=null ) {
if ( defined( $constant_name ) )
return;
if ( $env_var === null )
$env_var = $constant_name;
$value = getenv( $env_var );
if ( $value === false && $default !== null )
$value = $default;
define( $constant_name, $value );
// if executing under cli don't putenv or unset variables
if ( 'cli' !== php_sapi_name() ) {
unset( $_SERVER[ $env_var ] );
unset( $_ENV[ $env_var ] );
putenv( $env_var );
}
}
__atomic_env_define( 'WP_MEMORY_LIMIT', 'ATOMIC_SITE_MEMORY_LIMIT', '512M' );
__atomic_env_define( 'WP_MAX_MEMORY_LIMIT', 'ATOMIC_SITE_MEMORY_LIMIT', '512M' );
if ( 'cli' == php_sapi_name() ) {
ini_set( 'memory_limit', WP_MEMORY_LIMIT );
}
// For disabling outbound UDP ports 80, 3244 that trigger firewall alerts in WP defender plugin
define( 'WD_DONT_CHECK_UDP', true );
// Force flow-flow to use WPDB which prevents it from creating deadlocks in MariaDB
define('FF_USE_WPDB', true);
// This section of code needs to run before anything else, so that REMOTE_ADDR is set correctly in production
if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
$_SERVER['REMOTE_ADDR_ORIG'] = $_SERVER['REMOTE_ADDR'];
}
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && strstr( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' ) )
$_SERVER['HTTPS'] = 'on';
// Proxied request, adjust SERVER_PORT accordingly
if ( isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && is_numeric( $_SERVER['HTTP_X_FORWARDED_PORT'] ) )
$_SERVER['SERVER_PORT'] = intval( $_SERVER['HTTP_X_FORWARDED_PORT'] );
if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) &&
( filter_var( $_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ||
filter_var( $_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) )
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
// Clear some default fpm vars that are not used and/or shouldn't be accessed by the site
$vars_to_remove = array( 'HOME', 'USER', 'REMOTE_ADDR_ORIG', 'HTTP_X_IP_TRAIL', 'PHP_ADMIN_VALUE' );
foreach( $vars_to_remove as $var_to_remove ) {
putenv( $var_to_remove );
unset( $_SERVER[ $var_to_remove ] );
unset( $_ENV[ $var_to_remove ] );
}
// From http://php.net/manual/en/reserved.variables.server.php
// PATH_TRANSLATED should only exist if PATH_INFO is defined
if ( empty( $_SERVER['PATH_INFO'] ) )
unset( $_SERVER['PATH_TRANSLATED'] );
# Required to run WP transparently from symlinked subdir
define( 'FS_METHOD', 'direct' );
# If we don't have a client id we can't securely serve the site, so just die
if ( ! isset( $_SERVER['ATOMIC_CLIENT_ID'] ) && 'cli' !== php_sapi_name() ) {
http_response_code( 503 );
die( 'Client ID Not found.' );
}
if ( 'cli' == php_sapi_name() ) {
define( 'AT_PROXIED_REQUEST', false );
} elseif ( '1' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '2' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '3' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['PRESSABLE_PROXIED_REQUEST'] ) && '1' === $_SERVER['PRESSABLE_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '5' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '6' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '8' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '13' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '109' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '118' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} elseif ( '131' === $_SERVER['ATOMIC_CLIENT_ID'] && isset( $_SERVER['A8C_PROXIED_REQUEST'] ) && '1' === $_SERVER['A8C_PROXIED_REQUEST'] ) {
define( 'AT_PROXIED_REQUEST', true );
} else {
define( 'AT_PROXIED_REQUEST', false );
}
# We don't want this to be overidden in custom-redirects.php
__atomic_env_define( 'AT_SITE_SUSPENDED', 'SUSPENDED' ); // Whether the site is suspended
/*
* Catch cases when WP failed to connect to the database.
*/
function __atomic_wp_die_dead_db( $callback ) {
$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 10 );
foreach ( $backtrace as $trace ) {
if ( 'dead_db' === $trace['function'] ) {
$GLOBALS['__atomic_dead_db'] = true;
break;
}
if ( isset( $trace['file'] ) && '/wp-content/db-error.php' === substr( $trace['file'], -24 ) ) {
$GLOBALS['__atomic_dead_db'] = true;
break;
}
}
return $callback;
}
atomic_add_filter( 'wp_die_handler', '__atomic_wp_die_dead_db', 0, 1 );
atomic_add_filter( 'wp_die_ajax_handler', '__atomic_wp_die_dead_db', 0, 1 );
atomic_add_filter( 'wp_die_json_handler', '__atomic_wp_die_dead_db', 0, 1 );
atomic_add_filter( 'wp_die_jsonp_handler', '__atomic_wp_die_dead_db', 0, 1 );
atomic_add_filter( 'wp_die_xml_handler', '__atomic_wp_die_dead_db', 0, 1 );
atomic_add_filter( 'wp_die_xmlrpc_handler', '__atomic_wp_die_dead_db', 0, 1 );
/*
* Sanity check and correct bad status codes [and later on maybe caching headers].
*/
function __atomic_header_callback() {
$current_http_response_code = http_response_code();
if ( 502 == $current_http_response_code || // 502s are special for us, not allowed for users
$current_http_response_code < 100 || $current_http_response_code > 599 ) // matches the strictest check in Nginx
{
error_log( "Error: Sent an incorrect $current_http_response_code status code." );
http_response_code( 500 );
header( "{$_SERVER['SERVER_PROTOCOL']} 500 Internal Server Error", true, 500 );
}
// Make an exception for MySQL connection errors where we want 502
// We have this in addition to the __atomic_wp_die_dead_db() filter above
// because some users install db-error.php and exit there without wp_die().
$last_error = error_get_last();
if ( $last_error && false !== strpos( $last_error['message'], 'mysqli_real_connect' ) ) {
$conn_failure_strings = [ 'Too many connections', 'Connection timed out', 'Connection refused' ];
foreach ( $conn_failure_strings as $conn_failure ) {
if ( false === strpos( $last_error['message'], $conn_failure ) ) {
$GLOBALS['__atomic_dead_db'] = true;
break;
}
}
}
if ( ! empty( $GLOBALS['__atomic_dead_db'] ) && $current_http_response_code >= 500 ) {
http_response_code( 502 );
header( "{$_SERVER['SERVER_PROTOCOL']} 502 Bad Gateway", true, 502 );
}
}
if ( 'cli' !== php_sapi_name() && ! header_register_callback('__atomic_header_callback') ) {
http_response_code( 503 );
die( 'Unexpected PHP error.' );
}
final class Atomic_Persistent_Data implements Iterator {
private $initialized;
private $filename;
private $encrypted;
private $decrypted;
private $index = array();
private $position = 0;
public function __construct( $filename = null ) {
$this->initialized = false;
$this->filename = $filename;
}
private function initialize() {
if ( false !== $this->initialized ) {
return;
}
$this->initialized = true;
$this->encrypted = new stdClass();
$this->decrypted = new stdClass();
if ( empty( $this->filename ) ) {
$this->filename = sys_get_temp_dir() . '/.at-persistent-data';
}
if ( ! file_exists( $this->filename ) ) {
return;
}
if ( ! is_readable( $this->filename ) ) {
return;
}
if ( ! is_file( $this->filename ) ) {
return;
}
$contents = file_get_contents( $this->filename );
if ( false === $contents || empty( $contents ) ) {
return;
}
$data = json_decode( $contents );
if ( false === $data || empty( $data ) ) {
return;
}
$this->index = array_keys( get_object_vars( $data ) );
if ( empty( $data->_version ) || false === $data->_encrypted ) {
$this->encrypted = $data;
$this->decrypted = $data;
} else {
$this->encrypted = $data;
}
}
private function __key() {
static $key = null;
if ( null === $key ) {
$key = hash_hkdf(
'sha256',
constant( 'DB_PASSWORD' ),
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
'site-persist-data',
'site-persist-data'
);
}
return $key;
}
public function __get( $name ) {
$this->initialize();
if ( property_exists( $this->decrypted, $name ) ) {
return $this->decrypted->{$name};
}
if ( ! property_exists( $this->encrypted, $name ) ) {
return null;
}
if ( ! is_object( $this->encrypted->{$name} ) || ! property_exists( $this->encrypted->{$name}, 'nonce' ) || ! property_exists( $this->encrypted->{$name}, 'value' ) ) {
$this->decrypted->{$name} = $this->encrypted->{$name};
return $this->decrypted->{$name};
}
try {
$this->decrypted->{$name} = sodium_crypto_secretbox_open(
sodium_base642bin( $this->encrypted->{$name}->value, SODIUM_BASE64_VARIANT_URLSAFE ),
sodium_base642bin( $this->encrypted->{$name}->nonce, SODIUM_BASE64_VARIANT_URLSAFE ),
$this->__key()
);
return $this->decrypted->{$name};
} catch ( Throwable $ex ) {
error_log( sprintf( 'Critical error decrypting %s: %s', $name, $ex->getMessage() ) );
return null;
}
}
public function __isset( $name ) {
$this->initialize();
return property_exists( $this->encrypted, $name );
}
// iterator
#[\ReturnTypeWillChange]
public function current() {
$this->initialize();
return $this->__get( $this->index[ $this->position ] );
}
// iterator
#[\ReturnTypeWillChange]
public function key() {
$this->initialize();
return $this->index[ $this->position ];
}
// iterator
#[\ReturnTypeWillChange]
public function next() {
$this->initialize();
++$this->position;
}
// iterator
#[\ReturnTypeWillChange]
public function rewind() {
$this->initialize();
$this->position = 0;
}
// iterator
#[\ReturnTypeWillChange]
public function valid() {
$this->initialize();
return isset( $this->index[ $this->position ] );
}
}
# Per-client env.php. We want need the file names to be unique to not cause opcache collisions since they are executed in the chroot
$client_env_file = dirname( __FILE__ ) . '/env-client-' . $_SERVER['ATOMIC_CLIENT_ID'] . '.php';
if ( file_exists( $client_env_file ) ) {
include( $client_env_file );
}
switch( $_SERVER['ATOMIC_CLIENT_ID'] ) {
case "2":
// Used by Jetpack and maybe some other stuff
// See http://wp.me/p72Wkx-gY
define( 'IS_ATOMIC', true );
define( 'AT_SMTP_PORT', 25 );
define( 'WPCLOUD_STAGING_DOMAIN', '.wpcomstaging.com' );
define( 'WPCLOUD_STAGING_ALLOW_INDEXING', false );
$atomic_hosting_provider = 'WordPress.com';
break;
case "3":
define( 'IS_PRESSABLE', true );
define( 'AT_SMTP_PORT', 26 );
define( 'WPCLOUD_STAGING_DOMAIN', '.mystagingwebsite.com' );
define( 'WPCLOUD_STAGING_ALLOW_INDEXING', false );
$atomic_hosting_provider = 'Pressable';
break;
case "6":
define( 'AT_SMTP_PORT', 25 );
$atomic_hosting_provider = 'Newspack';
break;
case "8":
define( 'AT_SMTP_PORT', 25 );
$atomic_hosting_provider = 'HappyP2';
break;
case "9":
define( 'AT_SMTP_PORT', 25 ); // @TODO
$atomic_hosting_provider = 'Automattic'; // @TODO
break;
case "118": // commerce-garden
define( 'IS_ATOMIC', true );
define( 'AT_SMTP_PORT', 27 );
$atomic_hosting_provider = 'WordPress.com';
break;
case "131": // wpcom-flex
define( 'IS_ATOMIC', true );
define( 'AT_SMTP_PORT', 25 );
$atomic_hosting_provider = 'WordPress.com';
break;
default :
define( 'IS_ATOMIC', true );
define( 'AT_SMTP_PORT', 25 );
$atomic_hosting_provider = 'Automattic';
break;
}
/*
* Determine if staging sites should allow indexing when robots.txt file does not exist.
* Existing robots.txt file is served by NGINX.
*
* @return Do not handle the request, continue to WP.
*/
function atomic_maybe_handle_staging_robots_txt_request() {
// Check constants defined, if not continue to WP (existing behavior)
if ( ! defined( 'WPCLOUD_STAGING_DOMAIN' ) || ! defined( 'WPCLOUD_STAGING_ALLOW_INDEXING' ) ) {
return;
}
// If staging indexing enabled, allow WP to handle
if ( WPCLOUD_STAGING_ALLOW_INDEXING ) {
return;
}
// Check if robots request
if ( ! isset( $_SERVER['REQUEST_URI'] ) || '/robots.txt' !== strtok( $_SERVER['REQUEST_URI'], '?' ) ) {
return;
}
// Check if staging domain request
if ( ! isset( $_SERVER['HTTP_HOST'] ) || WPCLOUD_STAGING_DOMAIN !== substr( $_SERVER['HTTP_HOST'], 0 - strlen( WPCLOUD_STAGING_DOMAIN ) ) ) {
return;
}
// Disallow by default
header( 'Content-Type: text/plain; charset=utf-8' );
echo "User-agent: *\nDisallow: /\n";
exit;
}
atomic_maybe_handle_staging_robots_txt_request();
if ( isset( $_SERVER['REQUEST_URI'] ) && '/.well-known/hosting-provider' == $_SERVER['REQUEST_URI'] && isset( $atomic_hosting_provider ) ) {
die( $atomic_hosting_provider . "\n" );
}
// Disable Patchwork.
eval('namespace Patchwork; function replace() {}');
// Any non-overridable constants should be defined above
// If a custom-redirects.php file exists in the site's htdocs folder, then
// require it. Also check in CLI context because custom-redirects.php
// could be defining DB_{CHARSET,COLLATE} and not having CLI context match
// can produce unexpected results. Because defines haven't been set yet,
// need to use 'getenv'.
$custom_redirects = '/srv/htdocs/custom-redirects.php';
if ( file_exists( $custom_redirects ) ) {
require $custom_redirects;
}
// Any overridable constants should be defined, conditionally, below
// MySQL settings - we set them here so that we remove the sensitive data from $_SERVER before customer-executed code
// These are overridable in custom-redirects.php
__atomic_env_define( 'DB_NAME' ); // The name of the database for WordPress
__atomic_env_define( 'DB_USER' ); // MySQL database username
__atomic_env_define( 'DB_PASSWORD' ); // MySQL database password
__atomic_env_define( 'DB_HOST', null, '127.0.0.1' ); // MySQL hostname
__atomic_env_define( 'DB_CHARSET', null, 'utf8mb4' ); // Database Charset to use in creating database tables.
__atomic_env_define( 'DB_COLLATE' ); // The Database Collate type. Don't change this if in doubt
// Atomic Service Definitions. These are overridable in custom-redirects.php
__atomic_env_define( 'ATOMIC_SITE_ID' );
__atomic_env_define( 'ATOMIC_CLIENT_ID' );
__atomic_env_define( 'ATOMIC_SERVER_POOL_ID' );
__atomic_env_define( 'ATOMIC_SITE_API_KEY', null, '' ); // Virtual API key from metadata used by sites for calling real API
__atomic_env_define( 'WP_CACHE_KEY_SALT' ); // Cache key prefix. Used in object-cache.php
__atomic_env_define( 'AT_SMTP_PASS', 'SMTP_PASS' ); // SMTP Password. Used in /mu-plugins/mail.php
__atomic_env_define( 'JETPACK_BLOG_TOKEN' ); // Force a jetpack blog connection token if one is provided
__atomic_env_define( 'AT_DEVELOPMENT_MODE', 'DEVELOPMENT_MODE' );
__atomic_env_define( 'PHP_FS_PERMISSIONS', 'PHP_FS_PERMISSIONS' );
__atomic_env_define( 'AT_PRIVACY_MODEL', 'PRIVACY_MODEL' );
__atomic_env_define( 'AT_GAUGES_ID', 'GAUGES_ID' );
__atomic_env_define( 'AT_PHOTON_SUBSIZES', 'PHOTON_SUBSIZES', '' );
__atomic_env_define( 'AT_DEREFERENCED_SOFTWARE', 'DEREFERENCED_SOFTWARE', '0' );
__atomic_env_define( 'ATOMIC_APM_ENABLED' );
__atomic_env_define( 'AT_PHP_CURRENT_CONNECTIONS', 'PHP_CURRENT_CONNECTIONS' );
__atomic_env_define( 'AT_PHP_BURST_STATUS', 'PHP_BURST_STATUS' );
if ( ! AT_DEREFERENCED_SOFTWARE ) {
// Disable core WP updates for sites that should be using a managed version.
// Set this constant because Jetpack overrides the auto-update filters,
// and other plugins could as well.
define( 'WP_AUTO_UPDATE_CORE', false );
}
// Local API url prefix
define( 'SITE_API_BASE', 'http://127.0.0.1:47002/api/v1.0' );
// Page Optimize setup
define( 'PAGE_OPTIMIZE_CACHE_DIR', false ); // Disable cache
// paths so we can run service.php w/o WP
define( 'PAGE_OPTIMIZE_CONCAT_BASE_DIR', '/srv/htdocs' );
define( 'PAGE_OPTIMIZE_ABSPATH', PAGE_OPTIMIZE_CONCAT_BASE_DIR . '/__wp__' );
if ( !defined( 'WPCOM_API_KEY' ) )
define( 'WPCOM_API_KEY', '98e6b103f2e3' );
define( 'THEMES_SYMLINK_BASE', '../../../wordpress/themes' );
# For wp-cli to work
if ( 'cli' == php_sapi_name() ) {
__atomic_env_define( 'DOMAIN_NAME' );
// WP CLI if provided with --url arg should set WP_{HOME,SITEURL} the same way its set for HTTP
// otherwise it will use the home/siteurl options which most likely will return a staging address
// needs to be added as an action though because WP CLI setting of $_SERVER vars when --url
// present happens after env.php is required. if --url isn't provided then none of the
// usable $_SERVER vars get set, so the behavior should stay consistent for things that dont pass --url
atomic_add_action( 'muplugins_loaded', 'maybe_set_wp_home_and_wp_siteurl', 0 );
// disable WP cron reuqests from firing when using WP CLI
define( 'DISABLE_WP_CRON', true );
define( 'WP_HOME','https://' . DOMAIN_NAME );
define( 'WP_SITEURL','https://' . DOMAIN_NAME );
// no Batcache with CLI
if ( isset( $_SERVER[ 'argv' ] ) )
$batcache['max_age'] = 0;
}
define( 'WP_CONTENT_DIR', realpath( '/srv/htdocs/wp-content' ) );
define( 'ATOMIC_MU_PLUGIN_DIR', realpath( '/wordpress/mu-plugins' ) );
// for AT sites
define( 'THEMES_PATH_BASE', realpath( '/wordpress/themes' ) );
$memcached_servers = array( '127.0.0.1:11211' );
if ( isset( $_SERVER['SERVER_NAME'] ) && $_SERVER['SERVER_NAME'] ) {
if ( isset( $_SERVER['HTTPS'] ) && 'on' == $_SERVER['HTTPS'] ) {
define( 'WP_HOME','https://' . $_SERVER['SERVER_NAME'] );
define( 'WP_SITEURL','https://' . $_SERVER['SERVER_NAME'] );
} else {
define( 'WP_HOME','http://' . $_SERVER['SERVER_NAME'] );
define( 'WP_SITEURL','http://' . $_SERVER['SERVER_NAME'] );
}
}
// require SSL for wp-login/wp-admin when enabled or when staging
if ( getenv( 'SSL_ENABLED' ) === "true" || ( isset( $_SERVER['HTTP_HOST'] ) && strpos( $_SERVER['HTTP_HOST'], ".mystagingwebsite.com" ) > 0 ) ) {
define( 'FORCE_SSL_LOGIN', true );
define( 'FORCE_SSL_ADMIN', true );
} else {
define( 'FORCE_SSL_LOGIN', false );
define( 'FORCE_SSL_ADMIN', false );
}
unset( $_SERVER['SSL_ENABLED'] );
if ( AT_PROXIED_REQUEST ) {
ini_set( 'error_reporting', -1 ); // E_ALL + forward compat
global $batcache;
$batcache['max_age'] = 0;
define( 'WP_CACHE', false );
define( 'WP_DEBUG', true );
// when proxied turn off WP_DEBUG_LOG if defined
// we want errors sent to logstash
define( 'WP_DEBUG_LOG', false );
define( 'SAVEQUERIES', true );
// Record the start time of each database query
atomic_add_filter( 'query', 'atomic_debug_query_offsets', 1e9, 1 );
function atomic_debug_query_offsets( $query ) {
$offset = sprintf( '%0.6f', microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'] );
$GLOBALS[ 'atomic_debug_query_offsets' ][ $offset ] = $query;
return $query;
}
atomic_add_filter( 'swift_performance_is_cacheable', '__atomic_return_false' );
atomic_add_filter( 'swift_performance_is_cacheable_dynamic', '__atomic_return_false' );
atomic_add_filter( 'swift_performance_is_cacheable_ajax', '__atomic_return_false' );
function __atomic_return_false() {
return false;
}
}
defined( 'WP_CACHE' ) or define( 'WP_CACHE', true );
$batcache['cache_redirects'] = true;
header( 'X-AT-I-Renderer: php' );
// Allow adding filters/actions prior to loading WordPress.
// $function_to_add MUST be a string.
function atomic_add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
global $wp_filter;
$wp_filter[$tag][$priority][$function_to_add] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
}
function atomic_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
atomic_add_filter( $tag, $function_to_add, $priority, $accepted_args );
}
// Load our mu-plugins after customer mu-plugins
// NOTE: this means our mu-plugins can't use the muplugins_loaded action!
atomic_add_action( 'muplugins_loaded', 'atomic_load_mu_plugins', 0 );
function atomic_load_mu_plugins() {
$mu_plugins = array();
$persistent_data = null;
$overrides = null;
if ( ! is_dir( ATOMIC_MU_PLUGIN_DIR ) ) {
return;
}
if ( ! $dh = opendir( ATOMIC_MU_PLUGIN_DIR ) ) {
return;
}
// If we are told to enable this feature via a define in wp-config.php then initialize
// the required data structures
if ( defined( 'WPCLOUD_ALLOW_FILTERED_MU_PLUGINS' ) && WPCLOUD_ALLOW_FILTERED_MU_PLUGINS ) {
$persistent_data = new Atomic_Persistent_Data();
$overrides = array(
'loaded' => array(),
'skipped' => array(),
);
$disable_all = false;
if ( ! empty( $persistent_data->{"disable_mu-plugin_all"} ) ) {
$disable_all = true;
}
}
while ( ( $plugin = readdir( $dh ) ) !== false ) {
// Early continue for non-php-files
if ( substr( $plugin, -4 ) !== '.php' ) {
continue;
}
// Early continue for when we are not configured to disable mu-plugins
if ( null === $persistent_data ) {
$mu_plugins[] = $plugin;
continue;
}
// If we have been asked to skip this then
if ( true === $disable_all || ! empty( $persistent_data->{"disable_mu-plugin_$plugin"} ) ) {
// First: never allow disabling of the following mu-plugins:
// atomic-*, wpcloud-*, mail.php
if ( ! empty( preg_match( '/^(atomic|wpcloud|mail\.php$)/', $plugin ) ) ) {
$overrides['loaded'][] = rawurlencode( $plugin );
$mu_plugins[] = $plugin;
continue;
}
// Early continue for disabled mu-plugin
$overrides['skipped'][] = rawurlencode( $plugin );
continue;
}
// The plugin has not been specifically disabled, so add the plugin to the list
$overrides['loaded'][] = rawurlencode( $plugin );
$mu_plugins[] = $plugin;
}
if ( ! empty( $overrides ) ) {
header( 'X-Mu-Plugins-Loaded: ' . implode( '; ', $overrides['loaded'] ) );
header( 'X-Mu-Plugins-Skipped: ' . implode( '; ', $overrides['skipped'] ) );
}
closedir( $dh );
sort( $mu_plugins );
foreach ( $mu_plugins as $mu_plugin ) {
load_atomic_mu_plugin( $mu_plugin );
}
}
// APM AutoInstrumentation for WordPress
if ( 'true' === ATOMIC_APM_ENABLED && extension_loaded( 'elastic_apm' ) && class_exists( '\Elastic\Apm\ElasticApm', true ) ) {
// Wraps a callable so each invocation produces a child span, with subtype
// wp_core, wp_plugin, or wp_theme based on the callback's source file.
// Implemented as a class (not a closure) so wrapped callbacks are
// identifiable by instanceof, which prevents double-wrapping when a hook
// fires repeatedly.
class Atomic_APM_Hook_Wrapper {
private $original;
private $name;
private $kind; // 'wp_core' | 'wp_plugin' | 'wp_theme'
private $plugin; // plugin/theme dir name, 'core', or null if unknown
private $source; // "<file>:<line>" where the callback is defined, or null
private $hook;
public function __construct( $original, $name, $kind, $plugin, $source, $hook ) {
$this->original = $original;
$this->name = $name;
$this->kind = $kind;
$this->plugin = $plugin;
$this->source = $source;
$this->hook = $hook;
}
public function get_original() {
return $this->original;
}
public function __invoke( ...$args ) {
// Fail-open: if any APM call throws, the original WordPress callback
// must still run. Instrumentation never breaks the request path.
$span = null;
try {
$span = \Elastic\Apm\ElasticApm::getCurrentTransaction()->beginCurrentSpan(
$this->name,
'wp',
$this->kind,
$this->hook
);
if ( $this->plugin ) {
$span->context()->setLabel( 'wp_plugin', $this->plugin );
}
if ( $this->source ) {
// The agent's stacktrace starts inside this wrapper, so the
// real callback file isn't visible. Surface it as a label.
$span->context()->setLabel( 'callback_source', $this->source );
}
} catch ( Throwable $e ) {
$span = null;
}
try {
return ( $this->original )( ...$args );
} finally {
if ( $span ) {
try {
if ( ! $span->hasEnded() ) {
$span->end();
}
} catch ( Throwable $e ) {
}
}
}
}
}
// True if $fn is a closure defined inside this file (cleanup closures and
// the 'all'-hook handler itself). Used to skip our own machinery so we
// don't wrap or describe it.
function atomic_apm_is_self_closure( $fn ) {
if ( ! ( $fn instanceof Closure ) ) {
return false;
}
// Intentionally not cached by spl_object_id: ids are reused after a
// closure is freed, so a cached "not-self" result could mask a real
// self-closure that later inherits the same id (and vice versa,
// causing a legitimate user closure to be skipped).
try {
return __FILE__ === ( new ReflectionFunction( $fn ) )->getFileName();
} catch ( Throwable $e ) {
return false;
}
}
// Static cache of [absolute_prefix_with_trailing_slash, kind] pairs used to
// classify a callback's source file. Built from WP and Atomic constants
// (covers ATOMIC_MU_PLUGIN_DIR and symlinked plugin stores outside
// /wp-content/). Substring fallbacks cover calls made before WP defines
// its constants.
function atomic_apm_source_path_prefixes() {
static $prefixes = null;
if ( null !== $prefixes ) {
return $prefixes;
}
$prefixes = array();
$add = static function ( $path, $kind ) use ( &$prefixes ) {
if ( ! $path ) {
return;
}
$rp = @realpath( $path );
if ( ! $rp ) {
return;
}
$prefixes[] = array( $rp . '/', $kind );
};
if ( defined( 'WP_PLUGIN_DIR' ) ) {
$add( WP_PLUGIN_DIR, 'wp_plugin' );
}
if ( defined( 'WPMU_PLUGIN_DIR' ) ) {
$add( WPMU_PLUGIN_DIR, 'wp_plugin' );
}
if ( defined( 'ATOMIC_MU_PLUGIN_DIR' ) ) {
$add( ATOMIC_MU_PLUGIN_DIR, 'wp_plugin' );
}
if ( function_exists( 'get_theme_roots' ) ) {
$content = null;
if ( defined( 'WP_CONTENT_DIR' ) ) {
$content = WP_CONTENT_DIR;
}
foreach ( (array) get_theme_roots() as $root ) {
if ( ! $root ) {
continue;
}
// get_theme_roots() returns '/themes' (relative to WP_CONTENT_DIR)
// in the single-root case. Multi-root setups via
// register_theme_directory() may return absolute paths, so a
// leading '/' does not distinguish them. Try as-is first, then
// fall back to WP_CONTENT_DIR-relative.
if ( @realpath( $root ) ) {
$add( $root, 'wp_theme' );
} elseif ( $content ) {
$add( $content . $root, 'wp_theme' );
}
}
}
$prefixes[] = array( '/wp-content/plugins/', 'wp_plugin' );
$prefixes[] = array( '/wp-content/mu-plugins/', 'wp_plugin' );
$prefixes[] = array( '/wp-content/themes/', 'wp_theme' );
return $prefixes;
}
// Returns ['kind' => 'wp_core'|'wp_plugin'|'wp_theme',
// 'name' => <dir|'core'|null>,
// 'file' => <absolute path|null>,
// 'line' => <int|null>,
// 'has_ref_params' => <bool>]
// file:line points at the callback's own definition (surfaced as a span
// label since the captured stacktrace is dominated by wrapper/WP_Hook frames).
// has_ref_params is true if any parameter is declared by reference; the
// wrapper's variadic invoke would silently drop ref semantics, so callers
// must skip wrapping such callbacks.
// Memoized per-request by callback identity so the same callback registered
// on multiple hooks reflects only once.
function atomic_apm_callback_source_info( $callback ) {
static $cache = array();
$key = atomic_apm_callback_cache_key( $callback );
if ( null !== $key && isset( $cache[ $key ] ) ) {
return $cache[ $key ];
}
$file = null;
$line = null;
$has_ref_params = false;
try {
if ( $callback instanceof Closure ) {
$r = new ReflectionFunction( $callback );
} elseif ( is_string( $callback ) ) {
if ( false === ( $pos = strpos( $callback, '::' ) ) ) {
$r = new ReflectionFunction( $callback );
} else {
$r = new ReflectionMethod( substr( $callback, 0, $pos ), substr( $callback, $pos + 2 ) );
}
} elseif ( is_array( $callback ) && isset( $callback[0], $callback[1] ) ) {
$r = new ReflectionMethod( $callback[0], $callback[1] );
} elseif ( is_object( $callback ) && method_exists( $callback, '__invoke' ) ) {
$r = new ReflectionMethod( $callback, '__invoke' );
} else {
$r = null;
}
if ( $r ) {
$file = $r->getFileName();
if ( ! $file ) {
$file = null;
}
$line = $r->getStartLine();
if ( ! $line ) {
$line = null;
}
foreach ( $r->getParameters() as $param ) {
if ( $param->isPassedByReference() ) {
$has_ref_params = true;
break;
}
}
}
} catch ( Throwable $e ) {
}
if ( ! $file ) {
$result = array( 'kind' => 'wp_core', 'name' => null, 'file' => null, 'line' => $line, 'has_ref_params' => $has_ref_params );
} else {
$result = null;
foreach ( atomic_apm_source_path_prefixes() as list( $prefix, $kind ) ) {
$pos = strpos( $file, $prefix );
if ( false === $pos ) {
continue;
}
$rest = substr( $file, $pos + strlen( $prefix ) );
$end = strpos( $rest, '/' );
if ( false === $end ) {
$end = strpos( $rest, '.php' );
}
$name = $rest;
if ( false !== $end ) {
$name = substr( $rest, 0, $end );
}
$result = array( 'kind' => $kind, 'name' => $name, 'file' => $file, 'line' => $line, 'has_ref_params' => $has_ref_params );
break;
}
if ( null === $result ) {
$result = array( 'kind' => 'wp_core', 'name' => 'core', 'file' => $file, 'line' => $line, 'has_ref_params' => $has_ref_params );
}
}
if ( null !== $key ) {
$cache[ $key ] = $result;
}
return $result;
}
// Stable per-request key for memoizing source_info. Only returns keys for
// callbacks with identity-stable representations: function names and
// 'Class::method' arrays. Closures, object-method arrays, and invokable
// objects deliberately return null and are reflected on every call:
// spl_object_id is reused after GC, so caching by it would let a stale
// no-ref result mask a later by-ref callback (reintroducing the
// ref-semantics bug that the wrap-loop skip is meant to prevent).
function atomic_apm_callback_cache_key( $callback ) {
if ( is_string( $callback ) ) {
return 's:' . $callback;
}
if ( is_array( $callback ) && isset( $callback[0], $callback[1] ) && is_string( $callback[0] ) && is_string( $callback[1] ) ) {
return 'a:' . $callback[0] . '::' . $callback[1];
}
return null;
}
function atomic_apm_describe_callback( $callback ) {
if ( is_string( $callback ) ) {
return $callback . '()';
}
if ( is_array( $callback ) && isset( $callback[0], $callback[1] ) ) {
if ( is_string( $callback[0] ) ) {
return $callback[0] . '::' . $callback[1] . '()';
}
if ( is_object( $callback[0] ) ) {
return get_class( $callback[0] ) . '->' . $callback[1] . '()';
}
}
if ( $callback instanceof Closure ) {
try {
$r = new ReflectionFunction( $callback );
return sprintf( '[%s:%d-%d]', $r->getFileName(), $r->getStartLine(), $r->getEndLine() );
} catch ( Throwable $e ) {
return '[closure]';
}
}
if ( is_object( $callback ) && method_exists( $callback, '__invoke' ) ) {
return get_class( $callback ) . '->__invoke()';
}
return '[unknown]';
}
function atomic_apm_auto_instrumentation_wp_hook( $hook_name, $value = null ) {
// Top-level guard. Instrumentation must never break WP hook execution,
// so any Throwable from APM agent calls, reflection, or our own logic
// is swallowed and the request path continues normally.
try {
atomic_apm_run_wp_hook_instrumentation( $hook_name, $value );
} catch ( Throwable $e ) {
}
}
function atomic_apm_run_wp_hook_instrumentation( $hook_name, $value = null ) {
global $atomic_apm_wp_stage_spans;
global $wp_actions;
global $wp_filter;
##
# Add artificial grouping spans for WP Hooks
##
// Bail if stages are not setup
if ( !is_array( $atomic_apm_wp_stage_spans ) ) {
return;
}
// Stop setup stage on first call to wp action
if ( $atomic_apm_wp_stage_spans['setup'] instanceof \Elastic\Apm\SpanInterface && !$atomic_apm_wp_stage_spans['setup']->hasEnded() && 'wp' === $hook_name ) {
$atomic_apm_wp_stage_spans['setup']->end();
}
// Stop content stage on first call to shutdown action
if ( $atomic_apm_wp_stage_spans['content'] instanceof \Elastic\Apm\SpanInterface && !$atomic_apm_wp_stage_spans['content']->hasEnded() && 'shutdown' === $hook_name ) {
$atomic_apm_wp_stage_spans['content']->end();
}
// First hooked action, start a WP Setup stage
if ( null === $atomic_apm_wp_stage_spans['setup'] ) {
$atomic_apm_wp_stage_spans['setup'] = \Elastic\Apm\ElasticApm::getCurrentTransaction()->beginCurrentSpan(
'Stage: Setup',
'wp',
'wp_stage',
'setup'
);
}
// First call of wp action start content stage
if ( null === $atomic_apm_wp_stage_spans['content'] && 'wp' === $hook_name ) {
$atomic_apm_wp_stage_spans['content'] = \Elastic\Apm\ElasticApm::getCurrentTransaction()->beginCurrentSpan(
'Stage: Content',
'wp',
'wp_stage',
'content'
);
}
##
# Process "real" spans from actual WP Hook calls
##
// Ignore filters
if ( ! isset( $wp_actions[ $hook_name ] ) ) {
return;
}
// Ignore invalid hooks
if ( ! isset( $wp_filter[ $hook_name ] ) || ! is_a( $wp_filter[ $hook_name ] , 'WP_Hook' ) ) {
return;
}
// Ignore if we're not working with a WP_Hook object with callbacks
if ( ! $wp_filter[ $hook_name ]->has_filters() ) {
return;
}
$callback_names = array();
foreach ( $wp_filter[ $hook_name ]->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
$fn = $callback['function'];
if ( $fn instanceof Atomic_APM_Hook_Wrapper ) {
$callback_names[] = atomic_apm_describe_callback( $fn->get_original() );
continue;
}
if ( atomic_apm_is_self_closure( $fn ) ) {
continue;
}
$callback_names[] = atomic_apm_describe_callback( $fn );
}
}
// Ignore if we don't have any hooked callables to show
if ( 0 === count( $callback_names ) ) {
return;
}
if ( null === $value ) {
$arg_display = sprintf( "'%s'", $hook_name );
} else {
$arg_display = sprintf( "'%s', <%s>", $hook_name, gettype( $value ) );
}
// Start Span
$span = \Elastic\Apm\ElasticApm::getCurrentTransaction()->beginCurrentSpan(
"do_action($arg_display)",
'wp',
'wp_hook',
'do_action'
);
// Register the cleanup closure immediately, before any setLabel or
// wrapping work that could throw. If the top-level guard catches a
// later Throwable, this closure still fires at PHP_INT_MAX and closes
// the span. The closure also unregisters itself so it doesn't
// accumulate when the same hook fires repeatedly in a single request.
// Span->end is best-effort so a failing agent doesn't leave us unable
// to remove the closure.
$cleanup = null;
$cleanup = function () use ( $span, &$cleanup, $hook_name ) {
global $wp_filter;
try {
if ( ! $span->hasEnded() ) {
$span->end();
}
} catch ( Throwable $e ) {
}
if ( isset( $wp_filter[ $hook_name ] ) && $cleanup ) {
$wp_filter[ $hook_name ]->remove_filter( $hook_name, $cleanup, PHP_INT_MAX );
}
};
$wp_filter[ $hook_name ]->add_filter( $hook_name, $cleanup, PHP_INT_MAX, 0 );
// Set context for span
foreach ( $callback_names as $callback_idx => $callback_name ) {
if ( $callback_idx > 25 ) {
break;
}
$span->context()->setLabel(
sprintf( 'hooked-%02d', $callback_idx ),
$callback_name
);
// Elastic APM breaks if context is changed too fast so we sleep a bit between each set
usleep( 50 );
}
$span->context()->setLabel( 'hooked-count', count( $callback_names ) );
// Wrap each callback to produce a child span per invocation.
// Runs after the outer hook span begins so wrapper spans nest under
// it, and our own cleanup closure (added just above at PHP_INT_MAX)
// is recognized by is_self_closure and skipped.
foreach ( $wp_filter[ $hook_name ]->callbacks as $priority => &$cb_bucket ) {
foreach ( $cb_bucket as $cb_id => &$cb_entry ) {
$fn = $cb_entry['function'];
if ( $fn instanceof Atomic_APM_Hook_Wrapper || atomic_apm_is_self_closure( $fn ) ) {
continue;
}
$info = atomic_apm_callback_source_info( $fn );
// Variadic spread in the wrapper drops by-reference semantics,
// which would silently break callbacks expecting referenced args
// (e.g. do_action_ref_array consumers). Leave these unwrapped.
if ( $info['has_ref_params'] ) {
continue;
}
$source = null;
if ( $info['file'] ) {
$source = $info['file'];
if ( $info['line'] ) {
$source .= ':' . $info['line'];
}
}
$cb_entry['function'] = new Atomic_APM_Hook_Wrapper(
$fn,
atomic_apm_describe_callback( $fn ),
$info['kind'],
$info['name'],
$source,
$hook_name
);
}
unset( $cb_entry );
}
unset( $cb_bucket );
}
$atomic_apm_wp_stage_spans = array(
'setup' => null,
'content' => null,
);
atomic_add_action( 'all', 'atomic_apm_auto_instrumentation_wp_hook', PHP_INT_MIN, 2 );
register_shutdown_function( function () {
global $atomic_apm_wp_stage_spans;
if ( !is_array( $atomic_apm_wp_stage_spans ) ) {
return;
}
foreach ( $atomic_apm_wp_stage_spans as $maybe_span ) {
if ( $maybe_span instanceof \Elastic\Apm\SpanInterface && !$maybe_span->hasEnded() ) {
$maybe_span->end();
}
}
} );
}
// used for WP CLI when provided with --url arg, see above comment
function maybe_set_wp_home_and_wp_siteurl() {
if ( isset( $_SERVER['SERVER_NAME'] ) ) {
if ( isset( $_SERVER['HTTPS'] ) && 'on' == $_SERVER['HTTPS'] ) {
if ( ! defined( 'WP_HOME' ) ) {
define( 'WP_HOME','https://' . $_SERVER['SERVER_NAME'] );
}
if ( ! defined( 'WP_SITEURL' ) ) {
define( 'WP_SITEURL','https://' . $_SERVER['SERVER_NAME'] );
}
} else {
if ( ! defined( 'WP_HOME' ) ) {
define( 'WP_HOME','http://' . $_SERVER['SERVER_NAME'] );
}
if ( ! defined( 'WP_SITEURL' ) ) {
define( 'WP_SITEURL','http://' . $_SERVER['SERVER_NAME'] );
}
}
}
}
// Set memory limit for WP-CLI requests
// See: https://github.com/wp-cli/wp-cli/issues/5547
atomic_add_action( 'muplugins_loaded', 'atomic_set_memory_limit' );
function atomic_set_memory_limit() {
if ( ! defined( 'WP_MEMORY_LIMIT' ) || ! defined( 'WP_CLI' ) ) {
return;
}
if ( ! is_callable( 'WP_CLI', 'add_hook' ) ) {
return;
}
WP_CLI::add_hook( 'after_wp_load', function() {
WP_CLI::debug( 'Setting memory limit to ' . WP_MEMORY_LIMIT, 'atomic' );
ini_set( 'memory_limit', WP_MEMORY_LIMIT );
} );
}
// Show correct memory limits in Site Health - Info screen
// PHP caches calls to ini_get() in opcache so we can't trust them at run time
// See: https://github.com/php/php-src/issues/8699
atomic_add_filter( 'debug_information', 'atomic_memory_limit_debug' );
function atomic_memory_limit_debug( $info ) {
if ( defined( 'WP_MEMORY_LIMIT' ) && ! empty( $info['wp-server']['fields']['memory_limit']['value'] ) ) {
$info['wp-server']['fields']['memory_limit']['value'] = WP_MEMORY_LIMIT;
}
if ( defined( 'WP_MAX_MEMORY_LIMIT' ) && ! empty( $info['wp-server']['fields']['admin_memory_limit']['value'] ) ) {
$info['wp-server']['fields']['admin_memory_limit']['value'] = WP_MAX_MEMORY_LIMIT;
}
return $info;
}
// Correctly load mu-plugin from a symlinked subdir of mu-plugins.
// The argument must be relative to our subdir of mu-plugins.
// Sample args: 'mail.php' | 'debug-bar/debug-bar.php'
function load_atomic_mu_plugin( $relative_path ) {
$abs_path = ATOMIC_MU_PLUGIN_DIR . '/' . $relative_path;
include_once( $abs_path );
}
// Plugins outside of WP_CONTENT_DIR always get bad asset URLs.
atomic_add_filter( 'plugins_url', 'atomic_symlinked_plugins_url', 0, 3 );
function atomic_symlinked_plugins_url( $url, $path, $plugin ) {
$url = preg_replace(
// The subpattern eats multisite path prefixes. It uses a
// lookbehind assertion to avoid consuming a prefix preceded
// by a double slash; this preserves the hostname.
'#((?<!/)/[^/]+)*/wp-content/plugins/wordpress/mu-plugins/#',
'/wp-content/mu-host-plugins/',
$url
);
return $url;
}
// See: http://php.net/manual/en/class.sessionhandlerinterface.php
final class AtomicSessionHandler implements SessionHandlerInterface {
private $save_path = '';
private $name = '';
private $mc = null;
#[\ReturnTypeWillChange]
public function destroy( $session_id ) {
if ( $this->mc === null )
return false;
return $this->mc->delete( $this->key( $session_id ) );
}
#[\ReturnTypeWillChange]
public function read( $session_id ) {
if ( $this->mc === null )
return '';
$data = $this->mc->get( $this->key( $session_id ) );
if ( false === $data ) {
$data = '';
}
return $data;
}
#[\ReturnTypeWillChange]
public function write( $session_id, $session_data ) {
if ( $this->mc === null )
return false;
return $this->mc->set( $this->key( $session_id ), $session_data, ini_get( 'session.gc_maxlifetime' ) );
}
private function key( $session_id ) {
return sprintf(
'%s:_s2_:%s:%s',
WP_CACHE_KEY_SALT,
substr( md5( sprintf( ":%s:%s:", $this->save_path, $this->name ) ), 10, 15 ),
md5( $session_id ) );
}
#[\ReturnTypeWillChange]
public function close() {
$this->save_path = '';
$this->name = '';
$this->mc = null;
return true;
}
#[\ReturnTypeWillChange]
public function open( $save_path, $name ) {
global $memcached_servers;
$this->save_path = $save_path;
$this->name = $name;
$this->mc = new Memcached;
$this->mc->setOption( Memcached::OPT_COMPRESSION, true );
if ( Memcached::HAVE_IGBINARY && extension_loaded( 'igbinary' ) ) {
$this->mc->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
} else {
$this->mc->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP );
}
foreach( $memcached_servers as $server ) {
// Most of this borrowed from wp-content/object-cache.php
if ( 'unix://' == substr( $server, 0, 7 ) ) {
$node = substr( $server, 7 );
$port = 0;
} else {
list ( $node, $port ) = explode( ':', $server );
$port = intval( $port );
if ( ! $port ) {
$port = 11211;
}
}
$this->mc->addServer( $node, $port );
return true;
}
error_log( "AtomicSessionHandler failed to connect to any server" );
$this->mc = null;
return false;
}
#[\ReturnTypeWillChange]
public function gc( $maxlifetime ) {
return true;
}
}
// By default store sessions in memcached instead of the filesystem
$atomic_session_handler = new AtomicSessionHandler();
session_set_save_handler( $atomic_session_handler, true );
/**
* _atomic_platform_managed_symlink returns the path to use for creating a symlink to the managed version of specific software.
* The path is from the location of the ling being linked, and so should not normally need to be adjusted.
*
* ['drop-in', 'object-cache.php'] => ../../wordpress/drop-ins/object-cache.php
* ['mu-plugin', 'debug-bar-loader.php'] => ../../../wordpress/mu-plugins/debug-bar-loader.php
* ['mu-plugin', 'debug-bar'] => ../../../wordpress/mu-plugins/debug-bar
* ['plugin', 'akismet'] => ../../../wordpress/plugins/akismet/latest
* ['plugin', 'akismet', '5.2'] => ../../../wordpress/plugins/akismet/5.2
* ['theme', 'twentyseventeen'] => ../../../wordpress/themes/twentyseventeen/latest
* ['theme', 'twentyseventeen', '3.2'] => ../../../wordpress/themes/twentyseventeen/3.2
* ['theme', 'blahtheme', 'special'] => ../../../wordpress/themes/special/blahtheme (legacy use case only returned if exists)
*
* @param string $kind one of drop-in(s), mu-pliugin(s), plugin(s), or theme(s)
* @param string $thing the name of the managed thing. hello-dolly, or twentynineteen for example
* @param string $version the version of the thing (defaults to "latest" which should almost always be used.)
*
* @return mixed string on success, false on failure
*/
function _atomic_platform_managed_symlink( $kind, $thing, $version = 'latest' ) {
static $symlink_root = null;
if ( null === $symlink_root ) {
$symlink_root = _atomic_platform_managed_symlink_root();
}
$kind = rtrim( $kind, 's' );
switch( $kind ) {
case 'drop-in':
// linked to from htdocs/wp-content, ../puts the link target in htdocs, $symlink_root handles where to go below that
return sprintf( '../%s/%ss/%s', $symlink_root, $kind, $thing );
case 'mu-plugin':
// linked to from htdocs/wp-content/mu-plugins, ../../puts the link target in htdocs, $symlink_root handles where to go below that
return sprintf( '../../%s/%ss/%s', $symlink_root, $kind, $thing );
case 'plugin':
// linked to from htdocs/wp-content/plugins, ../../puts the link target in htdocs, $symlink_root handles where to go below that
return sprintf( '../../%s/%ss/%s/%s', $symlink_root, $kind, $thing, $version );
case 'theme':
// linked to from htdocs/wp-content/themes, ../../puts the link target in htdocs, $symlink_root handles where to go below that
$maybe = sprintf( '../../%s/%ss/%s/%s', $symlink_root, $kind, $version, $thing );
if ( file_exists( sprintf( '/srv/htdocs/wp-content/themes/%s', $maybe ) ) ) {
return $maybe;
}
return sprintf( '../../%s/%ss/%s/%s', $symlink_root, $kind, $thing, $version );
default:
return false;
}
}
/**
* _atomic_platform_managed_symlink_root determines where a site should symlink to for managed code
* from htdocs. For v1 link layouts this will be ../../wordpress, and for v2 this will be ../wordpress.
*
* @return string path to wordpress from htdocs
*/
function _atomic_platform_managed_symlink_root() {
static $default = '../wordpress';
if ( true !== file_exists( '/srv/htdocs/__wp__' ) ) {
return $default;
}
// dev / dereferenced sites... Default to the safest option...
if ( true !== is_link( '/srv/htdocs/__wp__' ) ) {
return $default;
}
// v1 will point to ../../wordpress/core/{version}, return ../../wordpress
// v2 will point to ../wordpress/core/{version} , return ../wordpress
return dirname( readlink( '/srv/htdocs/__wp__' ), 2 );
}