0 ) ? max( 5, min( 20, (int) floor( $max * 0.4 ) ) ) : 20; } $budget = (int) apply_filters( 'dbmg_tick_budget', $base ); $budget = max( 1, $budget ); return $short ? min( $budget, 10 ) : $budget; } /** * Lift limits where the host allows it. */ public static function raise_limits() { self::budget_seconds(); // Prime the cached base budget first. if ( self::can_call( 'set_time_limit' ) ) { @set_time_limit( 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, Squiz.PHP.DiscouragedFunctions.Discouraged -- long downloads/ticks; guarded by can_call() and the tick budget. } if ( function_exists( 'wp_raise_memory_limit' ) ) { wp_raise_memory_limit( 'admin' ); } } /** * @return int Bytes, or 0 for unlimited. */ public static function memory_limit() { $raw = (string) ini_get( 'memory_limit' ); if ( '' === $raw || '-1' === trim( $raw ) ) { return 0; } return (int) wp_convert_hr_to_bytes( $raw ); } /** * Memory guard at 70 % of memory_limit (§8.3). * * @return bool */ public static function memory_near_limit() { $limit = self::memory_limit(); if ( $limit <= 0 ) { return false; } return memory_get_usage( true ) > 0.7 * $limit; } /** * Bytes we can still allocate before the 70 % guard (used to size read buffers). * * @return int */ public static function memory_headroom() { $limit = self::memory_limit(); if ( $limit <= 0 ) { return 256 * MB_IN_BYTES; } return max( 0, (int) ( 0.7 * $limit ) - memory_get_usage( true ) ); } /** * Close the HTTP response early so the caller doesn't wait (loopback ticks, create). */ public static function finish_request() { if ( function_exists( 'fastcgi_finish_request' ) ) { fastcgi_finish_request(); return true; } if ( function_exists( 'litespeed_finish_request' ) ) { litespeed_finish_request(); return true; } return false; } /** * Which database family runs this site: mysql | sqlite | multidb | unsupported. * * @return string */ public static function db_engine() { global $wpdb; if ( ( defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ) || ( is_object( $wpdb ) && 'WP_SQLite_DB' === get_class( $wpdb ) ) ) { return 'sqlite'; } if ( ! is_object( $wpdb ) ) { return 'unsupported'; } if ( ! empty( $wpdb->hyper_servers ) || ! empty( $wpdb->ludicrous_servers ) ) { return 'multidb'; } if ( $wpdb instanceof \wpdb ) { // PG4WP and similar drop-ins keep the wpdb class but talk to another server. if ( defined( 'DB_DRIVER' ) && in_array( strtolower( (string) DB_DRIVER ), array( 'pgsql', 'postgres', 'postgresql', 'sqlsrv', 'mssql' ), true ) ) { return 'unsupported'; } return 'mysql'; } return 'unsupported'; } /** * Name of the web server software (for Nginx/IIS guidance). * * @return string apache|nginx|litespeed|iis|caddy|other */ public static function web_server() { $soft = isset( $_SERVER['SERVER_SOFTWARE'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) ) : ''; foreach ( array( 'nginx', 'litespeed', 'apache', 'caddy' ) as $name ) { if ( false !== strpos( $soft, $name ) ) { return $name; } } if ( false !== strpos( $soft, 'iis' ) ) { return 'iis'; } return 'other'; } /** * zlib incremental deflate available (else store-only zips). * * @return bool */ public static function has_deflate() { return function_exists( 'deflate_init' ) && function_exists( 'inflate_init' ) && (bool) apply_filters( 'dbmg_use_deflate', true ); } /** * @return bool WP-Cron can run by itself. */ public static function cron_enabled() { return ! ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ); } /** * Cached environment probes (12 h). * * @return array */ public static function cache() { $cache = Options::get( self::CACHE_OPTION, array() ); if ( ! is_array( $cache ) || empty( $cache['time'] ) || ( time() - (int) $cache['time'] ) > self::CACHE_TTL ) { return array(); } return $cache; } /** * @param array $data Merged into the cache. */ public static function cache_set( array $data ) { $cache = self::cache(); $cache = array_merge( $cache, $data ); $cache['time'] = isset( $cache['time'] ) ? $cache['time'] : time(); Options::set( self::CACHE_OPTION, $cache ); } /** * Can this site call its own admin-ajax.php? Result cached for 12 h. * * @param bool $force Ignore the cache. * @return bool */ public static function loopback_works( $force = false ) { if ( ! apply_filters( 'dbmg_use_loopback', true ) ) { return false; } $cache = self::cache(); if ( ! $force && isset( $cache['loopback'] ) ) { return (bool) $cache['loopback']; } $token = wp_generate_password( 20, false ); Options::set( 'dbmg_ping', $token ); $response = wp_remote_post( admin_url( 'admin-ajax.php' ), array( 'timeout' => 5, 'blocking' => true, 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core filter, used as core's own loopback requests do. 'body' => array( 'action' => 'dbmg_ping', 'token' => $token, 'nonce' => wp_create_nonce( Runner::LOOPBACK_NONCE ), ), 'cookies' => Runner::loopback_cookies(), ) ); Options::delete( 'dbmg_ping' ); $ok = ! is_wp_error( $response ) && 200 === (int) wp_remote_retrieve_response_code( $response ) && false !== strpos( (string) wp_remote_retrieve_body( $response ), 'dbmg-pong' ); self::cache_set( array( 'loopback' => $ok ) ); return $ok; } /** * background (loopback or cron can drive it) | browser (only open admin pages can) (§8.2). * * @return string */ public static function run_mode() { $mode = ( self::loopback_works() || self::cron_enabled() ) ? 'background' : 'browser'; return 'browser' === apply_filters( 'dbmg_run_mode', $mode ) ? 'browser' : 'background'; } /** * Summary for the details panel and the manifest. * * @return array */ public static function summary() { global $wp_version; return array( 'wp' => $wp_version, 'php' => PHP_VERSION, 'site_url' => home_url(), 'charset' => defined( 'DB_CHARSET' ) && DB_CHARSET ? DB_CHARSET : 'utf8mb4', 'table_prefix' => $GLOBALS['wpdb']->base_prefix, 'multisite' => is_multisite(), ); } }