File: /wordpress/mu-plugins/atomic-platform.php
<?php
// Atomic platform MU Plugin for platform specific necessities
/*
// When updating to a new version of WordPress that includes a schema
// change the db schema version gets changed. This causes wordpress to
// disallow admin logins until the schema has been updated. This bit
// of code disables that feature, allowing admins to log in, and us to
// update the db schema without user intervention.
add_filter( 'option_db_version', function( $v ) {
if ( ( defined( 'WP_CLI' ) && WP_CLI ) || 'cli' === PHP_SAPI ) {
// If we are in wp-cli then we do not want to change the version,
// so that we can run wp core update-db, and "finish" the update
return $v;
}
static $return_version = null;
if ( null === $return_version ) {
include( ABSPATH . WPINC . '/version.php' );
$return_version = $wp_db_version;
}
return $return_version;
} );
// */
// Protection from accidental double loading.
if ( ! class_exists( 'Atomic_Service_Private_Sites' ) ) {
class Atomic_Service_Private_Sites {
// Temporary place to save $wp_filesystem global while we replace it
// to support WP deleting managed plugin symlinks in its delete_plugins() function.
// See add_plugin_deletion_workaround() and remove_plugin_deletion_workaround() methods for usage.
private $plugin_deletion_original_wp_filesystem = false;
function __construct() {
$model = constant( 'AT_PRIVACY_MODEL' );
if ( ! $model && 'LOGGEDIN' === constant( 'PHP_FS_PERMISSIONS' ) ) {
$model = 'PHP_FS_PERMISSIONS_LOGGEDIN';
}
if ( ! $model ) {
return;
}
// Only works when wp is loaded (meaning add_action is present)
if ( ! function_exists( 'add_action' ) ) {
return;
}
// Only providing a single endpoint
if ( '/_atomic.private.wp.auth.check' !== $_SERVER['REQUEST_URI'] ) {
return;
}
// We want to load after all the plugins, and themes, to allow for maximum
// reasonable interaction with the stock wp authentication "stuff" so that
// when we ask if the user is logged in we should be reasonably sure of
// getting a valid answer even taking into consideration custom code.
add_action( 'wp_loaded', array( 'Atomic_Service_Private_Sites', 'authentication_api' ), 0, 0 );
switch( $model ) {
case 'wp_uploads':
// Code specific to this privacy model.
// NOTE: Site mu-plugins currently load before platform mu-plugins,
// so we use an earlier-than-default priority to make sure this filter can influence
// the default access decision before site-specific filters run at the default priority.
add_filter(
'atomic_service_authentication_api_intercept',
array( 'Atomic_Service_Private_Sites', 'authentication_api_wp_uploads' ),
0
);
break;
}
}
public static function authentication_api_wp_uploads( $data ) {
if ( empty( $_SERVER['HTTP_X_ORIGINAL_URI'] ) ) {
return $data;
}
// Strip excess / characters.
$uri_to_check = '/' . ltrim( $_SERVER['HTTP_X_ORIGINAL_URI'], '/' );
// Strip _photon_cache_bust request prefix.
if ( 0 == strpos( $uri_to_check, '/_photon_cache_bust/', 0 ) ) {
$uri_to_check = preg_replace( '#^/_photon_cache_bust/\d+(/.*)$#', '$1', $uri_to_check );
}
$upload_dir_info = wp_get_upload_dir();
$upload_dir_uri = parse_url( $upload_dir_info['baseurl'], PHP_URL_PATH );
if ( 0 !== strpos( $uri_to_check, $upload_dir_uri, 0 ) ) {
// Allow anything outside of uploads...
$data['response'] = 'HTTP/1.0 202 Accepted';
$data['headers']['X-Atomic-Access'] = 'public';
}
return $data;
}
public static function authentication_api() {
// Atomic will request this URI from the site to validate requests against
// potentially private resources
// Clear out any output buffers. Just in case.
while( ob_get_level() > 0 ) {
ob_end_clean();
}
$origin = '/';
if ( ! empty( $_SERVER['HTTP_X_ORIGINAL_URI'] ) ) {
$origin = $_SERVER['HTTP_X_ORIGINAL_URI'];
}
// Default response data. Unauthenticated and denied by default.
$data = array(
'origin' => $origin,
'logged_in' => is_user_logged_in(),
'response' => 'HTTP/1.0 403 Forbidden',
'headers' => array(
'X-Atomic-Authenticated' => 'false',
'X-Atomic-Access' => 'denied',
),
);
if ( $data['logged_in'] ) {
// If we are logged in: then authenticated, but private by default
// (allow access, but this is not a public resource, so no caching,
// especially no caching without attaching to this particular cookie)
$data['response'] = 'HTTP/1.0 202 Accepted';
$data['headers']['X-Atomic-Authenticated'] = 'true';
$data['headers']['X-Atomic-Access'] = 'private';
}
// Extensibility. Allow overriding of this stuff via a filter. This
// will allow site owners, and clients, greater control over what is and
// is not allowed when a site is using this privacy model
$data = apply_filters( 'atomic_service_authentication_api_intercept', $data );
// Send the HTTP response line
header( $data['response'] );
// Send all the headers
foreach( $data['headers'] as $header => $value ) {
header( sprintf( '%s: %s', $header, $value ) );
}
// That's all, folks.
die();
}
}
new Atomic_Service_Private_Sites();
} // if ( ! class_exists( 'atomic_service_private_sites' ) )
// protection from accidental double loading
if ( ! class_exists( 'Atomic_Service_Media_Settings' ) ) {
class Atomic_Service_Media_Settings {
const PATH_PREFIX = '/_atomic.media.settings.subsize.crop?';
function __construct() {
// Only works when wp is loaded (meaning add_action is present)
if ( ! function_exists( 'add_action' ) ) {
return;
}
// Only providing a single endpoint
if ( 0 !== strncmp( $_SERVER['REQUEST_URI'], self::PATH_PREFIX, strlen( self::PATH_PREFIX ) ) ) {
return;
}
// We want to load after all the plugins and themes have a chance to register intermediate image sizes
add_action( 'wp_loaded', array( 'Atomic_Service_Media_Settings', 'subsize_crop_api' ), 0, 0 );
}
public static function subsize_crop_api() {
$subsize_dimensions = substr( $_SERVER['REQUEST_URI'], strlen( self::PATH_PREFIX ) );
$is_cropped = static::is_subsize_cropped( $subsize_dimensions );
// Keep a low max-age because we want users to see different image
// cropping behavior soon after changing themes, plugins, or settings
header( 'Cache-Control: max-age: 300' );
header( 'Content-Type: application/json' );
echo $is_cropped ? '1' : '0';
die();
}
public static function is_subsize_cropped( $subsize_dimensions ) {
if ( empty( $subsize_dimensions ) ) {
return false;
}
$matches = array();
if ( ! preg_match( '/^(?<width>\d+)x(?<height>\d+)$/', $subsize_dimensions, $matches ) ) {
return false;
}
$width = (int) $matches['width'];
$height = (int) $matches['height'];
foreach ( wp_get_registered_image_subsizes() as $subsize ) {
if (
! isset( $subsize['width'], $subsize['height'], $subsize['crop'] ) ||
! ( $subsize['width'] === $width && $subsize['height'] === $height )
) {
continue;
}
if ( is_bool( $subsize['crop'] ) ) {
return $subsize['crop'];
} else if (
is_array( $subsize['crop'] ) && 2 === count( $subsize['crop'] ) &&
in_array( $subsize['crop'][0], array( 'left', 'center', 'right' ) ) &&
in_array( $subsize['crop'][1], array( 'top', 'center', 'bottom' ) )
) {
// NOTE: We do not fully honor these detailed crop settings,
// but we can at least treat them as cropped
return true;
} else {
// The crop setting has an unexpected value. Default to no crop.
return false;
}
}
return false;
}
}
new Atomic_Service_Media_Settings();
} // if ( ! class_exists( 'Atomic_Service_Media_Settings' ) ) {
if ( ! class_exists( 'Atomic_Photon_Heic_Support' ) ) {
/**
* Class Atomic_Photon_Heic_Support
*
* HEIC/HEIC support for WP Cloud sites.
*/
class Atomic_Photon_Heic_Support {
function __construct() {
// Return early if disabled by user.
if ( defined( 'WPCLOUD_DISABLE_HEIC_CONVERSION' ) && WPCLOUD_DISABLE_HEIC_CONVERSION ) {
return;
}
// Make sure WP is loaded.
if ( ! function_exists( 'add_filter' ) ) {
return;
}
// We can't do much unless Photon_OpenCV is available.
if ( ! class_exists( 'Photon_OpenCV' ) ) {
return;
}
// Add filters.
add_filter( 'upload_mimes', array( $this, 'add_mimes' ), 11 );
add_filter( 'plupload_default_settings', array( $this, 'disable_upload_error' ), 11 );
// Give a bit of space for other plugins to hook into this.
add_filter( 'wp_handle_upload_prefilter', array( $this, 'handle_heic_upload' ), 15 );
}
/**
* Add HEIF/HEIC to the list of supported upload mime types
*
* @param array $mimes The list of supported mime types.
* @return array The modified list of supported mime types.
*/
public function add_mimes( $mimes ) {
if ( is_array( $mimes ) ) {
if ( ! isset( $mimes['heic'] ) ) {
$mimes['heic'] = 'image/heic';
}
if ( ! isset( $mimes['heif'] ) ) {
$mimes['heif'] = 'image/heif';
}
}
return $mimes;
}
/**
* Disable the upload error for HEIC/HEIF files.
*
* @param array $defaults The default settings.
* @return array The modified settings.
*/
public function disable_upload_error( $defaults ) {
$defaults[ 'heic_upload_error' ] = false;
return $defaults;
}
/**
* Convert HEIF/HEIC uploads to JPEG.
*
* @param array $file The file array.
* @return array The file array.
*/
public function handle_heic_upload( $file ) {
if ( ! is_array( $file ) ) {
return $file;
}
if ( false === $this->maybe_convert_to_jpg( $file['tmp_name'] ) ) {
return $file;
}
// tmp_name is reused, cache needs to be cleared.
clearstatcache();
$new_file = array(
'name' => pathinfo( $file['name'], PATHINFO_FILENAME ) . '.jpg',
'type' => 'image/jpeg',
'tmp_name' => $file['tmp_name'],
'error' => 0,
'size' => filesize( $file['tmp_name'] ),
);
return $new_file;
}
/**
* Try to convert HEIF/HEIC files to JPEG.
* This function uses Photon_OpenCV to convert the files.
*
* @param string $filename The filename of the file to convert.
* @return bool True if the file was converted, false otherwise.
*/
private function maybe_convert_to_jpg( $filename ) {
// Double check if Photon_OpenCV is available since this is when we use the class.
if ( ! class_exists( 'Photon_OpenCV' ) ) {
return false;
}
if ( ! file_exists( $filename ) ) {
return false;
}
$valid_magic_bytes = array(
'ftypheic',
'ftypheix',
'ftyphevc',
'ftypheim',
'ftypheis',
'ftyphevm',
'ftyphevs',
'ftypmif1',
'ftypmsf1',
);
// Read the first 8 bytes of the file.
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$magic_bytes = file_get_contents( $filename, false, null, 4, 8 );
if ( false === in_array( $magic_bytes, $valid_magic_bytes, true ) ) {
return false;
}
$img = new Photon_OpenCV();
try {
$img->readimage( $filename );
} catch ( Exception $e ) {
return false;
}
$img->setimageformat( 'jpg' );
// This should never fail.
$img->writeimage( $filename );
return true;
}
}
new Atomic_Photon_Heic_Support();
} // if ( ! class_exists( 'Atomic_Photon_Heic_Support' ) )
// Protection from accidental double loading
if ( ! class_exists( 'Atomic_Platform_Suppress_Yoast_Indexable_Query_Errors' ) ) {
// See: http://wp.me/p9o2xV-1Nk/
class Atomic_Platform_Suppress_Yoast_Indexable_Query_Errors {
public $previous_suppress_errors;
public function __construct() {
add_filter( 'query', array( $this, 'maybe_suppress_query_errors' ) );
}
public function maybe_suppress_query_errors( $query ) {
global $wpdb;
// Queries for these tables sometimes flood the error logs and cause issues
$table_names = array(
"{$wpdb->prefix}yoast_",
"yoast_",
);
$found_table_reference = false;
foreach ( $table_names as $table_name ) {
$table_pattern = '#\b' . preg_quote( $table_name ) . '[\w]*#';
if ( 1 === preg_match( $table_pattern, $query ) ) {
$found_table_reference = true;
}
}
if ( $found_table_reference ) {
// It's possible that we will suppress errors for multiple queries in a row,
// so we only want to remember the value immediately before starting suppression.
if ( ! isset( $this->previous_suppress_errors ) ) {
$this->previous_suppress_errors = $wpdb->suppress_errors;
}
$wpdb->suppress_errors = true;
} else {
if ( isset( $this->previous_suppress_errors ) ) {
$wpdb->suppress_errors = $this->previous_suppress_errors;
// Forget the previous suppression value so it can be saved again the next time
// and so we only restore the value immediately after stopping suppression.
unset( $this->previous_suppress_errors );
}
}
return $query;
}
}
}
if ( ! class_exists( 'Atomic_Platform_Limit_ActionScheduler' ) ) {
// See https://wp.me/p9o2xV-2p0
class Atomic_Platform_Limit_ActionScheduler {
public static function maximum_execution_time_likely_to_be_exceeded( $likely_to_be_exceeded, $as_queuerunner, $processed_actions, $execution_time, $max_execution_time ) {
self::processed_actions( $processed_actions );
if ( ! $likely_to_be_exceeded ) {
// Action Scheduler multiplies $time_per_action * 3; we multiply
// total $execution_time by 1.33 to recover time for batch cleanup
$estimated_time = $execution_time * 1.33;
if ( $estimated_time > $max_execution_time ) {
return true;
}
}
return $likely_to_be_exceeded;
}
public static function processed_actions( $_processed_actions = null ) {
static $processed_actions = null;
if ( ! is_null( $_processed_actions ) ) {
$processed_actions = max( (int) $_processed_actions, (int) $processed_actions );
}
return $processed_actions;
}
public static function after_process_queue() {
remove_action( 'action_scheduler_after_process_queue', array( __CLASS__, 'after_process_queue' ), 10 );
if ( class_exists( 'ActionScheduler_QueueCleaner' ) ) {
add_filter( 'action_scheduler_cleanup_batch_size', array( __CLASS__, 'cleanup_batch_size' ), 99 );
add_filter( 'action_scheduler_retention_period', array( __CLASS__, 'retention_period' ), 99 );
$queue_cleaner = new ActionScheduler_QueueCleaner();
$queue_cleaner->delete_old_actions();
remove_filter( 'action_scheduler_cleanup_batch_size', array( __CLASS__, 'cleanup_batch_size' ), 99 );
remove_filter( 'action_scheduler_retention_period', array( __CLASS__, 'retention_period' ), 99 );
}
add_action( 'action_scheduler_after_process_queue', array( __CLASS__, 'after_process_queue' ), 10 );
}
public static function cleanup_batch_size( $batch_size ) {
$processed_actions = self::processed_actions();
if ( ! is_null( $processed_actions ) && ! empty( $processed_actions ) ) {
$_batch_size = $processed_actions * 20;
if ( $_batch_size > $batch_size ) {
$batch_size = min( 100000, $_batch_size );
}
}
return $batch_size;
}
public static function retention_period( $seconds ) {
$max_actions_retained = 100000;
$processed_actions = self::processed_actions();
if ( ! is_null( $processed_actions ) && ! empty( $processed_actions ) && ( $processed_actions > 100 ) ) {
$seconds = max( HOUR_IN_SECONDS, (int) ( MINUTE_IN_SECONDS * ( $max_actions_retained / $processed_actions ) ) );
}
return $seconds;
}
}
}
if ( ! class_exists( 'Atomic_Platform_Mu_Plugin' ) ) {
final class Atomic_Platform_Mu_Plugin {
// Used for storing any admin notices we want to display due to filters making changes (e.g. deactivating plugins)
private $admin_notices = array();
private $request_log = array();
// Single option limiter admin notices cache key
private $single_option_limiter_notices_key = 'atomic_single_option_limiter_notices';
private $logstash_endpoint = 'https://public-api.wordpress.com/rest/v1.1/logstash';
// Plugins which will be deactivated upon activation, with a notice.
// The notice must be safe HTML.
private $disallowed_plugins = array(
'bwp-minify/bwp-minify.php' => 'BWP Minify (as of 1.3.3) is not ready for use. The plugin writes a configuration file that must be edited manually to support plugins and themes installed via symlinks. Because it breaks sites upon activation, we have automatically deactivated the plugin to keep your site working. In the interest of making BWP Minify compatible, we provided <a href="https://github.com/OddOneOut/bwp-minify/pull/67">this patch</a> to the author in May 2016. If you choose to fix the configuration file yourself, you may skip the automatic deactivation by renaming <code>bwp-minify/bwp-minify.php</code>.',
'e-mail-broadcasting/e-mail-broadcasting.php' => 'The use of "E-Mail Broadcasting" is not allowed.',
'send-email-from-admin/send-email-from-admin.php' => 'The use of "Send Email From Admin" is not allowed.',
'mailit/mailit.php' => 'The use of "Mail It!" is not allowed.',
'nginx-helper/nginx-helper.php' => 'The use of Nginx Helper can interfere with caching, which is automatically provided for this site. Nginx Helper has been deactivated.',
'stopbadbots/stopbadbots.php' => 'The use of Stop Bad Bots is not allowed.',
'w3-total-cache/w3-total-cache.php' => 'The use of W3 Total Cache can interfere with caching, which is automatically provided for this site. W3 Total Cache has been deactivated.',
'wp-fastest-cache/wpFastestCache.php' => 'The use of WP Fastest Cache can interfere with caching, which is automatically provided for this site. WP Fastest Cache has been deactivated.',
'wp-super-cache/wp-cache.php' => 'The use of WP Super Cache can interfere with caching, which is automatically provided for this site. WP Super Cache has been deactivated.',
'wp-rest-api-log/wp-rest-api-log.php' => 'WP REST API Log inflates post table size beyond normal usage levels.',
'website-file-changes-monitor/website-file-changes-monitor.php' => 'Melapress File Monitor inflates the options table size beyond normal usage levels.',
);
function __construct() {
if ( 'cli' !== php_sapi_name() ) {
// Disable update notifications for managed plugins & themes
add_filter( 'site_transient_update_plugins', array( $this, 'site_transient_update_plugins' ) );
add_filter( 'site_transient_update_themes', array( $this, 'site_transient_update_themes' ) );
} else {
// Disable making a request during 'wp core install'
add_filter( 'pre_http_request', array( $this, 'preempt_wp_core_install_permalink_request' ), 0, 3 );
}
if ( AT_DEREFERENCED_SOFTWARE ) {
// These sites were given their own copies of WP core for development purposes,
// and we want them to auto-update.
add_filter( 'auto_update_core', '__return_true' );
add_filter( 'allow_major_auto_core_updates', '__return_true' );
add_filter( 'allow_minor_auto_core_updates', '__return_true' );
} else {
// These sites use managed copies of core, so we do not allow core auto updates.
// Disable auto updates.
add_filter( 'auto_update_core', '__return_false' );
// Disable nightlies and major updates.
add_filter( 'allow_dev_auto_core_updates', '__return_false' );
add_filter( 'allow_major_auto_core_updates', '__return_false' );
// Don't display any failed update notices.
add_filter( 'option_auto_core_update_failed', '__return_false' );
}
// Disable auto updates for managed plugins and themes.
add_filter( 'auto_update_plugin', array( $this, 'deny_auto_update_managed_plugin' ), 10, 2 );
add_filter( 'auto_update_theme', array( $this, 'deny_auto_update_managed_theme' ), 10, 2 );
// Provide auto update UI for managed plugins and themes
add_filter( 'plugin_auto_update_setting_html', array( $this, 'auto_update_setting_html_for_managed_plugin' ), 10, 2 );
add_filter( 'wp_prepare_themes_for_js', array( $this, 'add_management_to_themes_for_js' ) );
add_filter( 'theme_auto_update_setting_template', array( $this, 'auto_update_setting_template_for_managed_theme' ) );
// Provide auto update strings for Site Health page
add_filter( 'plugin_auto_update_debug_string', array( $this, 'plugin_auto_update_debug_string' ), 10, 2 );
add_filter( 'theme_auto_update_debug_string', array( $this, 'theme_auto_update_debug_string' ), 10, 2 );
// Avoid emailing about auto updates because we don't email about Atomic-managed plugin updates
add_filter( 'automatic_updates_send_debug_email', '__return_false' );
add_filter( 'auto_core_update_send_email', '__return_false' );
add_filter( 'auto_plugin_update_send_email', '__return_false' );
add_filter( 'auto_theme_update_send_email', '__return_false' );
// Exclude managed plugins and themes from auto update lists
add_filter( 'option_auto_update_plugins', array( $this, 'exclude_managed_from_auto_update_plugins_option' ) );
add_filter( 'option_auto_update_themes', array( $this, 'exclude_managed_from_auto_update_themes_option' ) );
add_filter( 'pre_update_option_auto_update_plugins', array( $this, 'exclude_managed_from_auto_update_plugins_option' ) );
add_filter( 'pre_update_option_auto_update_themes', array( $this, 'exclude_managed_from_auto_update_themes_option' ) );
// Removes delete link for managed (shared) plugins.
add_filter( 'plugin_action_links', array( $this, 'disable_deletion_of_required_plugins' ), 10, 4 );
// Add support for WP deleting managed plugin symlinks in its delete_plugins() function.
add_action( 'delete_plugin', array( $this, 'add_plugin_deletion_workaround' ) );
add_action( 'deleted_plugin', array( $this, 'remove_plugin_deletion_workaround' ) );
// Disable WordPress core Updating.
add_filter( 'user_has_cap', array( $this, 'user_has_cap' ), 1, 2 );
add_action( 'admin_init', array( $this, 'remove_update_nag' ), 0 );
// Remove version info from head and feeds.
add_filter( 'the_generator', array( $this, 'complete_version_removal' ) );
// Drop duplicate recurring cron events.
add_filter( 'schedule_event', array( $this, 'cron_drop_recurring_duplicates' ) );
// Potential fix for alloptions caching.
add_action( 'added_option', array( $this, 'maybe_clear_alloptions_cache' ) );
add_action( 'updated_option', array( $this, 'maybe_clear_alloptions_cache' ) );
add_action( 'deleted_option', array( $this, 'maybe_clear_alloptions_cache' ) );
// Potential fix for race condition in notoptions caching.
add_action( 'add_option', array( $this, 'maybe_clear_notoptions_cache' ), 10, 2 );
// Disallow bulk plugin deletion
// WSOD when bulk-deleting Jetpack http://wp.me/p72Wkx-hX
// However, the issue is bigger than that: bulk deletion is just bad.
// Related core tickets: 36704, 36705, 36709, 36710
add_action( 'bulk_actions-plugins', array( $this, 'bulk_actions_plugins' ) );
// Avoid expected Site Health warnings to reduce support costs and user confusion
add_action( 'site_status_tests', array( $this, 'avoid_expected_site_health_warnings' ) );
// Exclude specific PHP extension warnings to give us an opportunity to see future
// warnings that we might not expect
add_action( 'site_status_test_php_modules', array( $this, 'avoid_expected_site_health_php_extension_warnings' ) );
// Keep the alloptions size in check.
add_filter( 'pre_cache_alloptions', array( $this, 'limit_alloptions_size' ) );
// Keep total bytes written by single option updates at 600MB / 60 secs.
add_filter( 'pre_update_option', array( $this, 'limit_single_option_size' ), PHP_INT_MAX, 3 );
// Admin notices for limited option updates.
add_action( 'admin_init', array( $this, 'single_option_limiter_notices' ) );
// Deal with disallowed plugins on the platform.
add_action( 'admin_init', array( $this, 'deactivate_disallowed_plugins' ), 0 );
// Replace "Activate" plugin link for plugins that should not be activated (plugins.php)
add_filter( 'plugin_action_links', array( $this, 'disable_plugin_activate_link' ), 0, 4 );
add_filter( 'network_admin_plugin_action_links', array( $this, 'disable_plugin_activate_link' ), 0, 4 );
// Replace "Install" plugin link for plugins that not should not be activated (plugin-install.php)
add_filter( 'plugin_install_action_links', array( $this, 'disable_plugin_install_link' ), 0, 2 );
// Disallow activating known bad plugins
add_action( 'activate_plugin', array( $this, 'disable_known_bad_plugins' ) );
// Prints $admin_notices. Used for disallowed plugins at the moment.
add_action( 'admin_notices', array( $this, 'print_admin_notices' ) );
// Set special X-WP-Logged-In header for use in `nginx lua`. Unset before sending response.
add_filter( 'wp_headers', array( $this, 'add_logged_in_header' ), 0, 1 );
// For log2logstash long running HTTP requests
add_filter( 'http_request_args', array( $this, 'before_http_request' ), 10, 2 );
add_action( 'http_api_debug', array( $this, 'after_http_request' ), 10, 5 );
// Don't redirect if we're only re-encoding query params
add_filter( 'redirect_canonical', array( $this, 'no_redirect_for_reencoded_query_params' ), 10, 2 );
// Add stats params for Jetpack stats
add_filter( 'jetpack_stats_footer_amp_data', array( $this, 'jetpack_stats_footer_amp_data' ), 10, 1 );
add_filter( 'jetpack_stats_footer_js_data', array( $this, 'jetpack_stats_footer_js_data' ), 10, 1 );
// Limit error_log length for authenticate filter
add_filter( 'authenticate', function( $r ) {
ini_set( 'zend.exception_string_param_max_len', 3 );
return $r;
}, 10 );
// Mitigate ActionScheduler for platform.
add_action( 'init', function() {
if ( ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$apd = new Atomic_Persistent_Data();
if ( $apd && $apd->WPCLOUD_MITIGATE_ACTIONSCHEDULER ) {
add_filter( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', array( 'Atomic_Platform_Limit_ActionScheduler', 'maximum_execution_time_likely_to_be_exceeded' ), 10, 5 );
add_action( 'action_scheduler_after_process_queue', array( 'Atomic_Platform_Limit_ActionScheduler', 'after_process_queue' ), 10 );
}
}
}
}, 10 );
// Mitigate X/BoldThemes WordPress 6.2 incompatibility for platform.
add_action( 'after_setup_theme', function() {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$apd = new Atomic_Persistent_Data();
if ( $apd && $apd->WPCLOUD_MITIGATE_WP62_THEME_COMPAT ) {
remove_filter( 'wp_audio_shortcode_library', 'boldthemes_wp_audio_shortcode_library', 10 );
remove_filter( 'wp_audio_shortcode_library', 'x_native_wp_audio_shortcode_library', 10 );
remove_filter( 'wp_video_shortcode_library', 'boldthemes_wp_video_shortcode_library', 10 );
remove_filter( 'wp_video_shortcode_library', 'x_native_wp_video_shortcode_library', 10 );
}
}
}, 10 );
// Mitigate wp_nav_menu infinite loop for platform.
add_filter( 'wp_get_nav_menu_items', function( $items, $menu, $args ) {
foreach ( $items as $index => $item ) {
if ( is_object( $item ) && is_a( $item, 'WP_Post' ) && true === property_exists( $item, 'menu_item_parent' ) && $item->ID == $item->menu_item_parent ) {
$items[ $index ]->menu_item_parent = 0;
}
}
return $items;
}, 10, 3 );
// Mitigate WP 6.9 get_{$adjacent}_post_where filter for platform.
add_action( 'init', function() {
if ( defined( 'WC_PLUGIN_FILE' ) ) {
add_filter( 'get_next_post_where', array( $this, 'wp69_product_adjacent_where' ), 99999, 5 );
add_filter( 'get_previous_post_where', array( $this, 'wp69_product_adjacent_where' ), 99999, 5 );
}
} );
// Mitigate fox-framework infinite option.
// See https://wp.me/p9F6qB-cmz
add_action( 'plugins_loaded', function() {
if ( defined( 'FOX_FRAMEWORK_VERSION' ) ) {
$elementor_cpt_support = get_option( 'elementor_cpt_support' );
if ( is_array( $elementor_cpt_support ) && in_array( 'fox_block', $elementor_cpt_support ) ) {
remove_action( 'init', 'fox_update_elementor_fox_block_support', 1 );
remove_action( 'init', 'fox_update_elementor_cpt_support', 1 );
}
}
} );
// Mitigate Spectra (ultimate-addons-for-gutenberg) for platform.
// See https://wp.me/p9o2xV-2Dk
if ( ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) {
add_filter( 'option_spectra_blocks_count_status', function( $value, $option ) {
if ( 'processing' === $value ) {
add_filter( 'pre_transient_spectra_analytics_action', '__return_true', 10 );
return 'done';
}
}, 10, 2 );
}
// Brute force detection
add_action( 'xmlrpc_login_error', array( $this, 'atomic_set_status_header_on_xmlrpc_failed_login_requests' ) );
// Security: Divi/Extra/Divi Builder
add_action( 'wp_ajax_et_core_portability_import', array( $this, 'et_sp6_harden_json_import' ), 0 );
add_action( 'wp_ajax_et_theme_builder_api_import_theme_builder', array( $this, 'et_sp6_harden_json_import' ), 0 );
// Block signup spam using bkismet
add_filter( 'wp_pre_insert_user_data', array( $this, 'bkismet_check_signup' ), 10, 2 );
// See: http://wp.me/p9o2xV-1Nk/
add_action( 'plugins_loaded', array( $this, 'maybe_suppress_high_volume_yoast_indexable_errors' ) );
// Option Management - elementor_cpt_support https://wp.me/p9o2xV-4gu
add_filter( 'pre_update_option_elementor_cpt_support', array( $this, 'wpcloud_drop_duplicate_values_elementor_cpt_support' ), 10, 1 );
// Retention and Clean-up Policies for common plugins that lead to db growth.
// api-log-pro : "API Log Pro"
add_filter( 'schedule_event', array( $this, 'wpcloud_modify_schedule_api_log_pro' ) );
// wp-security-audit-log : "WP Activity Log"
add_action( 'wsal_init', array( $this, 'wpcloud_init_cleanup_wordpress_security_activity_log' ) );
add_action( 'wpcloud_schedule_cleanup_wsal', array( $this, 'wpcloud_cleanup_wordpress_security_audit_logs' ) );
// wp-time-capsule : WordPress Time Capsule
add_action( 'just_initialized_wptc_h', array( $this, 'wpcloud_manage_wp_time_capsule_activity_log' ) );
add_action( 'wpcloud_schedule_cleanup_wptc', array( $this, 'wpcloud_cleanup_wordpress_time_capsule_activity_logs' ) );
// ai-engine-pro : AI Engine Pro
add_action( 'plugins_loaded', array( $this, 'wpcloud_manage_ai_engine_pro_logs' ) );
add_action( 'wpcloud_schedule_cleanup_aiep', array( $this, 'wpcloud_cleanup_ai_engine_pro_logs' ) );
// Add query comments for request URL and request ID
// https://wp.me/p9o2xV-4uZ
if ( isset( $_SERVER['HTTP_HOST'] ) || isset( $GLOBALS['argv'] ) ) {
add_filter( 'query', function( $query ) {
$comment = '';
if ( ! empty( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) ) {
$comment = ' ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
} else if ( ! empty( $GLOBALS['argv'] ) && is_array( $GLOBALS['argv'] ) ) {
$comment = '$ ' . implode( ' ', $GLOBALS['argv'] );
}
$comment_length = strlen( $comment );
if ( $comment_length > 200 ) {
$comment = substr( $comment, 0, 200 ) . " (Characters: $comment_length)";
}
if ( isset( $_SERVER['HTTP_X_A8C_REQUEST_ID'] ) ) {
$comment = $comment . " request_id: ". $_SERVER['HTTP_X_A8C_REQUEST_ID'];
}
$comment = str_replace( '*', '-*-', $comment );
if ( ! empty( $comment ) ) {
$query = rtrim( $query, ";\t \n\r" ) . ' /* ' . $comment . ' */';
}
return $query;
} );
}
}
function atomic_set_status_header_on_xmlrpc_failed_login_requests( $error ) {
if ( $error instanceof IXR_Error ) {
header( "X-XMLRPC-Error-Code: {$error->code}" );
}
return $error;
}
function get_disallowed_plugins() {
static $disallowed;
if ( empty( $disallowed ) ) {
// Grab client specific disallowed plugins.
$disallowed_client_plugins = apply_filters( 'atomic_disallowed_plugins', [] );
// Always merge with the platform level disallowed plugins.
$disallowed = array_merge(
$disallowed_client_plugins,
$this->disallowed_plugins
);
}
return $disallowed;
}
// Deactivate plugins that should not be activated.
function deactivate_disallowed_plugins() {
foreach ( $this->get_disallowed_plugins() as $plugin => $message ) {
if ( ! is_plugin_active( $plugin ) ) {
continue;
}
deactivate_plugins( array( $plugin ) );
$this->add_admin_notice( $message );
}
}
function add_admin_notice( $message, $class = 'notice notice-error is-dismissible' ) {
$this->admin_notices[] = array( $message, $class );
}
function print_admin_notices() {
foreach ( $this->admin_notices as $notice ) {
?>
<div class="<?php echo esc_attr( $notice[1] ); ?>">
<p><?php echo $notice[0]; ?></p>
</div>
<?php
}
}
function disable_plugin_activate_link( $actions, $plugin_file, $plugin_data, $context ) {
if ( array_key_exists( 'activate', $actions ) && array_key_exists( $plugin_file, $this->get_disallowed_plugins() ) ) {
$actions['activate'] = 'Disabled';
unset( $actions['edit'] );
}
return $actions;
}
function disable_plugin_install_link( $action_links, $plugin ) {
foreach ( $this->get_disallowed_plugins() as $disallowed_plugin => $message ) {
if ( strpos( $disallowed_plugin, $plugin['slug'] ) !== false ) {
$action_links = array();
$action_links[] = '<a class="install-now button button-disabled" href="#">Not Supported</a>';
}
}
return $action_links;
}
function complete_version_removal() {
return '';
}
/**
* Drops duplicate recurring events.
*/
function cron_drop_recurring_duplicates( $event ) {
// Skip non-recurring events.
if ( ! isset( $event->schedule ) ) {
return $event;
}
$event_limit = 0;
// Allow 1 duplicate event if a cron run.
// cron order of operations: reschedule, unschedule, execute
if ( wp_doing_cron() ) {
$event_limit = 1;
}
$crons = (array) _get_cron_array();
$key = md5( serialize( $event->args ) );
$event_count = 0;
foreach ( $crons as $event_timestamp => $cron ) {
// If there is an event with the same args and schedule, treat it as duplicate.
if ( isset( $cron[ $event->hook ][ $key ] ) && $cron[ $event->hook ][ $key ]['schedule'] === $event->schedule ) {
$event_count ++;
if ( $event_count > $event_limit ) {
return false;
}
}
}
return $event;
}
static function is_managed_dir( $dir ) {
return is_dir( $dir ) && is_link( $dir ) && strpos( realpath( $dir ), '/wordpress/' ) === 0;
}
static function is_managed_plugin( $plugin_file ) {
if ( empty( $plugin_file ) || !is_string( $plugin_file ) ) {
return false;
}
$plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin_file );
return self::is_managed_dir( $plugin_dir );
}
static function is_managed_theme( $theme_file ) {
$theme_dir = WP_CONTENT_DIR . '/themes/' . $theme_file;
return self::is_managed_dir( $theme_dir );
}
function is_root_symlink( $dir ) {
return is_dir( $dir ) && is_link( $dir ) && 0 === lstat( $dir )[ uid ];
}
function is_unremovable_plugin( $plugin_file ) {
$plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin_file );
return $this->is_root_symlink( $plugin_dir );
}
function is_unremovable_theme( $theme_file ) {
$theme_dir = WP_CONTENT_DIR . '/themes/' . $theme_file;
return $this->is_root_symlink( $theme_dir );
}
function site_transient_update_plugins( $value ) {
if ( ! is_object( $value ) ) {
return $value;
}
if ( ! isset( $value->response ) || ! is_array( $value->response ) ) {
return $value;
}
foreach ( array_keys( $value->response ) as $plugin_file ) {
if ( $this->is_managed_plugin( $plugin_file ) ) {
unset( $value->response[ $plugin_file ] );
}
}
return $value;
}
function site_transient_update_themes( $value ) {
if ( ! is_object( $value ) ) {
return $value;
}
if ( ! isset( $value->response ) || ! is_array( $value->response ) ) {
return $value;
}
foreach ( array_keys( $value->response ) as $theme_file ) {
if ( $this->is_managed_theme( $theme_file ) ) {
unset( $value->response[ $theme_file ] );
}
}
return $value;
}
function preempt_wp_core_install_permalink_request( $preempt, $r, $url ) {
$first_post = get_page_by_path( sanitize_title( _x( 'hello-world', 'Default post slug' ) ), OBJECT, 'post' );
if ( isset( $first_post ) ) {
$test_url = get_permalink( $first_post->ID );
if ( isset( $test_url ) && $url === $test_url ) {
return true;
}
}
return $preempt;
}
function deny_auto_update_managed_plugin( $allow_update, $item ) {
if ( !empty( $item->plugin ) && $this->is_managed_plugin( $item->plugin ) ) {
return false;
}
return $allow_update;
}
function deny_auto_update_managed_theme( $allow_update, $item ) {
if ( !empty( $item->theme ) && $this->is_managed_theme( $item->theme ) ) {
return false;
}
return $allow_update;
}
function auto_update_setting_html_for_managed_plugin( $html, $plugin_file ) {
if ( $this->is_managed_plugin( $plugin_file ) ) {
$managed_label = apply_filters( 'atomic_managed_plugin_row_auto_update_label', 'Managed by host' );
return '<span class="label">' . esc_html( $managed_label ) . '</span>';
}
return $html;
}
function add_management_to_themes_for_js( $themes ) {
foreach ( $themes as &$theme ) {
$theme['managed_by_host'] = isset( $theme['id'] ) && $this->is_managed_theme( $theme['id'] );
}
return $themes;
}
function auto_update_setting_template_for_managed_theme() {
$managed_label = apply_filters( 'atomic_managed_theme_template_auto_update_label', 'Updates managed by host' );
return '
<div class="theme-autoupdate">
<# if ( data.managed_by_host ) { #>
' . esc_html( $managed_label ) . '
<# } else if ( data.autoupdate.supported ) { #>
<# if ( data.autoupdate.forced === false ) { #>
' . __( 'Auto-updates disabled' ) . '
<# } else if ( data.autoupdate.forced ) { #>
' . __( 'Auto-updates enabled' ) . '
<# } else if ( data.autoupdate.enabled ) { #>
<button type="button" class="toggle-auto-update button-link" data-slug="{{ data.id }}" data-wp-action="disable">
<span class="dashicons dashicons-update spin hidden" aria-hidden="true"></span><span class="label">' . __( 'Disable auto-updates' ) . '</span>
</button>
<# } else { #>
<button type="button" class="toggle-auto-update button-link" data-slug="{{ data.id }}" data-wp-action="enable">
<span class="dashicons dashicons-update spin hidden" aria-hidden="true"></span><span class="label">' . __( 'Enable auto-updates' ) . '</span>
</button>
<# } #>
<# } #>
<# if ( data.hasUpdate ) { #>
<# if ( data.autoupdate.supported && data.autoupdate.enabled ) { #>
<span class="auto-update-time">
<# } else { #>
<span class="auto-update-time hidden">
<# } #>
<br />' . wp_get_auto_update_message() . '</span>
<# } #>
<div class="notice notice-error notice-alt inline hidden"><p></p></div>
</div>
';
}
function plugin_auto_update_debug_string( $string, $plugin_path ) {
if ( $this->is_managed_plugin( $plugin_path ) ) {
return apply_filters( 'atomic_managed_plugin_auto_update_debug_label', 'Updates managed by host' );
}
return $string;
}
function theme_auto_update_debug_string( $string, $theme ) {
if ( $this->is_managed_theme( $theme->stylesheet ) ) {
return apply_filters( 'atomic_managed_theme_auto_update_debug_label', 'Updates managed by host' );
}
return $string;
}
function exclude_managed_from_auto_update_plugins_option( $plugin_files ) {
return array_filter( $plugin_files, function ( $plugin_file ) {
return ! $this->is_managed_plugin( $plugin_file );
} );
}
function exclude_managed_from_auto_update_themes_option( $themes ) {
return array_filter( $themes, function ( $theme ) {
return ! $this->is_managed_theme( $theme );
} );
}
function disable_deletion_of_required_plugins( $actions, $plugin_file, $plugin_data, $context ) {
if ( array_key_exists( 'delete', $actions ) ) {
$path = trailingslashit( WP_PLUGIN_DIR ) . dirname( $plugin_file );
$stat = null;
if ( is_link( $path ) ) {
// Use lstat() so we get statistics from the symlink
$stat = lstat( $path );
}
$owned_by_site = isset( $stat['uid'] ) && defined( 'ATOMIC_SITE_ID' ) && ATOMIC_SITE_ID == $stat['uid'];
if ( $this->is_managed_plugin( $plugin_file ) && ! $owned_by_site ) {
unset( $actions['delete'] );
}
}
return $actions;
}
function add_plugin_deletion_workaround( $plugin_file ) {
if ( ! static::is_managed_plugin( $plugin_file ) ) {
return;
}
if ( empty( $GLOBALS['wp_filesystem'] ) ) {
return;
}
$this->plugin_deletion_original_wp_filesystem = $GLOBALS['wp_filesystem'];
if ( ! class_exists( 'WP_Filesystem_Direct' ) ) {
return;
}
$GLOBALS['wp_filesystem'] = new class( false /* unused arg */ ) extends WP_Filesystem_Direct {
public function delete( $file, $recursive = false, $type = false ) {
// Remove trailing slash because it causes symlinks to be dereferenced.
// Example:
// - is_link( 'wp-content/<symlink>/' ) === false
// - is_link( 'wp-content/<symlink>' ) === true
$file = untrailingslashit( $file );
$plugins_dir = trailingslashit( WP_PLUGIN_DIR );
$realpath = realpath( $file );
if (
strlen( $plugins_dir ) < strlen( $file ) &&
0 === strncmp( $plugins_dir, $file, strlen( $plugins_dir ) ) &&
is_link( $file ) &&
0 === strncmp( $realpath, '/wordpress/plugins/', strlen( '/wordpress/plugins/' ) )
) {
return unlink( $file );
} else {
return parent::delete( $file, $recursive, $type );
}
}
};
}
function remove_plugin_deletion_workaround( $plugin_file ) {
if ( ! static::is_managed_plugin( $plugin_file ) ) {
return;
}
if ( ! empty( $this->plugin_deletion_original_wp_filesystem ) ) {
$GLOBALS['wp_filesystem'] = $this->plugin_deletion_original_wp_filesystem;
$this->plugin_deletion_original_wp_filesystem = false;
}
}
/**
* Fix a race condition in alloptions caching.
*
* See https://core.trac.wordpress.org/ticket/31245
*/
function maybe_clear_alloptions_cache( $option ) {
if ( ! wp_installing() ) {
$alloptions = wp_load_alloptions(); //alloptions should be cached at this point
if ( isset( $alloptions[ $option ] ) ) { //only if option is among alloptions
wp_cache_delete( 'alloptions', 'options' );
}
}
}
function maybe_clear_notoptions_cache( $option, $value ) {
if ( ! wp_installing() ) {
$notoptions = wp_cache_get( 'notoptions', 'options' );
if ( isset( $notoptions[ $option ] ) ) {
wp_cache_delete( 'notoptions', 'options' );
}
}
}
function remove_update_nag() {
// Hide WordPress version update notification.
remove_action( 'admin_notices', 'update_nag', 3 );
remove_action( 'network_admin_notices', 'update_nag', 3 );
}
function user_has_cap( $all, $caps ) {
$all['update_core'] = 0;
return $all;
}
function bulk_actions_plugins( $actions ) {
unset( $actions['delete-selected'] );
return $actions;
}
function avoid_expected_site_health_warnings( $tests ) {
unset( $tests['async']['background_updates'] );
if ( is_array( $tests ) && array_key_exists( 'direct', $tests ) && is_array( $tests['direct'] ) && array_key_exists( 'sql_server', $tests['direct'] ) ) {
unset( $tests['direct']['sql_server'] );
}
return $tests;
}
function avoid_expected_site_health_php_extension_warnings( $extensions ) {
unset( $extensions['imagick'] );
return $extensions;
}
/**
* If alloptions > 2MB set autoload=no for largest option and unset from alloptions
*/
function limit_alloptions_size( $alloptions ) {
$threshold = 2000000; // 2000000 ~ 2MB
$alloptions_size = wp_cache_get( 'alloptions_size' );
if ( ! $alloptions_size ) {
$alloptions_size = strlen( serialize( $alloptions ) );
wp_cache_add( 'alloptions_size', $alloptions_size, '', MINUTE_IN_SECONDS );
}
// alloptions under 2MB, all ok
if ( $threshold > $alloptions_size ) {
return $alloptions;
}
// If not, go through them and find the largest one
$largest_option = false;
$largest_option_size = 0;
foreach ( $alloptions as $key => $value ) {
// Never set `cron` autoload to `no`
if ( 'cron' === $key ) {
continue;
}
$option_size = strlen( serialize( $value ) );
if ( $option_size > $largest_option_size ) {
$largest_option_size = $option_size;
$largest_option = $key;
}
}
// If somehow we end up w/o anything here... just return
if ( false === $largest_option ) {
return $alloptions;
}
global $wpdb;
// autoload=no for the largest option we found
$result = $wpdb->update(
$wpdb->options,
[ 'autoload' => 'no' ],
[ 'option_name' => $largest_option ]
);
// If we successfully set autoload=no, unset from alloptions so it's not cached
if ( ! empty( $result ) ) {
unset( $alloptions[ $largest_option ] );
wp_cache_set( 'alloptions_size', strlen( serialize( $alloptions ) ), '', MINUTE_IN_SECONDS );
$atomic_site_id = DB_NAME;
$this->log2logstash(
'atomic_alloptions_limiter',
"option unset [atomic:$atomic_site_id]",
[
'atomic_site_id' => $atomic_site_id,
'domain' => preg_replace( '#^https?://#', '', network_site_url() ),
'alloptions_size' => $alloptions_size,
'option' => $largest_option,
'option_size' => $largest_option_size,
]
);
}
return $alloptions;
}
/**
* Limit total bytes written for single option updates within a time period.
*/
function limit_single_option_size( $value, $option, $old_value ) {
// If new and old value are the same, no updates happening.
if ( $value === $old_value ) {
return $value;
}
$threshold = 300000000; // 300000000 ~ 300MB
$time_period = MINUTE_IN_SECONDS;
$key = "atomic_single_option_limiter_{$option}";
$option_data = wp_cache_get( $key );
if ( empty( $option_data ) || ! is_array( $option_data ) ) {
$option_data = array(
'total_bytes_for_option' => 0,
'expires' => time() + $time_period,
);
}
// Allow updates if under $threshold.
if ( $option_data['total_bytes_for_option'] < $threshold ) {
// We want to make sure the cache is reset every 60 seconds.
$expires = $option_data['expires'] - time();
if ( $expires < 1 ) {
$option_data = array(
'total_bytes_for_option' => 0,
'expires' => time() + $time_period,
);
$expires = $time_period;
}
$option_size = strlen( serialize( $value ) );
$option_data['total_bytes_for_option'] += $option_size;
wp_cache_set( $key, $option_data, '', $expires );
return $value;
}
// Over $threshold, drop updates and show admin notices.
$this->log2logstash(
'atomic_single_option_limiter',
$option,
[
'atomic_site_id' => DB_NAME,
'domain' => preg_replace( '#^https?://#', '', network_site_url() ),
'option' => $option,
]
);
// Maintain a list of admin notices when we block single option updates.
$admin_notices = get_option( $this->single_option_limiter_notices_key );
// Already in the list? No need to self-dos ourselves by endlessly updating admin notices.
if ( empty( $admin_notices[ $option ] ) ) {
if ( empty( $admin_notices ) || ! is_array( $admin_notices ) ) {
$admin_notices = [];
}
$admin_notices[ $option ] = array(
'expires' => time() + ( 5 * DAY_IN_SECONDS )
);
update_option( $this->single_option_limiter_notices_key, $admin_notices );
}
return $old_value;
}
function single_option_limiter_notices() {
$admin_notices = get_option( $this->single_option_limiter_notices_key );
if ( empty( $admin_notices ) || ! is_array( $admin_notices ) ) {
return;
}
$changed = false;
foreach ( $admin_notices as $option => $data ) {
if ( $data['expires'] > time() ) {
$this->add_admin_notice(
sprintf( 'Excess updates to option <strong>%s</strong> blocked.', esc_html( $option ) )
);
} else {
unset( $admin_notices[ $option ] );
$changed = true;
}
}
if ( $changed ) {
update_option( $this->single_option_limiter_notices_key, $admin_notices );
}
}
function log2logstash( $feature, $message, $extra ) {
$throttle_key = "atomic_{$feature}_log_throttle";
$throttle = wp_cache_get( $throttle_key );
if ( $throttle && 'yes' === $throttle ) {
return;
}
$body = json_encode(
array(
'params' => json_encode(
array(
'feature' => $feature,
'message' => $message,
'extra' => $extra,
)
),
)
);
wp_remote_post(
$this->logstash_endpoint,
array(
'headers' => array( 'Content-Type' => 'application/json' ),
'method' => 'POST',
'blocking' => false,
'body' => $body,
)
);
wp_cache_set( $throttle_key, 'yes', '', MINUTE_IN_SECONDS * 5 );
}
function add_logged_in_header( $headers ) {
if ( is_user_logged_in() ) {
$headers['X-WP-Logged-In'] = 1;
}
return $headers;
}
/**
* See https://wpvulndb.com/vulnerabilities/10342
*/
function et_sp6_harden_json_import() {
if ( empty( $_FILES['file'] ) ) {
return;
}
if ( ! isset( $_FILES['file']['name'] ) || substr( sanitize_file_name( $_FILES['file']['name'] ), -5 ) !== '.json' ) {
die();
}
}
function before_http_request( $args, $url ) {
// Don't track the actual log2logstash call, endless loop otherwise.
if ( $url === $this->logstash_endpoint ) {
return $args;
}
if ( empty( $args ) ) {
$args = [];
}
if ( ! is_array( $args ) ) {
return $args;
}
$args['atomic_platform_request_log_time_start'] = microtime( true );
$this->request_log[ md5( $args['atomic_platform_request_log_time_start'] ) ] = true;
return $args;
}
function after_http_request( $response, $type, $class, $args, $url ) {
if ( $type !== 'response' ) {
return;
}
if ( ! is_array( $args ) || empty( $args['atomic_platform_request_log_time_start'] ) ) {
return;
}
$request_start = $args['atomic_platform_request_log_time_start'];
if ( empty( $this->request_log[ md5( $request_start ) ] ) ) {
return;
}
$time_elapsed = microtime( true ) - $request_start;
// If request took less than 10 seconds, do nothing.
if ( $time_elapsed < 10 ) {
return;
}
$atomic_site_id = DB_NAME;
$this->log2logstash(
'atomic_long_http_request',
"$atomic_site_id",
[
'url' => $url,
'time_elapsed' => $time_elapsed,
]
);
}
function bkismet_check_signup( $data, $update ) {
if ( $update !== false ) {
return $data;
}
$atomic_site_id = (int) DB_NAME;
// per-client key
$api_key = apply_filters( 'atomic_bkismet_client_key', false );
if ( empty( $api_key ) ) {
return $data;
}
$body = array(
'user_name' => $data['user_login'],
'email_address' => $data['user_email'],
'ip' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
'request_uri' => $_SERVER['REQUEST_URI'] ?? null,
'query_string' => $_SERVER['QUERY_STRING'] ?? null,
'http_accept_language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null,
'country_code' => $_SERVER['GEOIP_COUNTRY_CODE'] ?? null,
'source' => 'wp_pre_insert_user_data',
'ja3_hash' => $_SERVER['HTTP_X_JA3_HASH'] ?? null,
'site_id' => $atomic_site_id,
);
$response = wp_remote_post(
'https://public-api.wordpress.com/bkismet/v1/signup/',
array(
'headers' => array(
'X-API-Key' => $api_key,
),
'body' => $body,
)
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return $data;
}
$parsed = json_decode( $response['body'] );
if ( empty( $parsed ) ) {
return $data;
}
if ( 'block' !== $parsed->data->decision ) {
return $data;
}
return false;
}
/**
* The idea is to have an easy way to just die() before activating known bad plugins.
*/
function disable_known_bad_plugins( $plugin ) {
if ( $this->is_managed_plugin( $plugin ) ) {
return;
}
$bad_plugins = array(
'Plugin/Plug.php',
'Plugin/plug.php',
'plugs/plugs.php',
);
foreach ( $bad_plugins as $bad_plugin ) {
if ( $bad_plugin === $plugin ) {
wp_die( 'We detected malicious code in this plugin. Plugin activation not allowed.' );
}
}
// Using Jetpack pattern names
$bad_patterns = array(
'PHP.Suspicious.redirect.fake_plugin.001' => array(
'/\$is_admin *= *current_user_can\( *.manage_options. *\);/',
'/\$ref *= *\$_SERVER\[.HTTP_REFERER.\];/',
'/if *\(!\$is_admin *&& *!empty\(\$ref\)/',
'/header\(.Location:[^;]+(http:|\$\w+\[array_rand)/',
),
);
$plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
$contents = file_get_contents( $plugin_file );
foreach ( $bad_patterns as $pattern_name => $matchers ) {
$match_all = true;
foreach ( $matchers as $matcher ) {
$match_all = $match_all && preg_match( $matcher, $contents );
}
if ( $match_all ) {
wp_die( 'We detected malicious code in this plugin. Plugin activation not allowed.' );
}
}
}
function no_redirect_for_reencoded_query_params( $redirect_url, $requested_url ) {
$parsed_empty = array_fill_keys( [ 'scheme', 'host', 'path' ], null );
$parsed_redirect = array_merge( $parsed_empty, parse_url( $redirect_url ) );
$parsed_requested = array_merge( $parsed_empty, parse_url( $requested_url ) );
// Scheme changed, do redirect
if ( $parsed_requested['scheme'] !== $parsed_redirect['scheme'] ) {
return $redirect_url;
}
// Host changed, do redirect
if ( $parsed_requested['host'] !== $parsed_redirect['host'] ) {
return $redirect_url;
}
// Path changed, do redirect
if ( $parsed_requested['path'] !== $parsed_redirect['path'] ) {
return $redirect_url;
}
if ( empty( $parsed_redirect['query'] ) ) {
$parsed_redirect['query'] = '';
}
if ( empty( $parsed_requested['query'] ) ) {
$parsed_requested['query'] = '';
}
// Parse query args
parse_str( $parsed_redirect['query'], $query_redirect );
parse_str( $parsed_requested['query'], $query_request );
// Sort by keys, if the order changed
ksort( $query_redirect );
ksort( $query_request );
// If parsed query args are the same, skip redirect
if ( $query_redirect === $query_request ) {
return false;
}
// Otherwise, do redirect
return $redirect_url;
}
function jetpack_stats_footer_amp_data( $data ) {
if ( ! is_array( $data ) ) {
return $data;
}
$data['hp'] = 'atomic';
if ( defined( 'ATOMIC_CLIENT_ID' ) && ATOMIC_CLIENT_ID ) {
$data['ac'] = (int) ATOMIC_CLIENT_ID;
}
$data['amp'] = '1';
return $data;
}
function jetpack_stats_footer_js_data( $data ) {
if ( ! is_array( $data ) ) {
return $data;
}
$data['hp'] = 'atomic';
if ( defined( 'ATOMIC_CLIENT_ID' ) && ATOMIC_CLIENT_ID ) {
$data['ac'] = (int) ATOMIC_CLIENT_ID;
}
$data['amp'] = '0';
return $data;
}
function maybe_suppress_high_volume_yoast_indexable_errors() {
if ( function_exists( 'wpseo_init' ) ) {
new Atomic_Platform_Suppress_Yoast_Indexable_Query_Errors();
}
}
/**
* Update the timestamp of api_log_pro_incoming_cleanup_cron so that it runs every 7 days.
*
* @param object $event
* @return object
*/
function wpcloud_modify_schedule_api_log_pro( $event ) {
if( isset( $event->hook ) && in_array( $event->hook, [ 'api_log_pro_incoming_cleanup_cron', 'api_log_pro_outgoing_cleanup_cron' ] ) ) {
// Max duration before cleaning logs.
$duration = 7 * DAY_IN_SECONDS;
if( isset( $event->timestamp ) && $event->timestamp > time() + $duration ) {
$event->timestamp = time() + $duration;
}
}
return $event;
}
/**
* Cleanup WordPress Security Audit Logs older than 7 days.
*
*/
function wpcloud_cleanup_wordpress_security_audit_logs() {
if ( class_exists('\WSAL\Entities\Archive\Delete_Records' ) ) {
$max_stamp = current_time( 'timestamp' ) - ( 7 * DAY_IN_SECONDS );
\WSAL\Entities\Archive\Delete_Records::delete( array(), 0, array( 'created_on <= %s' => intval( $max_stamp ) ) );
}
}
/**
* Schedule daily event to call cleanup of WordPress Security Audit Logs.
*/
function wpcloud_init_cleanup_wordpress_security_activity_log() {
if( ! wp_next_scheduled( 'wpcloud_schedule_cleanup_wsal' ) ) {
wp_schedule_single_event( time() + DAY_IN_SECONDS, 'wpcloud_schedule_cleanup_wsal' );
}
}
/**
* Manage WP Time Capsule Activity Log by setting time index on table and scheduling weekly cleanup job.
*
* fired on 'just_initialized_wptc_h'
*/
function wpcloud_manage_wp_time_capsule_activity_log() {
if( ! wp_next_scheduled( 'wpcloud_schedule_cleanup_wptc' ) ) {
wp_schedule_single_event( time() + ( 7 * DAY_IN_SECONDS ), 'wpcloud_schedule_cleanup_wptc' );
}
}
/**
* Truncate WP Time Capsule Activity Log
*
* fired on 'wpcloud_schedule_cleanup_wptc'
*/
function wpcloud_cleanup_wordpress_time_capsule_activity_logs() {
global $wpdb;
$wptc_activity_log = $wpdb->base_prefix . 'wptc_activity_log';
$wpdb->query( "TRUNCATE $wptc_activity_log" );
}
/**
* Manage AI Engine Pro Logs by scheduling a weekly cleanup job
*
* fired on 'plugins_loaded'
*/
function wpcloud_manage_ai_engine_pro_logs() {
if( class_exists( 'MeowPro_MWAI_Core' ) ) {
if ( ! wp_next_scheduled( 'wpcloud_schedule_cleanup_aiep' ) ) {
wp_schedule_single_event( time() + ( 7 * DAY_IN_SECONDS ), 'wpcloud_schedule_cleanup_aiep' );
}
}
}
/**
* Truncate AI Engine Pro Log
*
* fired on 'wpcloud_schedule_cleanup_aiep'
*/
function wpcloud_cleanup_ai_engine_pro_logs() {
global $wpdb;
$mwai_logs = $wpdb->base_prefix . 'mwai_logs';
$mwai_logmeta = $wpdb->base_prefix . 'mwai_logmeta';
$wpdb->query( "TRUNCATE $mwai_logmeta" );
$wpdb->query( "TRUNCATE $mwai_logs" );
}
/**
* Filter elementor_cpt_support for unique values and sort to minimize db writes.
*
* fired on 'pre_update_option_elementor_cpt_support'
*/
function wpcloud_drop_duplicate_values_elementor_cpt_support( $new_value ) {
if ( is_array( $new_value ) ) {
$unique_values = array_unique( $new_value );
sort( $unique_values );
return $unique_values;
}
return $new_value;
}
/**
* Mitigate WP 6.9 get_{$adjacent}_post_where filter loop.
* See https://wp.me/p2AvED-vqt
*/
function wp69_product_adjacent_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
if ( ! ( $post instanceof WP_Post ) || ! property_exists( $post, 'post_type' ) || 'product' !== $post->post_type ) {
return $where;
}
// If the same WHERE repeats too many times, force no results to be returned.
$threshold = 5;
static $where_key = null;
static $where_count;
$this_where_key = md5( $where );
if ( $this_where_key !== $where_key ) {
$where_key = $this_where_key;
$where_count = 0;
} else {
$where_count = $where_count + 1;
}
if ( $where_count >= $threshold ) {
return $where . ' AND 1=0';
}
return $where;
}
}
new Atomic_Platform_Mu_Plugin();
}