Skip to content

Commit

Permalink
Import QTS slugs options and meta data into QTX (#1171)
Browse files Browse the repository at this point in the history
When slugs is uninstalled all QTS data are erased and will be lost for QTX. The options and meta data from the legacy plugin should be properly imported in QTX to separate entries.

Add new features in the import settings to migrated QTS data:
- QTS `postmeta` / `termmeta` slugs are migrated renaming `_qts_slug_` prefix to `qtranslate_slug_` prefix.
- QTS `qts_options` slugs are migrated to `qtranslate_module_slugs` options, after removing the `_qts_` prefix for each type entry.

ATTENTION: existing slugs options and meta before migration are erased! A confirmation is required. Without this, a dry-run mode allows to see the effects before change.

Update prefix constants accordingly for QTX:
- rename `QTS_META_PREFIX` to `QTX_SLUGS_META_PREFIX`
- delete `QTS_PREFIX` made obsolete.

Add a new admin notice to suggest import when old QTS options and meta are found. The import can be done manually by the admin at any time.
Remove most of the HTML `id` attributes from the Slugs settings form, not needed (`name` attributes matter).
  • Loading branch information
herrvigg authored May 29, 2022
1 parent 3a54f9e commit 346d2ee
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 48 deletions.
33 changes: 32 additions & 1 deletion admin/qtx_activation_hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,6 @@ function qtranxf_activation_hook() {
// Migrate (rename/import) legacy options, temporary transitions during evolutions.
qtranxf_rename_legacy_option( 'qtranslate_modules', QTX_OPTIONS_MODULES_STATE );
qtranxf_import_legacy_option( 'acf_qtranslate', QTX_OPTIONS_MODULE_ACF, false );
qtranxf_import_legacy_option( 'qts_options', QTX_OPTIONS_MODULE_SLUGS, false );

$ts = time();
$next_thanks = get_option( 'qtranslate_next_thanks' );
Expand Down Expand Up @@ -932,6 +931,30 @@ function qtranxf_admin_notices_gutenberg() {

add_action( 'admin_notices', 'qtranxf_admin_notices_gutenberg' );

function qtranxf_admin_notices_slugs_migrate() {
if ( qtranxf_check_admin_notice( 'slugs-migrate' ) || ! QTX_Module_Loader::is_module_active( 'slugs' ) ) {
return;
}
$old_value = get_option( 'qts_options' ); // Very quick check to avoid loading more code.
if ( ! $old_value ) {
return;
}
require_once( QTRANSLATE_DIR . '/modules/slugs/admin/slugs-migrate-qts.php' );
$msg = qtranxf_slugs_check_migrate_qts(); // More advanced checks with QTS meta.
if ( empty( $msg ) ) {
return;
}
qtranxf_admin_notice_dismiss_script();
echo '<div class="notice notice-warning qtranxs-notice-ajax is-dismissible" id="qtranxs-slugs-migrate"><p>';
$options_link = admin_url( 'options-general.php?page=qtranslate-xt#import' );
echo '<p>' . sprintf( __( '%s : found slugs meta that can be migrated. Go to the <a href="%s">import settings</a> to migrate.', 'qtranslate' ), qtranxf_get_plugin_link(), $options_link ) . '</p>';
echo '<p>' . $msg . '</p>';
echo '</p><p><a class="button qtranxs-notice-dismiss" href="javascript:void(0);">' . __( 'I have already done it, dismiss this message.', 'qtranslate' );
echo '</a></p></div>';
}

add_action( 'admin_notices', 'qtranxf_admin_notices_slugs_migrate' );

function qtranxf_admin_notice_deactivate_plugin( $name, $plugin ) {
deactivate_plugins( $plugin, true );
$d = dirname( $plugin );
Expand Down Expand Up @@ -1028,6 +1051,14 @@ function qtranxf_update_option_admin_notices( $messages, $id, $set = true ) {
return $messages;
}

/**
* Update an admin notice to be set (hidden) / unset (shown).
*
* @param string $id
* @param bool $set true to set the message as seen (hide), false to unset (show)
*
* @return array|mixed
*/
function qtranxf_update_admin_notice( $id, $set ) {
$messages = get_option( 'qtranslate_admin_notices', array() );

Expand Down
14 changes: 14 additions & 0 deletions admin/qtx_admin_options_update.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ function qtranxf_edit_config() {
if ( isset( $_POST['qtranslate_reset'] ) && isset( $_POST['qtranslate_reset2'] ) ) {
$messages[] = __( 'qTranslate has been reset.', 'qtranslate' );
} elseif ( isset( $_POST['default_language'] ) ) {
// TODO: remove temporary hack - restore QTS options for master dev before migration.
// Undo import legacy options in master before new options are saved with new keys...
$qts_options = get_option( 'qts_options' );
$new_options = get_option( QTX_OPTIONS_MODULE_SLUGS );
// Re-create original QTS options that can be properly imported again.
if ( ! $qts_options && $new_options && count( $new_options ) > 0 && strpos( array_keys( $new_options )[0], '_qts_' ) === 0 ) {
update_option( 'qts_options', $new_options, false );
}

qtranxf_update_settings();

Expand Down Expand Up @@ -971,6 +979,12 @@ function qtranxf_executeOnUpdate() {
$messages[] = $msg;
}
}

if ( isset( $_POST['qtranslate_import_slugs_migrate'] ) && $_POST['qtranslate_import_slugs_migrate'] ) {
require_once( QTRANSLATE_DIR . '/modules/slugs/admin/slugs-migrate-qts.php' );
$db_commit = isset( $_POST['qtranslate_import_slugs_confirm'] ) && $_POST['qtranslate_import_slugs_confirm'];
$messages[] = qtranxf_slugs_migrate_qts_data( $db_commit );
}
}

function qtranxf_mark_default( $text ) {
Expand Down
21 changes: 21 additions & 0 deletions admin/qtx_import_export.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,27 @@ function qtranxf_admin_section_import_export( $request_uri ) {
'text' => sprintf( __( 'Use plugin %s to import data.', 'qtranslate' ), '<a href="https://wordpress.org/plugins/w2q-wpml-to-qtranslate/" target="_blank">W2Q: WPML to qTranslate</a>' )
) ) ?>
<?php do_action( 'qtranslate_add_row_migrate' ) ?>
<?php if ( QTX_Module_Loader::is_module_active( 'slugs' ) ): ?>
<tr id="qtranslate-import-slugs">
<th scope="row"><?php _e( 'Migrate QTS slugs', 'qtranslate' ) ?></th>
<td>
<label for="qtranslate_import_slugs_migrate">
<input type="checkbox" name="qtranslate_import_slugs_migrate"
id="qtranslate_import_slugs_migrate"
value="1"
onclick="let c=jQuery('#qtranslate_import_slugs_confirm'); c.prop('disabled', !jQuery(this).prop('checked')); c.prop('checked', false);"/>
<?php _e( 'Migrate slugs options, post and term meta from legacy QTS plugin to qTranslate.', 'qtranslate' ); ?>
</label>
<br/>
<label for="qtranslate_import_slugs_confirm">
<input type="checkbox"
name="qtranslate_import_slugs_confirm"
id="qtranslate_import_slugs_confirm"
value="1" <?php disabled( true ) ?> /> <?php _e( "Confirm migration of QTS slugs in database. Attention! Existing slugs are replaced. Leave unchecked for a dry-run.", 'qtranslate' ) ?>
</label>
</td>
</tr>
<?php endif ?>
<tr>
<th scope="row"><?php _e( 'Reset qTranslate', 'qtranslate' ) ?></th>
<td>
Expand Down
29 changes: 15 additions & 14 deletions modules/slugs/admin/slugs-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ function qts_uninstall() {

$meta_keys = array();
foreach ( $q_config['enabled_languages'] as $lang ) {
$meta_keys[] = QTS_META_PREFIX . $lang;
$meta_keys[] = QTX_SLUGS_META_PREFIX . $lang;
}
$meta_keys = "'" . implode( "','", $meta_keys ) . "'";
$wpdb->query( "DELETE from $wpdb->postmeta WHERE meta_key IN ($meta_keys)" );
$wpdb->query( "DELETE from $wpdb->termmeta WHERE meta_key IN ($meta_keys)" );

qts_deactivate();

Expand Down Expand Up @@ -126,7 +127,7 @@ function qts_draw_meta_box( $post ) {
echo '<input type="hidden" name="qts_nonce" id="qts_nonce" value="' . wp_create_nonce( 'qts_nonce' ) . '" />' . PHP_EOL;
$flag_location = qtranxf_flag_location();
foreach ( $q_config['enabled_languages'] as $lang ):
$slug = get_post_meta( $post->ID, QTS_META_PREFIX . $lang, true );
$slug = get_post_meta( $post->ID, QTX_SLUGS_META_PREFIX . $lang, true );
$value = ( $slug ) ? htmlspecialchars( $slug, ENT_QUOTES ) : '';
$name = $q_config['language_name'][ $lang ];
$title = sprintf( __( 'Slug' ) . ' (%s)', $name );
Expand All @@ -149,7 +150,7 @@ function qts_draw_meta_box( $post ) {
*/
function qts_sanitize_post_slug( $slug, $post, $lang ) {
$post_title = trim( qtranxf_use( $lang, $post->post_title ) );
$post_name = get_post_meta( $post->ID, QTS_META_PREFIX . $lang, true );
$post_name = get_post_meta( $post->ID, QTX_SLUGS_META_PREFIX . $lang, true );
if ( ! $post_name ) {
$post_name = $post->post_name;
}
Expand Down Expand Up @@ -229,15 +230,15 @@ function qts_wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $po
// TODO: update unique_slug :: missing hieararchical from current wp func ( 4.3.1 )
// Post slugs must be unique across all posts.
$check_sql = "SELECT $wpdb->postmeta.meta_value FROM $wpdb->posts,$wpdb->postmeta WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id AND $wpdb->postmeta.meta_key = '%s' AND $wpdb->postmeta.meta_value = '%s' AND $wpdb->posts.post_type = %s AND $wpdb->posts.ID != %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, QTS_META_PREFIX . $lang, $slug, $post_type, $post_ID ) );
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, QTX_SLUGS_META_PREFIX . $lang, $slug, $post_type, $post_ID ) );

// TODO: update unique_slug :: missing check for conflict with dates archive from current wp func ( 4.3.1 )
if ( $post_name_check || in_array( $slug, $feeds ) || apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type ) ) {
$suffix = 2;
do {
// TODO: update unique_slug :: same as above: differs from current wp func ( 4.3.1 )
$alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, QTS_META_PREFIX . $lang, $alt_post_name, $post_type, $post_ID ) );
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, QTX_SLUGS_META_PREFIX . $lang, $alt_post_name, $post_type, $post_ID ) );
$suffix++;
} while ( $post_name_check );
$slug = $alt_post_name;
Expand Down Expand Up @@ -276,8 +277,8 @@ function qts_save_postdata( $post_id, $post = null ) {
$slug = qts_sanitize_post_slug( $slug, $post, $lang );
$slug = qts_unique_post_slug( $slug, $post, $lang );

delete_post_meta( $post_id, QTS_META_PREFIX . $lang );
update_post_meta( $post_id, QTS_META_PREFIX . $lang, $slug );
delete_post_meta( $post_id, QTX_SLUGS_META_PREFIX . $lang );
update_post_meta( $post_id, QTX_SLUGS_META_PREFIX . $lang, $slug );
}
}
}
Expand Down Expand Up @@ -318,15 +319,15 @@ function qts_sanitize_term_slug( $slug, $term, $lang ) {
function qts_unique_term_slug( $slug, $term, $lang ) {
global $wpdb;

$query = $wpdb->prepare( "SELECT term_id FROM $wpdb->termmeta WHERE meta_key = '%s' AND meta_value = '%s' AND term_id != %d ", QTS_META_PREFIX . $lang, $slug, $term->term_id );
$query = $wpdb->prepare( "SELECT term_id FROM $wpdb->termmeta WHERE meta_key = '%s' AND meta_value = '%s' AND term_id != %d ", QTX_SLUGS_META_PREFIX . $lang, $slug, $term->term_id );
$exists_slug = $wpdb->get_results( $query );

if ( empty( $exists_slug ) ) {
return $slug;
}

// If we didn't get a unique slug, try appending a number to make it unique.
$query = $wpdb->prepare( "SELECT meta_value FROM $wpdb->termmeta WHERE meta_key = '%s' AND meta_value = '%s' AND term_id != %d", QTS_META_PREFIX . $lang, $slug, $term->term_id );
$query = $wpdb->prepare( "SELECT meta_value FROM $wpdb->termmeta WHERE meta_key = '%s' AND meta_value = '%s' AND term_id != %d", QTX_SLUGS_META_PREFIX . $lang, $slug, $term->term_id );

if ( $wpdb->get_var( $query ) ) {
$num = 2;
Expand All @@ -336,7 +337,7 @@ function qts_unique_term_slug( $slug, $term, $lang ) {
$slug_check = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_value FROM $wpdb->termmeta WHERE meta_key = '%s' AND meta_value = '%s'",
QTS_META_PREFIX . $lang,
QTX_SLUGS_META_PREFIX . $lang,
$alt_slug ) );
} while ( $slug_check );
$slug = $alt_slug;
Expand Down Expand Up @@ -371,8 +372,8 @@ function qts_save_term( $term_id, $tt_id, $taxonomy ) {
$slug = qts_sanitize_term_slug( $slug, $term, $lang );
$slug = qts_unique_term_slug( $slug, $term, $lang );

delete_metadata( 'term', $term_id, QTS_META_PREFIX . $lang );
update_metadata( 'term', $term_id, QTS_META_PREFIX . $lang, $slug );
delete_metadata( 'term', $term_id, QTX_SLUGS_META_PREFIX . $lang );
update_metadata( 'term', $term_id, QTX_SLUGS_META_PREFIX . $lang, $slug );
}
}

Expand All @@ -389,7 +390,7 @@ function qts_show_list_term_fields( $term ) {
$flag_location = qtranxf_flag_location(); ?>
<ul class="qtranxs-slugs-list qtranxs-slugs-terms"><?php
foreach ( $q_config['enabled_languages'] as $lang ) {
$slug = is_object( $term ) ? get_metadata( 'term', $term->term_id, QTS_META_PREFIX . $lang, true ) : '';
$slug = is_object( $term ) ? get_metadata( 'term', $term->term_id, QTX_SLUGS_META_PREFIX . $lang, true ) : '';
$value = $slug ? htmlspecialchars( $slug, ENT_QUOTES ) : '';
$flag = $q_config['flag'][ $lang ];
$name = $q_config['language_name'][ $lang ];
Expand Down Expand Up @@ -532,7 +533,7 @@ function qts_taxonomy_custom_column( $str, $column_name, $term_id ) {
global $q_config;

if ( $column_name === 'qts-slug' ) {
echo get_metadata( 'term', $term_id, QTS_META_PREFIX . $q_config['language'], true );
echo get_metadata( 'term', $term_id, QTX_SLUGS_META_PREFIX . $q_config['language'], true );
}

return false;
Expand Down
165 changes: 165 additions & 0 deletions modules/slugs/admin/slugs-migrate-qts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php
/**
* Legacy meta and options from QTS plugin.
*/
const QTX_SLUGS_LEGACY_QTS_META_PREFIX = '_qts_slug_';
const QTX_SLUGS_LEGACY_QTS_OPTIONS_PREFIX = '_qts_';
const QTX_SLUGS_LEGACY_QTS_OPTIONS_NAME = 'qts_options';

/**
* Check if slugs meta should be migrated from the legacy QTS postmeta and termmeta.
*
* @return string messages giving details, empty if new meta found or no legacy meta found.
*/
function qtranxf_slugs_check_migrate_qts() {
global $wpdb;

/**
* Generic function that counts the slugs meta, legacy (QTS) or new (QTX).
*
* @param string $table name of the meta table (postmeta, termmeta)
* @param string $prefix prefix for the meta key
* @param string[] $msg array of messages, updated
*
* @return void
*/
$count_slugs = function ( $table, $prefix, &$msg ) use ( $wpdb ) {
$results = $wpdb->get_var( "SELECT count(*) FROM $table WHERE meta_key like '$prefix%'" );
if ( $results ) {
$msg[] = sprintf( __( "Found %d slugs from $table.", 'qtranslate' ), $results );
}
};

$msg = [];
$count_slugs( $wpdb->postmeta, QTX_SLUGS_META_PREFIX, $msg );
$count_slugs( $wpdb->termmeta, QTX_SLUGS_META_PREFIX, $msg );
if ( ! empty( $msg ) ) {
// Found some post/term meta with the new keys, no migrate to suggest (it can still be done manually).
return '';
}

$msg = [];
$count_slugs( $wpdb->postmeta, QTX_SLUGS_LEGACY_QTS_META_PREFIX, $msg );
$count_slugs( $wpdb->termmeta, QTX_SLUGS_LEGACY_QTS_META_PREFIX, $msg );

return empty ( $msg ) ? $msg : implode( '<br>', $msg );
}

/**
* Migrate slugs meta by migrating the legacy QTS postmeta and termmeta to QTX.
* Attention: current slugs meta are deleted if QTS slugs are found.
*
* @param bool $db_commit true to commit changes, false for dry-run mode.
*
* @return string messages giving details.
*/
function qtranxf_slugs_migrate_qts_meta( $db_commit ) {
global $wpdb;

$new_prefix = QTX_SLUGS_META_PREFIX;
$old_prefix = QTX_SLUGS_LEGACY_QTS_META_PREFIX;

/**
* Generic function that migrates QTS meta to QTX meta.
*
* @param string $table name of the meta table (postmeta, termmeta)
* @param string $colid column name of the parent id (post_id, term_id)
* @param string[] $msg array of messages, updated
*
* @return void
*/
$migrate_meta = function ( $table, $colid, $db_commit, &$msg ) use ( $wpdb, $old_prefix, $new_prefix ) {
$count_qts = $wpdb->get_var( "SELECT count(*) FROM $table WHERE meta_key like '$old_prefix%'" );
if ( ! $count_qts ) {
$msg[] = sprintf( __( "No slugs to migrate from %s.", 'qtranslate' ), $table );

return;
}
// Find the related post_id/term_id to delete (not meta_id), to ensure the migrated slugs replace the whole existing groups.
$id_to_delete = "SELECT DISTINCT($colid) FROM $table WHERE meta_key LIKE '$old_prefix%'";
if ( $db_commit ) {
$results = $wpdb->query( "DELETE FROM $table WHERE meta_key like '$new_prefix%' AND $colid in ($id_to_delete)" );
$msg[] = sprintf( __( "Deleted %d slugs from %s (%s).", 'qtranslate' ), $results ?: 0, $table, $new_prefix );
// Rename meta keys.
$results = $wpdb->query( "UPDATE $table SET meta_key = REPLACE(meta_key, '$old_prefix', '$new_prefix') WHERE meta_key LIKE '$old_prefix%'" );
$msg[] = sprintf( __( "Migrated %d slugs from %s (%s).", 'qtranslate' ), $results ?: 0, $table, $old_prefix );
} else {
// Dry-run mode: show how many slugs are to be deleted and migrated, no change in DB.
$results = $wpdb->get_var( "SELECT count(*) FROM $table WHERE meta_key like '$new_prefix%' AND $colid in ($id_to_delete)" );
$msg[] = sprintf( __( "Deleted %d slugs from %s (%s).", 'qtranslate' ), $results ?: 0, $table, $new_prefix );
$msg[] = sprintf( __( "Migrated %d slugs from %s (%s).", 'qtranslate' ), $count_qts, $table, $old_prefix );
}
};

$msg = [];
$migrate_meta( $wpdb->postmeta, 'post_id', $db_commit, $msg );
$migrate_meta( $wpdb->termmeta, 'term_id', $db_commit, $msg );

return implode( '<br>', $msg );
}

/**
* Migrate legacy QTS options to QTX.
* Attention: current slugs options are deleted if QTS options are found.
*
* @param bool $db_commit true to commit changes, false for dry-run mode.
*
* @return string messages giving details.
*/
function qtranxf_slugs_migrate_qts_options( $db_commit ) {
$msg = [];

$qts_options = get_option( QTX_SLUGS_LEGACY_QTS_OPTIONS_NAME );
if ( ! $qts_options ) {
return __( "No options to migrate.", 'qtranslate' );
}

$old_options = get_option( QTX_OPTIONS_MODULE_SLUGS );
if ( $old_options ) {
if ( $db_commit ) {
delete_option( QTX_OPTIONS_MODULE_SLUGS );
}
$msg[] = sprintf( __( "Deleted %d types from options.", 'qtranslate' ), count( $old_options ) );
}

$new_options = [];
// Drop the legacy prefix.
foreach ( $qts_options as $type => $slugs ) {
$type = str_replace( QTX_SLUGS_LEGACY_QTS_OPTIONS_PREFIX, '', $type );
$new_options[ $type ] = $slugs;
}
if ( $db_commit ) {
update_option( QTX_OPTIONS_MODULE_SLUGS, $new_options, false );
delete_option( QTX_SLUGS_LEGACY_QTS_OPTIONS_NAME );

global $qtranslate_slugs;
if ( $qtranslate_slugs->options_buffer != $new_options ) {
$qtranslate_slugs->options_buffer = $new_options;
flush_rewrite_rules();
}
}
$msg[] = sprintf( __( "Migrated %d types from options.", 'qtranslate' ), count( $new_options ) );

return implode( '<br/>', $msg );
}

/**
* Migrate slugs legacy QTS data (meta and options).
* Attention: current slugs data are deleted if QTS data are found.
*
* @param bool $db_commit true to commit changes, false for dry-run mode.
*
* @return string messages giving details.
*/
function qtranxf_slugs_migrate_qts_data( $db_commit ) {
$msg = [];
$msg[] = $db_commit ? __( 'Migrate slugs:', 'qtranslate' ) : __( "Dry-run mode:", 'qtranslate' );
$msg[] = qtranxf_slugs_migrate_qts_meta( $db_commit );
$msg[] = qtranxf_slugs_migrate_qts_options( $db_commit );

if ( $db_commit ) {
qtranxf_update_admin_notice( 'slugs-migrate', true ); // Hide the automatic admin notice.
}

return implode( '<br/>', $msg );
}
Loading

0 comments on commit 346d2ee

Please sign in to comment.