| Current File : /home/digitaw/www/wp-content/plugins/wordpress-popup/inc/update/class-hustle-410-migration.php |
<?php
/**
* File for Hustle_410_Migration class.
*
* @package Hustle
* @since 4.1.0
*/
/**
* Class Hustle_410_Migration.
*
* This class handles the migration when going from 4.0.x to 4.1.x
* We adjusted the modules' visibility conditions, and we need the
* conditions from 4.0.x to remain compatible to how we're handling them from 4.1.x on.
* Note this won't make the modules behave as they used to. This just makes the conditions that
* changed, such as Source of Arrival, Posts/Pages/Tags/Categories all/none options, and so on,
* compatible with their expected values in 4.1.x.
*
* @since 4.1.0
*/
class Hustle_410_Migration {
/**
* Flag name to mark the migration as "done".
*
* @since 4.1.0
*/
const MIGRATION_FLAG = 'hustle_40_migrated';
/**
* Meta key for the visibility backup.
*
* @since 4.1.0
*/
const VISIBILITY_BACKUP_META = 'visibility_backup_40x';
/**
* Instance of the wpdb class.
*
* @since 4.1.0
* @var object
*/
private $wpdb;
/**
* Array of metas to be inserted.
*
* @since 4.1.0
* @var array
*/
private $backup_metas = array();
/**
* Hustle_401_Migration class constructor.
*/
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
if ( $this->is_migrating() ) {
add_action( 'init', array( $this, 'do_migration' ) );
}
}
/**
* Checks whether we should run da migration.
*
* @since 4.1.0
*
* @return bool
*/
private function is_migrating() {
// If migration is being forced, do it.
if ( filter_input( INPUT_GET, 'run_41_migration', FILTER_VALIDATE_BOOLEAN ) ) {
return true;
}
// If migration was already done, skip.
if ( Hustle_Migration::is_migrated( self::MIGRATION_FLAG ) ) {
$prev_version = Hustle_Migration::get_previous_installed_version();
if ( $prev_version && version_compare( $prev_version, '4.1', '>=' ) ) {
Hustle_Notifications::add_dismissed_notification( '41_visibility_behavior_update' );
}
return false;
}
return ! self::is_fresh();
}
/**
* Checks if it's a fresh 4.1 installation or not
*
* @since 4.1.0
*
* @return bool
*/
private static function is_fresh() {
$is_fresh = Hustle_Db::$is_fresh_install;
if ( $is_fresh ) {
Hustle_Migration::migration_passed( self::MIGRATION_FLAG );
}
return $is_fresh;
}
/**
* Does the migration from 4.0.x to 4.1.x.
*
* @since 4.1.0
*/
public function do_migration() {
$limit = apply_filters( 'hustle_40_migration_limit', 100 );
// Restore the 4.0.x metas if they exist.
// Avoid duplicated backup metas and running migration on already migrated settings.
if ( $this->is_backup_created() ) {
$this->restore( false );
}
do {
$offset = get_option( 'hustle_40_migration_offset', 0 );
$m2_modules = get_option( 'hustle_notice_stop_support_m2', array() );
$conditions = $this->get_all_hustle_module_conditions( $limit, $offset );
foreach ( $conditions as $meta ) {
// Let's keep aside this meta to save them all together at the end.
$this->queue_meta_to_backup( $meta );
$meta_id = $meta->meta_id;
$value = json_decode( $meta->meta_value, true );
if ( empty( $value['conditions'] ) || ! is_array( $value['conditions'] ) || 1 > count( $value['conditions'] ) ) {
$group_id = substr( md5( wp_rand() ), 0, 10 );
$value['conditions'] = array(
$group_id => array(
'filter_type' => 'all',
'group_id' => $group_id,
),
);
}
foreach ( $value['conditions'] as $group_id => $conds ) {
if ( ! empty( $conds['ms_membership'] ) || ! empty( $conds['ms_membership-n'] ) ) {
$m2_modules[] = $meta_id;
}
unset( $conds['group_id'], $conds['filter_type'] );
$count_conds = count( $conds );
if ( ! $count_conds || ! empty( $conds['page_404'] ) ) {
// Hide on 404 page according old behavior.
$filter_type = ! $count_conds ? 'except' : 'only';
$conds['wp_conditions'] = array(
'wp_conditions' => array( 'is_404' ),
'filter_type' => $filter_type,
);
// By default, we start showing modules on 404 page.
unset( $conds['page_404'] );
}
if ( ! empty( $conds['source_of_arrival']['source_external'] )
&& 'true' === $conds['source_of_arrival']['source_external'] ) {
$conds['source_of_arrival']['source_direct'] = 'true';
}
// Remove 'all' values.
$post_types = Opt_In_Utils::get_post_types();
$cpts = array_keys( $post_types );
$types = array_merge( array( 'posts', 'pages', 'tags', 'categories' ), $cpts );
foreach ( $types as $type ) {
if (
! empty( $conds[ $type ][ $type ] ) &&
in_array( 'all', $conds[ $type ][ $type ], true ) ||
! empty( $conds[ $type ]['selected_cpts'] ) &&
in_array( 'all', $conds[ $type ]['selected_cpts'], true )
) {
unset( $conds[ $type ][ $type ], $conds[ $type ]['selected_cpts'] );
$conds[ $type ]['filter_type'] = empty( $conds[ $type ]['filter_type'] ) || 'only' !== $conds[ $type ]['filter_type'] ? 'only' : 'except';
}
}
// Transform condition rules according new logic.
$and_rules = array(
'visitor_logged_in_status',
'visitor_device',
'from_referrer',
'source_of_arrival',
'on_url',
'visitor_commented',
'visitor_country',
'shown_less_than',
);
$or_rules = array_diff_key( $conds, array_flip( $and_rules ) );
// Get "AND" conditions.
$and_conds = array_intersect_key( $conds, array_flip( $and_rules ) );
if ( isset( $or_rules['pages'] ) && 1 < count( $or_rules ) ) {
$this->add_new_group(
$value,
array_merge( $and_conds, array( 'pages' => $conds['pages'] ) )
);
unset( $conds['pages'] );
unset( $or_rules['pages'] );
}
$post_group = array_intersect_key( $or_rules, array_flip( array( 'posts', 'tags', 'categories' ) ) );
if ( ! empty( $post_group ) && count( $post_group ) < count( $or_rules ) ) {
$this->add_new_group( $value, array_merge( $and_conds, $post_group ) );
unset( $conds['posts'], $conds['tags'], $conds['categories'] );
unset( $or_rules['posts'], $or_rules['tags'], $or_rules['categories'] );
}
foreach ( $or_rules as $name => $args ) {
if ( 2 > count( $or_rules ) ) {
break;
}
$this->add_new_group(
$value,
array_merge( $and_conds, array( $name => $args ) )
);
unset( $conds[ $name ] );
unset( $or_rules[ $name ] );
}
// Add new show/hide option.
$conds['show_or_hide_conditions'] = 'show';
$conds['filter_type'] = 'all';
$conds['group_id'] = $group_id;
$value['conditions'][ $group_id ] = $conds;
}
// Save transformed conditions.
$this->wpdb->update(
Hustle_Db::modules_meta_table(),
array( 'meta_value' => wp_json_encode( $value ) ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
array( 'meta_id' => $meta_id )
);
wp_cache_delete( $meta->module_id, 'hustle_module_meta' );
}
$count_conditions = count( $conditions );
$offset += $limit;
update_option( 'hustle_40_migration_offset', $offset );
update_option( 'hustle_notice_stop_support_m2', $m2_modules );
// Store the backup metas.
$this->insert_backup_metas();
} while ( $count_conditions === $limit );
Hustle_Migration::migration_passed( self::MIGRATION_FLAG );
delete_option( 'hustle_40_migration_offset' );
}
/**
* Gets all the stored visibility metas.
*
* @since 4.1.0
*
* @param int $limit Query limit.
* @param int $offset Query offset.
* @return array
*/
private function get_all_hustle_module_conditions( $limit, $offset ) {
$modules_table = Hustle_Db::modules_meta_table();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$query = $this->wpdb->prepare( "SELECT meta_id, module_id, meta_value FROM {$modules_table} WHERE meta_key = 'visibility' LIMIT %d OFFSET %d", intval( $limit ), intval( $offset ) );
$results = $this->wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $results;
}
/**
* Adds a new group within the passed visibility settings.
*
* @since 4.1.0
*
* @param array $conditions Reference to the settings to which this group will be added.
* @param array $args Group's properties and conditions.
*/
private function add_new_group( &$conditions, $args ) {
$new_group_id = substr( md5( wp_rand() ), 0, 10 );
if ( ! isset( $args['filter_type'] ) ) {
$args['filter_type'] = 'all';
$args['group_id'] = $new_group_id;
}
// Add new show/hide option.
$args['show_or_hide_conditions'] = 'show';
$conditions['conditions'][ $new_group_id ] = $args;
}
/**
* Checks if there's any backup meta already created.
*
* These should be deleted when rolling back, so if a single one exists,
* it's likely that we have them all and creating new ones isn't needed.
*
* @since 4.1.0
*
* @return bool
*/
public function is_backup_created() {
$modules_table = Hustle_Db::modules_meta_table();
$query = $this->wpdb->prepare( "SELECT meta_id FROM {$modules_table} WHERE meta_key = %s LIMIT 1", self::VISIBILITY_BACKUP_META ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$results = $this->wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$is_backup_created = 0 < count( $results );
return $is_backup_created;
}
/**
* Adds the old meta to be inserted later on.
*
* Store the old 4.0.x meta formatted for INSERT in the class property,
* to be saved in the db later on. Used when looping through the metas.
*
* @since 4.1.0
*
* @param object $meta The original 4.0.x visibility meta retrieved from the db.
*/
private function queue_meta_to_backup( $meta ) {
// Format this ready for IMPORT.
$row = $this->wpdb->prepare( '(%d, %s, %s)', $meta->module_id, self::VISIBILITY_BACKUP_META, $meta->meta_value );
$this->backup_metas[] = $row;
}
/**
* Inserts the backup visibility metas into the db.
*
* @since 4.1.0
*/
private function insert_backup_metas() {
// Skip if there isn't any meta queued.
if ( empty( $this->backup_metas ) ) {
return;
}
$modules_meta_table = Hustle_Db::modules_meta_table();
$backup_values = implode( ', ', $this->backup_metas );
// Build the query with the already queued metas.
$sql = "INSERT INTO {$modules_meta_table} (module_id, meta_key, meta_value) VALUES {$backup_values}";
// Do the insert.
$this->wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
// Empty the property.
$this->backup_metas = array();
}
/*
---------- Deactivation stuff ----------
*/
/**
* Deletes the 4.1.x visibility metas and restore the ones for 4.0.x.
*
* This will allow admins to find their old visibility settings
* when they activate 4.0.x again.
*
* @since 4.1.0
*
* @param bool $check_if_exists Skip check for existing backup. False only if already checked.
*
* @throws Exception When there's no data to restore or the restore failed.
* @return bool
*/
private function restore( $check_if_exists = true ) {
try {
// If there's nothing to restore, abort.
if ( $check_if_exists && ! $this->is_backup_created() ) {
throw new Exception( __( "There's no backup to restore.", 'hustle' ) );
}
$modules_meta_table = Hustle_Db::modules_meta_table();
// Get the meta id and module id of the modules with backups.
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$backup_sql = $this->wpdb->prepare( "SELECT meta_id, module_id FROM {$modules_meta_table} WHERE meta_key = %s", self::VISIBILITY_BACKUP_META );
$backup_result = $this->wpdb->get_results( $backup_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$backup_modules_id = array_column( $backup_result, 'module_id' );
if ( empty( $backup_modules_id ) ) {
throw new Exception( __( "There's no backup to restore.", 'hustle' ) );
}
// Delete the visibility conditions created for 4.1.x migration.
$modules_id_holder = implode( ', ', array_fill( 0, count( $backup_modules_id ), '%s' ) );
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$delete_sql = $this->wpdb->prepare( "DELETE FROM {$modules_meta_table} WHERE module_id IN ($modules_id_holder) AND meta_key = 'visibility'", $backup_modules_id );
$deleted = $this->wpdb->query( $delete_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
// Check if the metas were successfully deleted.
if ( false === $deleted ) {
throw new Exception( __( "The 4.1.x visibility metas couldn't be deleted.", 'hustle' ) );
}
// Restore the visibility conditions created in 4.0.x.
$backup_metas_id = array_column( $backup_result, 'meta_id' );
$metas_id_holder = implode( ', ', array_fill( 0, count( $backup_metas_id ), '%s' ) );
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$rename_sql = $this->wpdb->prepare( "UPDATE {$modules_meta_table} SET meta_key = 'visibility' WHERE meta_id IN ($metas_id_holder)", $backup_metas_id );
$renamed = $this->wpdb->query( $rename_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
// Check if the old metas were successfully restored.
if ( false === $renamed ) {
throw new Exception( __( "The 4.0.x visibility metas couldn't be restored.", 'hustle' ) );
}
} catch ( Exception $e ) {
$message = Opt_In_Utils::maybe_log( __METHOD__, $e->getMessage() );
return false;
}
// Clear caches.
foreach ( $backup_modules_id as $id ) {
wp_cache_delete( $id, 'hustle_module_meta' );
}
// Reset the migration flag so migration runs next time 4.1.x is enabled.
Hustle_Migration::remove_migration_passed_flag( self::MIGRATION_FLAG );
return true;
}
}