= self::VERSION ) { return; } if ( $current < 1 ) { if ( false === get_option( Edition::OPTION_SETTINGS, false ) ) { add_option( Edition::OPTION_SETTINGS, array(), '', false ); } if ( false === get_option( Edition::OPTION_MIGRATION_STATE, false ) ) { add_option( Edition::OPTION_MIGRATION_STATE, array(), '', false ); } if ( false === get_option( Edition::OPTION_FEATURE_FLAGS, false ) ) { add_option( Edition::OPTION_FEATURE_FLAGS, array(), '', false ); } } if ( $current < 2 ) { if ( false === get_option( 'reactwoo_atomic_enabled_elements', false ) ) { if ( class_exists( '\\ReactWoo\\Atomic\\Admin\\Elements\\Enabled_Store', false ) || class_exists( '\\ReactWoo\\Atomic\\Admin\\Elements\\Enabled_Store' ) ) { \ReactWoo\Atomic\Admin\Elements\Enabled_Store::maybe_init_option(); } } } if ( $current < 3 ) { self::migrate_legacy_rwa_storage(); } if ( $current < 4 ) { self::migrate_v4_identifiers(); } update_option( Edition::OPTION_SCHEMA_VERSION, self::VERSION, false ); } /** * Remove leftover keys from before the reactwoo_atomic prefix. * * Called from uninstall.php so the packaged plugin keeps those names in this file only. * * @return void */ public static function delete_legacy_keys() { delete_option( 'rwa_atomic_global_class_map' ); delete_option( 'reactwoo_atomic_rwa_class_repair_v3' ); delete_metadata( 'user', 0, 'rwa_dismiss_wporg_addons', '', true ); } /** * Copy rwa_ options, user meta, entry meta and rate-limit transients to reactwoo_atomic_ keys. * * @return void */ private static function migrate_legacy_rwa_storage() { $legacy_map = get_option( 'rwa_atomic_global_class_map', null ); if ( null !== $legacy_map && null === get_option( 'reactwoo_atomic_global_class_map', null ) ) { update_option( 'reactwoo_atomic_global_class_map', $legacy_map, false ); } if ( null !== $legacy_map ) { delete_option( 'rwa_atomic_global_class_map' ); } global $wpdb; if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) { return; } // One-time copy of legacy keys. The options API cannot LIKE-scan unknown suffixes. $user_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", 'rwa_dismiss_wporg_addons' ) ); if ( is_array( $user_ids ) ) { foreach ( $user_ids as $user_id ) { $user_id = (int) $user_id; $current = get_user_meta( $user_id, 'reactwoo_atomic_dismiss_wporg_addons', true ); if ( '' === (string) $current ) { update_user_meta( $user_id, 'reactwoo_atomic_dismiss_wporg_addons', get_user_meta( $user_id, 'rwa_dismiss_wporg_addons', true ) ); } delete_user_meta( $user_id, 'rwa_dismiss_wporg_addons' ); } } $meta_rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT post_id, meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $wpdb->esc_like( '_rwa_' ) . '%' ) ); if ( is_array( $meta_rows ) ) { foreach ( $meta_rows as $row ) { $old_key = isset( $row->meta_key ) ? (string) $row->meta_key : ''; $post_id = isset( $row->post_id ) ? (int) $row->post_id : 0; if ( $post_id <= 0 || 0 !== strpos( $old_key, '_rwa_' ) ) { continue; } $new_key = '_reactwoo_atomic_' . substr( $old_key, strlen( '_rwa_' ) ); if ( ! metadata_exists( 'post', $post_id, $new_key ) ) { update_post_meta( $post_id, $new_key, get_post_meta( $post_id, $old_key, true ) ); } delete_post_meta( $post_id, $old_key ); } } self::rename_transient_options( '_transient_rwa_msf_rl_', '_transient_reactwoo_atomic_msf_rl_' ); self::rename_transient_options( '_transient_timeout_rwa_msf_rl_', '_transient_timeout_reactwoo_atomic_msf_rl_' ); } /** * Move the form-entry post type, the class-repair flag and saved editor payload keys. * * The new post type must stay `reactwoo_atomic_form` (20 characters, the post-type limit) * and match Forms\Submission_Store::POST_TYPE. * * @return void */ private static function migrate_v4_identifiers() { $flag = get_option( 'reactwoo_atomic_rwa_class_repair_v3', null ); if ( null !== $flag ) { if ( null === get_option( 'reactwoo_atomic_class_repair_v3', null ) ) { update_option( 'reactwoo_atomic_class_repair_v3', $flag, false ); } delete_option( 'reactwoo_atomic_rwa_class_repair_v3' ); } global $wpdb; if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) { return; } $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->posts, array( 'post_type' => 'reactwoo_atomic_form' ), array( 'post_type' => 'rwa_form_entry' ), array( '%s' ), array( '%s' ) ); self::rewrite_postmeta_payloads(); self::rewrite_option_payloads(); } /** * @return void */ private static function rewrite_postmeta_payloads() { global $wpdb; $seen = array(); foreach ( array( '__rwa_ref', '__rwa_ctx' ) as $needle ) { $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT meta_id, post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_value LIKE %s", // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value '%' . $wpdb->esc_like( $needle ) . '%' ) ); if ( ! is_array( $rows ) ) { continue; } foreach ( $rows as $row ) { $meta_id = isset( $row->meta_id ) ? (int) $row->meta_id : 0; $post_id = isset( $row->post_id ) ? (int) $row->post_id : 0; if ( $meta_id <= 0 || isset( $seen[ $meta_id ] ) ) { continue; } $seen[ $meta_id ] = true; $raw = isset( $row->meta_value ) ? (string) $row->meta_value : ''; $new = self::rewrite_payload_value( $raw ); if ( $new === $raw ) { continue; } $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->postmeta, array( 'meta_value' => $new ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value array( 'meta_id' => $meta_id ), array( '%s' ), array( '%d' ) ); if ( $post_id > 0 ) { wp_cache_delete( $post_id, 'post_meta' ); } } } } /** * @return void */ private static function rewrite_option_payloads() { global $wpdb; $seen = array(); foreach ( array( '__rwa_ref', '__rwa_ctx' ) as $needle ) { $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT option_id, option_name, option_value FROM {$wpdb->options} WHERE option_value LIKE %s", '%' . $wpdb->esc_like( $needle ) . '%' ) ); if ( ! is_array( $rows ) ) { continue; } foreach ( $rows as $row ) { $option_id = isset( $row->option_id ) ? (int) $row->option_id : 0; $name = isset( $row->option_name ) ? (string) $row->option_name : ''; if ( $option_id <= 0 || '' === $name || isset( $seen[ $option_id ] ) ) { continue; } $seen[ $option_id ] = true; $raw = isset( $row->option_value ) ? (string) $row->option_value : ''; $new = self::rewrite_payload_value( $raw ); if ( $new === $raw ) { continue; } $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->options, array( 'option_value' => $new ), array( 'option_id' => $option_id ), array( '%s' ), array( '%d' ) ); wp_cache_delete( $name, 'options' ); wp_cache_delete( 'alloptions', 'options' ); wp_cache_delete( 'notoptions', 'options' ); } } } /** * Rewrite __rwa_ref / __rwa_ctx inside a stored meta or option value. * * Serialized values are unserialized first so a longer key cannot corrupt length prefixes. * * @param string $value Raw database value. * @return string */ private static function rewrite_payload_value( $value ) { if ( function_exists( 'is_serialized' ) && is_serialized( $value ) ) { $data = function_exists( 'maybe_unserialize' ) ? maybe_unserialize( $value ) : $value; $mapped = self::map_payload_keys( $data ); if ( ! function_exists( 'maybe_serialize' ) ) { return $value; } return maybe_serialize( $mapped ); } $trim = ltrim( $value ); if ( '' !== $trim && ( '{' === $trim[0] || '[' === $trim[0] ) ) { $decoded = json_decode( $value, true ); if ( JSON_ERROR_NONE === json_last_error() && is_array( $decoded ) ) { $encoded = wp_json_encode( self::map_payload_keys( $decoded ) ); if ( is_string( $encoded ) ) { return $encoded; } } } return str_replace( array( '__rwa_ref', '__rwa_ctx' ), array( '__reactwoo_atomic_ref', '__reactwoo_atomic_ctx' ), $value ); } /** * @param mixed $value Decoded payload. * @return mixed */ private static function map_payload_keys( $value ) { if ( is_array( $value ) ) { $out = array(); foreach ( $value as $key => $item ) { $new_key = $key; if ( '__rwa_ref' === $key ) { $new_key = '__reactwoo_atomic_ref'; } elseif ( '__rwa_ctx' === $key ) { $new_key = '__reactwoo_atomic_ctx'; } $out[ $new_key ] = self::map_payload_keys( $item ); } return $out; } if ( is_object( $value ) ) { foreach ( get_object_vars( $value ) as $key => $item ) { $new_key = $key; if ( '__rwa_ref' === $key ) { $new_key = '__reactwoo_atomic_ref'; } elseif ( '__rwa_ctx' === $key ) { $new_key = '__reactwoo_atomic_ctx'; } if ( $new_key !== $key ) { unset( $value->$key ); } $value->$new_key = self::map_payload_keys( $item ); } return $value; } if ( is_string( $value ) && ( false !== strpos( $value, '__rwa_ref' ) || false !== strpos( $value, '__rwa_ctx' ) ) ) { return self::rewrite_payload_value( $value ); } return $value; } /** * @param string $from Option-name prefix. * @param string $to Replacement prefix. * @return void */ private static function rename_transient_options( $from, $to ) { global $wpdb; $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $from ) . '%' ) ); if ( ! is_array( $rows ) ) { return; } foreach ( $rows as $row ) { $old_name = isset( $row->option_name ) ? (string) $row->option_name : ''; if ( '' === $old_name || 0 !== strpos( $old_name, $from ) ) { continue; } $new_name = $to . substr( $old_name, strlen( $from ) ); $exists = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT option_id FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", $new_name ) ); if ( $exists ) { delete_option( $old_name ); continue; } $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->options, array( 'option_name' => $new_name ), array( 'option_name' => $old_name ) ); } } }