*/ private static $pending = array(); /** @var bool */ private static $shutdown_registered = false; public static function init() { add_filter( 'pre_http_request', array( __CLASS__, 'filter' ), 1, 3 ); // Weiterleitungen folgt der HTTP-Client selbst, ohne `pre_http_request` // erneut zu durchlaufen. Dieser Haken sieht jedes Ziel vor dem Aufruf. add_action( 'requests-requests.before_redirect', array( __CLASS__, 'check_redirect' ), 1, 1 ); } /** * @param false|array|WP_Error $pre Vorab-Antwort eines anderen Plugins. * @param array $args Anfrageargumente. * @param string $url Aufgerufene URL. * @return false|array|WP_Error */ public static function filter( $pre, $args, $url ) { // Hat schon jemand anders geantwortet, bleibt das so. if ( false !== $pre || ! Wp360_Staging_Guard_Environment::is_active() ) { return $pre; } $method = is_array( $args ) && isset( $args['method'] ) ? strtoupper( (string) $args['method'] ) : 'GET'; $decision = self::decide( (string) $url, $method ); if ( $decision['allow'] ) { return $pre; } return new WP_Error( 'wp360_sg_blocked', self::message( $decision ) ); } /** * Prueft das Ziel einer Weiterleitung. Ein Verbot bricht den Aufruf mit * einer Ausnahme des HTTP-Clients ab, die WordPress in einen WP_Error * umwandelt. * * @param string $location Ziel der Weiterleitung. */ public static function check_redirect( $location ) { if ( ! Wp360_Staging_Guard_Environment::is_active() ) { return; } $decision = self::decide( (string) $location, 'GET' ); if ( $decision['allow'] ) { return; } if ( class_exists( 'WpOrg\\Requests\\Exception' ) ) { throw new WpOrg\Requests\Exception( esc_html( self::message( $decision ) ), 'wp360_sg_blocked' ); } } /** * Entscheidung samt Protokoll fuer eine Adresse. * * @param string $url Adresse. * @param string $method HTTP-Methode. * @return array{allow: bool, host: string, reason: string, live: bool, log: bool} */ private static function decide( $url, $method ) { $settings = Wp360_Staging_Guard_Settings::get(); $pro = array(); $decision = Wp360_Staging_Guard_Http_Policy::decide( $url, $settings, self::own_hosts(), self::always_allowed(), $method, $pro ); if ( $decision['log'] ) { self::remember( $decision['host'], ! $decision['allow'], $decision['live'] ); } return $decision; } /** * Meldung zu einem blockierten Aufruf. * * @param array{allow: bool, host: string, reason: string, live: bool, log: bool} $decision Entscheidung. * @return string */ private static function message( array $decision ) { if ( Wp360_Staging_Guard_Http_Policy::REASON_UNPARSABLE === $decision['reason'] ) { return __( 'Blocked by Staging Guard: the address of this call could not be read.', '360-orbit-staging-guard' ); } if ( Wp360_Staging_Guard_Http_Policy::REASON_LIVE === $decision['reason'] ) { /* translators: %s: host name of the live site */ return sprintf( __( 'Blocked by Staging Guard: %s is the live site, which a copy must not call.', '360-orbit-staging-guard' ), $decision['host'] ); } /* translators: %s: host name that was called */ return sprintf( __( 'Blocked by Staging Guard: calls to %s are not allowed on this copy.', '360-orbit-staging-guard' ), $decision['host'] ); } /** * Hosts der eigenen Website (siteurl und home). * * @return string[] */ private static function own_hosts() { return array_values( array_filter( array( (string) get_option( 'siteurl' ), (string) get_option( 'home' ), ) ) ); } /** * Hosts, die nie blockiert werden (im Pro-Build der Lizenzserver, damit der * eigene Schutz die Lizenzpruefung nicht aussperrt). * * @return string[] */ private static function always_allowed() { $hosts = array(); return $hosts; } /** * Merkt einen Aufruf vor und schreibt das Protokoll am Ende des Requests. * * @param string $host Host. * @param bool $blocked Blockiert. * @param bool $live Ziel ist die Live-Adresse. */ private static function remember( $host, $blocked, $live ) { self::$pending[] = array( 'host' => $host, 'blocked' => $blocked, 'live' => $live, ); if ( ! self::$shutdown_registered ) { self::$shutdown_registered = true; add_action( 'shutdown', array( __CLASS__, 'flush_log' ) ); } } /** Schreibt die vorgemerkten Aufrufe ins Protokoll (eine Schreibabfrage je Request). */ public static function flush_log() { if ( ! self::$pending ) { return; } $log = get_option( self::LOG_OPTION, array() ); $log = is_array( $log ) ? $log : array(); $now = time(); $max = Wp360_Staging_Guard_Log_Summary::MAX_HOSTS; foreach ( self::$pending as $call ) { $log = Wp360_Staging_Guard_Log_Summary::record( $log, $call['host'], $call['blocked'], $now, $call['live'], $max ); } self::$pending = array(); update_option( self::LOG_OPTION, $log, false ); } /** * Das Protokoll, neueste Hosts zuerst. * * @return array */ public static function log_rows() { $log = get_option( self::LOG_OPTION, array() ); return Wp360_Staging_Guard_Log_Summary::latest( is_array( $log ) ? $log : array() ); } }