_option_name = $option_name; } else { throw new Exception( 'Unknown option name' ); } $this->_migrations_path = trailingslashit( $path ); $this->_required_version = $required_version; $this->_current_version = $this->_get_current_version(); $this->_product_name = $product_name; $this->_table_prefix = $table_prefix; $this->_reset_option_param = $reset_option_param ? $reset_option_param : ( $this->_option_name . '_reset' ); } protected function _get_current_version( $default = '0.0' ) { $this->_current_version = get_option( $this->_option_name, $default ); return $this->_current_version; } /** * Reset the wp_option so that all the migrations will run again at next check() * * @return bool */ protected function _reset() { $this->_current_version = '0.0'; return delete_option( $this->_option_name ); } protected function _update( $value ) { $this->_current_version = $value; return update_option( $this->_option_name, $value ); } /** * Compare the current db version with the required version and * Runs all the scrips from current version until the required version */ public function check() { if ( is_admin() && ! empty( $_REQUEST[ $this->_reset_option_param ] ) ) { $this->_reset(); } if ( version_compare( $this->_current_version, $this->_required_version, '<' ) ) { $scripts = $this->_get_scripts( $this->_current_version, $this->_required_version ); if ( ! empty( $scripts ) ) { ! defined( 'THRIVE_DB_UPGRADING' ) ? define( 'THRIVE_DB_UPGRADING', true ) : null; } /** @var $wpdb wpdb */ global $wpdb; /** * We only want to hide the errors not suppress them * in case we need to log them somewhere */ $wpdb->hide_errors(); $has_error = false; foreach ( $scripts as $file_path ) { $migration = new TD_DB_Migration( $this->_table_prefix, $file_path ); $has_error = $migration->run() === false; if ( $has_error ) { /* ERROR: we don't change the DB version option and notify the user about the last error */ break; } } if ( $has_error ) { $this->_last_db_error = $wpdb->last_error; add_action( 'admin_notices', array( $this, 'display_admin_error' ) ); } else { $this->_update( $this->_required_version ); } do_action( 'tve_after_db_migration', $this->_option_name ); } } /** * Get all DB update scripts from $from_version to $to_version. * * @param string $from_version from version. * @param string $to_version to version. * * @return array */ protected function _get_scripts( $from_version, $to_version ) { $scripts = array(); $dir = new DirectoryIterator( $this->_migrations_path ); foreach ( $dir as $file ) { /** * DirectoryIterator * * @var $file */ if ( $file->isDot() ) { continue; } $script_version = $this->_get_script_version( $file->getFilename() ); if ( empty( $script_version ) ) { continue; } if ( version_compare( $script_version, $from_version, '>' ) && version_compare( $script_version, $to_version, '<=' ) ) { $script_key = $this->_generate_script_key( $script_version, $scripts ); $scripts[ $script_key ] = $file->getPathname(); } } /** * Sort the scripts in the correct version order */ uksort( $scripts, 'version_compare' ); return $scripts; } /** * Generate a key for script if there is multiple migration file name with same version. * * @param string $script_version migration file name's version. * @param array $scripts list of scripts. * * @return string */ protected function _generate_script_key( $script_version, $scripts ) { if ( ! isset( $scripts[ $script_version ] ) ) { return $script_version; } return $script_version . '.0'; } /** * Parse the script_ame and returns its version * * @param string $script_name in the following format {name}-{[\d+].[\d+]}.php. * * @return string */ protected function _get_script_version( $script_name ) { if ( ! preg_match( '/(.+?)-(\d+)\.(\d+)(.\d+)?\.php/', $script_name, $m ) ) { return false; } return $m[2] . '.' . $m[3] . ( ! empty( $m[4] ) ? $m[4] : '' ); } /** * Display a WP Notification with last db error */ public function display_admin_error() { if ( ! $this->_last_db_error ) { return; } // @codingStandardsIgnoreStart echo '

' . sprintf( __( 'There was an error while updating the database tables%s. Detailed error message: %s. If you continue seeing this message, please contact %s', 'thrive-dash' ), $this->_product_name ? " ({$this->_product_name})" : '', '' . $this->_last_db_error . '', '' . __( 'Thrive Themes Support', 'thrive-dash' ) . '' ) . '

'; // @codingStandardsIgnoreEnd } /** * Collect all thrive db managers * Each plugin should hook into the 'thrive_prepare_migrations' hook */ public static function collect_migration_managers() { /** * Action hook. * Executed during the WP 'init' hook * * @since 2.0.49 */ try { do_action( 'thrive_prepare_migrations' ); } catch ( Exception $exception ) { /* do nothing for now. better safe than sorry. each plugin should catch errors */ } foreach ( static::$_instances as $instance ) { $instance->check(); } } /** * @param string $path Path to the migrations folder * @param string $option_name db wp_options table option name * @param string $required_version version to check against * @param string $product_name optional, a string used to display a nice product name to the user in case of errors * @param string $table_prefix optional, table prefix - can be used inside the migration file to automatically prepend it to table names * @param string $reset_option_param query parameter name for resetting the option holding the version * * @throws Exception */ public static function add_manager( $path, $option_name, $required_version, $product_name = '', $table_prefix = '', $reset_option_param = '' ) { static::$_instances [] = new static( $path, $option_name, $required_version, $product_name, $table_prefix, $reset_option_param ); } }