R2 — WordPress bridge PHP internals (verified Sept 2026, WP 6.0–7.1)¶
Verdicts¶
-
get_plugins(): requires
require_once ABSPATH . 'wp-admin/includes/plugin.php'on WP <= 6.7. Since 6.8, wp-settings.php already includes it (line 527), so the function exists after wp-load. Bridge must use:if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; }The include file is definitions-only; safe with WP_USE_THEMES=false. Sources: developer.wordpress.org/reference/functions/get_plugins/ · WP 6.8 raw source (wp-admin/includes/plugin.php, wp-settings.php:52,115,444,527). -
get_option('active_plugins'): available everywhere after wp-load (core itself reads it in wp_get_active_and_valid_plugins during bootstrap). Entries are 'folder/file.php' strings.
-
do_action('litespeed_purge_all'): no-op when LiteSpeed inactive — do_action with zero listeners returns early. LiteSpeed docs state the API hook "works whether or not LSCWP is active". No function_exists guard needed.
-
wp_upload_dir(): available after wp-load; bridge must call
wp_upload_dir( null, false )— default$create_dir=trueattempts mkdir as a side effect. Check theerrorkey. -
Translations: since 6.5, runtime loads
.l10n.phpINSTEAD of.mowhen present (both kept as candidates). No core runtime path reads.po. Deleting.pois runtime-safe but NOT durable: wp.org language packs re-ship.po+.mo+.l10n.php, and translations auto-update by default (since 3.7) via thewp_version_checktwicedaily cron ->wp_maybe_auto_update-> WP_Automatic_Updater (translation offers carry autoupdate). Manual trigger: update-core.php "Update Translations" button. NOTE:wp_update_translations()does NOT exist in core 5.0–7.x — never reference it. -
fileperms pattern:
$p = @fileperms( $f ); $perms = ( false === $p ) ? null : substr( sprintf( '%o', $p ), -4 );— PHP manual Example #1. Check for false first (sprintf('%o', false) === '0' silently). -
PHP/WP error-display interplay (critical for the bridge):
hash_equals(known, user)verified timing-safe.php://input+json_decode(..., JSON_THROW_ON_ERROR)verified; JsonException is Throwable-catchable.catch (Throwable)covers TypeError and engine Errors but NOT hard fatals (OOM, max_execution_time, failed require, parse errors).- During wp-load,
wp_debug_mode()(wp-settings.php:84) setsdisplay_errors=0for JSON requests (detected via Content-Type/Accept headers!) and rewrites error_reporting when WP_DEBUG=false. Therefore the bridge MUST seterror_reporting(E_ALL); ini_set('display_errors','1');AFTER wp-load returns. - Core registers NO set_error_handler during bootstrap; restore_error_handler() after wp-load is harmless belt-and-braces vs plugins.
- WP registers a fatal-error SHUTDOWN handler that masks hard fatals with the
generic "critical error" page (the opaque-500 failure mode). Bridge defines
WP_DISABLE_FATAL_ERROR_HANDLERBEFORE wp-load so hard fatals surface raw; the client detects non-JSON responses and reports "hard fatal + body excerpt". - json_decode flags: use JSON_THROW_ON_ERROR.
Bridge recipe (binding for W4)¶
<?php
define( 'WP_USE_THEMES', false );
define( 'WP_DISABLE_FATAL_ERROR_HANDLER', true ); // before wp-load
require_once __DIR__ . '/wp-load.php';
error_reporting( E_ALL ); // AFTER wp-load (survives)
ini_set( 'display_errors', '1' );
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
// ... token gate, action dispatch, try/catch (Throwable) -> JSON