$args Weitere Parameter.
* @return string
*/
public static function tab_url( $tab, array $args = array() ) {
return add_query_arg( array_merge( array( 'tab' => $tab ), $args ), self::page_url() );
}
public static function register_menu() {
self::$hook = (string) add_submenu_page(
Wp360_Staging_Guard_Orbit_Menu::SLUG,
__( 'Staging Guard', '360-orbit-staging-guard' ),
__( 'Staging Guard', '360-orbit-staging-guard' ),
self::CAPABILITY,
self::SLUG,
array( __CLASS__, 'render_page' )
);
}
/**
* @param string[] $links Bisherige Links.
* @return string[]
*/
public static function action_links( $links ) {
array_unshift( $links, '' . esc_html__( 'Settings', '360-orbit-staging-guard' ) . '' );
return $links;
}
/**
* @param string $hook_suffix Hook-Suffix des aktuellen Bildschirms.
*/
public static function enqueue_assets( $hook_suffix ) {
if ( '' === self::$hook || self::$hook !== $hook_suffix ) {
return;
}
wp_enqueue_style( 'wp360-sg-admin', WP360_SG_URL . 'assets/admin.css', array(), WP360_SG_VERSION );
}
/**
* Zeile fuer die Orbit-Uebersicht.
*
* @param string[] $lines Bisherige Zeilen.
* @param string $slug Plugin, fuer das die Zeilen gefragt sind.
* @return string[]
*/
public static function health_lines( array $lines, $slug ) {
if ( '360-orbit-staging-guard' !== $slug ) {
return $lines;
}
$state = Wp360_Staging_Guard_Environment::state();
if ( Wp360_Staging_Guard_Plugin::is_disabled() ) {
$lines[] = __( 'Switched off by WP360_SG_DISABLE.', '360-orbit-staging-guard' );
} elseif ( $state['active'] ) {
$lines[] = __( 'Protection is active.', '360-orbit-staging-guard' );
} elseif ( $state['notice'] ) {
$lines[] = __( 'Not protected: this address differs from the live site.', '360-orbit-staging-guard' );
} else {
$lines[] = __( 'Protection is off.', '360-orbit-staging-guard' );
}
return $lines;
}
/* ---------------------------------------------------------------- */
/* Admin-Leiste und Hinweise */
/* ---------------------------------------------------------------- */
/**
* Kurzbezeichnung der Umgebung fuer die Admin-Leiste.
*
* @param array{active: bool, reason: string, notice: bool, environment: string} $state Zustand.
* @return string
*/
private static function environment_label( array $state ) {
$labels = array(
'local' => __( 'LOCAL', '360-orbit-staging-guard' ),
'development' => __( 'DEV', '360-orbit-staging-guard' ),
'staging' => __( 'STAGING', '360-orbit-staging-guard' ),
);
return $labels[ $state['environment'] ] ?? __( 'STAGING', '360-orbit-staging-guard' );
}
/**
* Begruendung, warum der Schutz gerade aktiv ist.
*
* @param array{active: bool, reason: string, notice: bool, environment: string} $state Zustand.
* @return string
*/
private static function reason_text( array $state ) {
if ( Wp360_Staging_Guard_Environment::REASON_FORCED === $state['reason'] ) {
return __( 'Switched on manually.', '360-orbit-staging-guard' );
}
/* translators: %s: environment type such as staging, local or development */
return sprintf( __( 'Environment type: %s.', '360-orbit-staging-guard' ), $state['environment'] );
}
/**
* Eintrag in der Admin-Leiste: rot bei aktivem Schutz, amber bei dem
* Hinweis "Adresse weicht ab".
*
* @param WP_Admin_Bar $bar Admin-Leiste.
*/
public static function admin_bar_node( $bar ) {
if ( ! self::can_manage() ) {
return;
}
$state = Wp360_Staging_Guard_Environment::state();
if ( $state['active'] ) {
$bar->add_node(
array(
'id' => 'wp360-sg',
'title' => '' . esc_html( self::environment_label( $state ) ) . '',
'href' => self::page_url(),
'meta' => array( 'class' => 'wp360-sg-active' ),
)
);
return;
}
if ( $state['notice'] ) {
$bar->add_node(
array(
'id' => 'wp360-sg',
'title' => '' . esc_html__( 'NOT PROTECTED', '360-orbit-staging-guard' ) . '',
'href' => self::page_url(),
'meta' => array( 'class' => 'wp360-sg-notice' ),
)
);
}
}
/** Farben des Eintrags; nur ausgegeben, wenn die Admin-Leiste ihn zeigt. */
public static function enqueue_bar_styles() {
if ( ! is_admin_bar_showing() || ! self::can_manage() ) {
return;
}
$state = Wp360_Staging_Guard_Environment::state();
if ( ! $state['active'] && ! $state['notice'] ) {
return;
}
$css = '#wpadminbar .wp360-sg-active > .ab-item{background:' . self::COLOR_ACTIVE . ';color:#fff;font-weight:600}';
$css .= '#wpadminbar .wp360-sg-active:hover > .ab-item,#wpadminbar .wp360-sg-active > .ab-item:focus{background:#9f1d1d;color:#fff}';
$css .= '#wpadminbar .wp360-sg-notice > .ab-item{background:' . self::COLOR_NOTICE . ';color:#fff;font-weight:600}';
$css .= '#wpadminbar .wp360-sg-notice:hover > .ab-item,#wpadminbar .wp360-sg-notice > .ab-item:focus{background:#92400e;color:#fff}';
$css .= '#wpadminbar .wp360-sg-active > .ab-item .ab-icon:before,#wpadminbar .wp360-sg-notice > .ab-item .ab-icon:before{content:"\f334";top:2px;color:#fff}';
wp_register_style( 'wp360-sg-bar', false, array(), WP360_SG_VERSION );
wp_enqueue_style( 'wp360-sg-bar' );
wp_add_inline_style( 'wp360-sg-bar', $css );
}
/** Hinweise im Backend (nicht im Dashboard). */
public static function render_notices() {
if ( ! self::can_manage() ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( $screen && 'dashboard' === $screen->id ) {
return;
}
if ( Wp360_Staging_Guard_Plugin::is_disabled() ) {
echo '
' . esc_html__( 'Staging Guard is switched off.', '360-orbit-staging-guard' ) . ' ';
echo esc_html__( 'The constant WP360_SG_DISABLE is set in wp-config.php. Remove it to switch the protection back on.', '360-orbit-staging-guard' ) . '
';
return;
}
$state = Wp360_Staging_Guard_Environment::state();
// Bei aktivem Schutz zeigt die rote Markierung in der Admin-Leiste, dass
// dies eine Kopie ist; ein grosser Hinweis auf jeder Seite entfaellt
// (Entscheidung 12.10.2026). Der Knopf "Das ist die Live-Seite" steht auf
// der Status-Seite.
if ( $state['notice'] ) {
echo '
' . esc_html__( 'This site runs under a different address than the live site.', '360-orbit-staging-guard' ) . ' ';
echo esc_html__( 'It is not protected yet: emails and outgoing calls still go out for real.', '360-orbit-staging-guard' ) . '
';
echo '
';
self::render_action_form( 'wp360_sg_enable', __( 'Switch protection on now', '360-orbit-staging-guard' ), 'primary', false );
self::render_action_form( 'wp360_sg_is_live', __( 'This is the live site', '360-orbit-staging-guard' ), 'secondary', false, __( 'Use this address as the live address? Only do this if this really is the live site.', '360-orbit-staging-guard' ) );
echo '
';
}
}
/**
* Kleines Formular, das eine der admin-post-Aktionen ausloest.
*
* @param string $action Aktionsname.
* @param string $label Beschriftung.
* @param string $type primary oder secondary.
* @param bool $wrap In einen Absatz einhuellen.
* @param string $confirm Rueckfrage vor dem Absenden (leer = keine).
*/
private static function render_action_form( $action, $label, $type, $wrap = true, $confirm = '' ) {
if ( $wrap ) {
echo '
';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nur Anzeige einer Meldung.
$notice = isset( $_GET['wp360_sg_notice'] ) ? sanitize_key( wp_unslash( $_GET['wp360_sg_notice'] ) ) : '';
$notices = array(
'saved' => array( 'success', __( 'Settings saved.', '360-orbit-staging-guard' ) ),
'allowed' => array( 'success', __( 'The host was added to the allow list.', '360-orbit-staging-guard' ) ),
'bad_host' => array( 'error', __( 'That is not a valid host name.', '360-orbit-staging-guard' ) ),
'log_cleared' => array( 'success', __( 'The log was cleared.', '360-orbit-staging-guard' ) ),
'bad_live_url' => array( 'error', __( 'The live address could not be read and was not saved. Please enter a full address such as https://example.com.', '360-orbit-staging-guard' ) ),
);
if ( isset( $notices[ $notice ] ) ) {
echo '
' . esc_html( $notices[ $notice ][1] ) . '
';
}
self::render_tabs( $tab );
switch ( $tab ) {
case 'settings':
self::render_settings_tab();
break;
case 'log':
self::render_log_tab();
break;
case 'mails':
Wp360_Staging_Guard_Mail_Viewer::render();
break;
case 'pro':
self::render_pro_tab();
break;
default:
self::render_status_tab();
}
echo '
' . esc_html__( 'Protection runs on the wrong site? Use this only if this really is the live site; it switches the protection off and remembers this address as the live address.', '360-orbit-staging-guard' ) . '
';
self::render_action_form( 'wp360_sg_is_live', __( 'This is the live site', '360-orbit-staging-guard' ), 'secondary', true, __( 'Use this address as the live address? Only do this if this really is the live site.', '360-orbit-staging-guard' ) );
if ( Wp360_Staging_Guard_Host_Matcher::is_live_host( Wp360_Staging_Guard_Host_Matcher::host_of( home_url() ), $settings['live_url'] ) ) {
echo '
' . esc_html__( 'This address is the same as the saved live address. Calls to the live site cannot be told apart from calls to this site. If this is a copy of the live database, update the site address first.', '360-orbit-staging-guard' ) . '
';
}
} elseif ( $state['notice'] ) {
echo '
' . esc_html__( 'Not protected', '360-orbit-staging-guard' ) . ' ' . esc_html__( 'This address differs from the live site, but the protection is not on.', '360-orbit-staging-guard' ) . '
';
} else {
echo '
' . esc_html__( 'Off', '360-orbit-staging-guard' ) . ' ' . esc_html__( 'This looks like the live site, nothing is blocked.', '360-orbit-staging-guard' ) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Namen und Werte oben einzeln escaped.
}
echo '
';
echo '
';
echo '
' . esc_html__( 'What this protects, and what it does not', '360-orbit-staging-guard' ) . '
';
echo '
' . esc_html__( 'Staging Guard is a safeguard against accidents, not an isolation. It stops what goes through the WordPress HTTP API and wp_mail().', '360-orbit-staging-guard' ) . '
';
echo '
';
echo '
' . esc_html__( 'Code that calls curl, file_get_contents() or its own library directly (many payment and cloud SDKs) is not caught.', '360-orbit-staging-guard' ) . '
';
echo '
' . esc_html__( 'Plugins that talk to a mail service or SMTP directly, or replace wp_mail(), bypass the email protection.', '360-orbit-staging-guard' ) . '
';
echo '
' . esc_html__( 'For real isolation, block outgoing traffic in the firewall or remove the service credentials from the copy.', '360-orbit-staging-guard' ) . '
';
echo '
' . esc_html__( 'Password reset and login emails are caught as well. If you need them on this copy, set the email mode to "Let through" or set WP360_SG_DISABLE in wp-config.php.', '360-orbit-staging-guard' ) . '
';
echo '
';
}
/**
* Eine Tabellenzeile; $value ist bereits escaped.
*
* @param string $label Beschriftung.
* @param string $value HTML des Werts.
*/
private static function row( $label, $value ) {
echo '
' . esc_html__( 'Outgoing calls seen on this copy, per host. Only host names and counters are stored, never paths, parameters or content.', '360-orbit-staging-guard' ) . '