query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- einmalige Umbenennung. $wpdb->prepare( "UPDATE IGNORE {$wpdb->options} SET option_name = %s WHERE option_name = %s", $new, $old ) ); if ( false === $renamed ) { $ok = false; continue; } $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- einmalige Umbenennung. $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s", $old ) ); } $renamed = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- einmalige Umbenennung. $wpdb->prepare( "UPDATE {$wpdb->usermeta} SET meta_key = %s WHERE meta_key = %s", self::NEW_PREFIX . self::LEGACY_USER_META, self::OLD_PREFIX . self::LEGACY_USER_META ) ); if ( false === $renamed ) { $ok = false; } if ( ! self::migrate_table() ) { $ok = false; } // Fehlt die Tabelle danach (Umbenennen gescheitert, Tabelle nie // angelegt), wird sie leer angelegt -- sonst liefe das Plugin bis zum // naechsten Versuch ohne sie. Erst bei init, weil die Klasse dafuer // erst nach der Umstellung geladen wird (und im FREE-Paket evtl. fehlt). if ( ! self::table_exists( $wpdb->prefix . 'wp360_header_security_hosts' ) ) { add_action( 'init', function () { global $wpdb; if ( class_exists( 'Wp360_Header_Security_External_Hosts' ) && ! self::table_exists( $wpdb->prefix . 'wp360_header_security_hosts' ) ) { Wp360_Header_Security_External_Hosts::install(); } } ); } if ( ! self::migrate_cron() ) { $ok = false; } wp_cache_delete( 'alloptions', 'options' ); wp_cache_delete( 'notoptions', 'options' ); // Nutzer-Einstellungen wurden per SQL umbenannt -- ein persistenter // Object-Cache (Redis, Memcached) lieferte sonst weiter die alten // Schluessel aus. Nur diese Gruppe leeren, nie den ganzen Cache. if ( function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_group' ) ) { wp_cache_flush_group( 'user_meta' ); } // Erledigt ist die Umstellung erst, wenn jeder Schritt geklappt hat. // Sonst naechster Versuch in einer Stunde; was schon umbenannt ist, // findet der nicht mehr und laesst es in Ruhe. if ( $ok ) { update_option( self::DONE_OPTION, 1, true ); } else { set_transient( self::RETRY_TRANSIENT, 1, HOUR_IN_SECONDS ); } } /** * @return array Exakte Altnamen (Optionen und Transients). */ private static function legacy_option_names() { $names = array(); foreach ( self::LEGACY_OPTIONS as $key ) { $names[] = array( 'kind' => '', 'key' => $key, ); } foreach ( self::LEGACY_TRANSIENTS as $key ) { foreach ( array( '_transient_', '_transient_timeout_', '_site_transient_', '_site_transient_timeout_' ) as $kind ) { $names[] = array( 'kind' => $kind, 'key' => $key, ); } } return $names; } /** Ob es unter dem alten Praefix ueberhaupt etwas umzustellen gibt. */ private static function has_legacy_data() { global $wpdb; $names = array(); foreach ( self::legacy_option_names() as $name ) { $names[] = $name['kind'] . self::OLD_PREFIX . $name['key']; } $found = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- einmalige Pruefung. $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name IN (" . implode( ', ', array_fill( 0, count( $names ), '%s' ) ) . ') LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- nur Platzhalter werden eingesetzt. $names ) ); if ( $found ) { return true; } $meta = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- einmalige Pruefung. $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s LIMIT 1", self::OLD_PREFIX . self::LEGACY_USER_META ) ); if ( $meta ) { return true; } return self::table_exists( $wpdb->prefix . 'we_header_security_hosts' ) || (bool) wp_next_scheduled( self::OLD_PREFIX . self::LEGACY_CRON ); } private static function table_exists( $table ) { global $wpdb; return (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- einmalige Pruefung. } /** * Alte Tabelle unter den neuen Namen bringen, ohne Daten zu verlieren. * * Existiert die neue schon (leer angelegt, weil ein frueherer Versuch * scheiterte), wird sie ersetzt, wenn sie leer ist, sonst werden die alten * Zeilen hineinkopiert. Die alte Tabelle verschwindet nur, wenn das geklappt * hat. * * @return bool Ob die Daten jetzt unter dem neuen Namen liegen. */ private static function migrate_table() { global $wpdb; if ( ! self::table_exists( $wpdb->prefix . 'we_header_security_hosts' ) ) { return true; } if ( self::table_exists( $wpdb->prefix . 'wp360_header_security_hosts' ) ) { $rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$wpdb->prefix}wp360_header_security_hosts`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- einmalige Pruefung der eigenen Tabelle, Name ist fest. if ( 0 === $rows ) { if ( false === $wpdb->query( "DROP TABLE `{$wpdb->prefix}wp360_header_security_hosts`" ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- leere eigene Tabelle, Name ist fest. return false; } } else { if ( false === $wpdb->query( "INSERT IGNORE INTO `{$wpdb->prefix}wp360_header_security_hosts` SELECT * FROM `{$wpdb->prefix}we_header_security_hosts`" ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- einmaliges Zusammenfuehren der eigenen Tabellen, Namen sind fest. return false; } return false !== $wpdb->query( "DROP TABLE `{$wpdb->prefix}we_header_security_hosts`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- alte eigene Tabelle, Inhalt ist uebernommen. } } return false !== $wpdb->query( "RENAME TABLE `{$wpdb->prefix}we_header_security_hosts` TO `{$wpdb->prefix}wp360_header_security_hosts`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- einmalige Umbenennung der eigenen Tabelle. } /** * Geplante Ereignisse unter neuem Namen mit gleichem Takt einplanen und den * alten erst danach abmelden -- scheitert das Einplanen, bleibt der alte * Termin stehen, statt dass ein Zeitplan verloren geht. * * @return bool Ob alle Ereignisse umgezogen sind. */ private static function migrate_cron() { $crons = _get_cron_array(); if ( ! is_array( $crons ) ) { return true; } $ok = true; foreach ( $crons as $timestamp => $hooks ) { foreach ( $hooks as $hook => $events ) { if ( self::OLD_PREFIX . self::LEGACY_CRON !== $hook ) { continue; } $new_hook = self::NEW_PREFIX . self::LEGACY_CRON; foreach ( $events as $event ) { $scheduled = (bool) wp_next_scheduled( $new_hook, $event['args'] ); if ( ! $scheduled ) { $result = empty( $event['schedule'] ) ? wp_schedule_single_event( $timestamp, $new_hook, $event['args'] ) : wp_schedule_event( $timestamp, $event['schedule'], $new_hook, $event['args'] ); $scheduled = true === $result; } if ( ! $scheduled ) { $ok = false; continue; } wp_unschedule_event( $timestamp, $hook, $event['args'] ); } } } return $ok; } }