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 ) ) {
function atomic_apm_auto_instrumentation_wp_hook( $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 ) {
if ( is_string( $callback['function'] ) ) {
$callback_names[] = $callback['function'] . '()';
} elseif ( is_array( $callback['function'] ) && is_string( $callback['function'][0] ) ) {
$callback_names[] = $callback['function'][0] . '::' . $callback['function'][1] . '()';
} elseif ( is_array( $callback['function'] ) && is_object( $callback['function'][0] ) ) {
$callback_names[] = get_class( $callback['function'][0] ) . '->' . $callback['function'][1] . '()';
} elseif ( $callback['function'] instanceof Closure ) {
$reflection = new ReflectionFunction( $callback['function'] );
if ( '/scripts/env.php' === $reflection->getFileName() ) {
continue; // Hide thyself
}
$callback_names[] = sprintf(
'[%s:%d-%d]',
$reflection->getFileName(),
$reflection->getStartLine(),
$reflection->getEndLine()
);
} elseif ( is_object( $callback['function'] ) && method_exists( $callback['function'], '__invoke' ) ) {
$callback_names[] = get_class( $callback['function'] ) . '->__invoke()';
} else {
$callback_names[] = '[unknown]';
}
}
}
// 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'
);
// 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 ) );
// Add a callback to end the span at lowest priority
$wp_filter[ $hook_name ]->add_filter(
$hook_name,
function () use ( $span ) {
if ( !$span->hasEnded() ) {
$span->end();
}
},
PHP_INT_MAX,
0
);
}
$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, MEMCACHE_COMPRESSED, ini_get( 'session.gc_maxlifetime' ) );
}
private function key( $session_id ) {
return sprintf(
'%s:_s_:%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 = '';
if ( $this->mc !== null ) {
$this->mc->close();
$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 Memcache;
$success = false;
foreach( $memcached_servers as $server ) {
// Most of this borrowed from wp-content/object-cache.php
if ( 'unix://' == substr( $server, 0, 7 ) ) {
$node = $server;
$port = 0;
} else {
list ( $node, $port ) = explode(':', $server);
if ( !$port )
$port = ini_get('memcache.default_port');
$port = intval($port);
if ( !$port )
$port = 11211;
}
if ( $this->mc->connect( $node, $port ) ) {
$this->mc->setCompressThreshold(20000, 0.2);
return true;
}
error_log( "AtomicSessionHandler failed to connect to $server" );
}
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 );
}