diff --git a/class/class-mainwp-child-install.php b/class/class-mainwp-child-install.php index c5cc2a6c..41e89c87 100644 --- a/class/class-mainwp-child-install.php +++ b/class/class-mainwp-child-install.php @@ -516,7 +516,7 @@ private function after_installed( $result, &$output ) { //phpcs:ignore -- NOSONA continue; } $thePlugin = get_plugin_data( $path . $srcFile ); - if ( null !== $thePlugin && '' !== $thePlugin && '' !== $thePlugin['Name'] ) { + if ( null !== $thePlugin && '' !== $thePlugin && '' !== $thePlugin['Name'] && 'readme.txt' !== $srcFile && 'README.md' !== $srcFile ) { // to fix: skip readme.txt. $args['type'] = 'plugin'; $args['Name'] = $thePlugin['Name']; $args['Version'] = $thePlugin['Version']; diff --git a/class/class-mainwp-child-updates.php b/class/class-mainwp-child-updates.php index 712c8a54..2177fb02 100644 --- a/class/class-mainwp-child-updates.php +++ b/class/class-mainwp-child-updates.php @@ -137,6 +137,8 @@ public function upgrade_plugin_theme() { ); MainWP_Child_Actions::get_instance()->init_custom_hooks( $hooks ); + static::enable_pre_auto_rollback_hooking(); + $plugin_update = false; // phpcs:disable WordPress.Security.NonceVerification if ( isset( $_POST['type'] ) && 'plugin' === $_POST['type'] ) { @@ -382,8 +384,9 @@ private function to_update_plugins( &$information, $plugins ) { //phpcs:ignore - $updated_plugins = array(); foreach ( $result as $plugin => $info ) { - $success = false; - $error = ''; + $success = false; + $problematic_update = false; + $error = ''; if ( empty( $info ) ) { $information['upgrades'][ $plugin ] = false; @@ -408,6 +411,10 @@ private function to_update_plugins( &$information, $plugins ) { //phpcs:ignore - } elseif ( is_wp_error( $info ) ) { $error = $info->get_error_message(); $information['upgrades_error'][ $plugin ] = $error; + $errors_codes = $info->get_error_codes(); + if ( is_array( $errors_codes ) && in_array( 'mainwp_update_error_code', $errors_codes ) ) { + $problematic_update = true; + } } else { $information['upgrades'][ $plugin ] = true; $success = true; @@ -427,6 +434,10 @@ private function to_update_plugins( &$information, $plugins ) { //phpcs:ignore - $info['error'] = $error; } + if ( $problematic_update ) { + $info['rollback'] = 1; + } + $current_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); if ( ! is_array( $current_info ) ) { $current_info = array(); @@ -582,9 +593,18 @@ private function to_upgrade_themes( &$information, $themes, $last_update ) { //p $themes_info = MainWP_Child_Actions::get_instance()->get_current_themes_info(); $updated_themes = array(); foreach ( $result as $theme => $value ) { - $success = false; + $success = false; + $error = ''; + $problematic_update = false; if ( empty( $value ) ) { $information['upgrades'][ $theme ] = false; + } elseif ( is_wp_error( $value ) ) { + $error = $value->get_error_message(); + $information['upgrades'][ $theme ] = false; + $errors_codes = $value->get_error_codes(); + if ( is_array( $errors_codes ) && in_array( 'mainwp_update_error_code', $errors_codes ) ) { + $problematic_update = true; + } } else { $information['upgrades'][ $theme ] = true; $success = true; @@ -601,6 +621,14 @@ private function to_upgrade_themes( &$information, $themes, $last_update ) { //p 'success' => $success ? 1 : 0, ); + if ( $problematic_update ) { + $info['rollback'] = 1; + } + + if ( ! empty( $error ) ) { + $info['error'] = $error; + } + $current_info = wp_get_theme( $theme ); if ( ! is_array( $current_info ) ) { $current_info = array(); @@ -650,33 +678,206 @@ private function to_upgrade_themes( &$information, $themes, $last_update ) { //p * @param array $mwp_premium_updates_to_do_slugs An array containing the list of premium themes slugs to update. */ private function update_premiums_to_do( &$information, $premiumUpgrader, $mwp_premium_updates_to_do, $mwp_premium_updates_to_do_slugs ) { //phpcs:ignore -- NOSONAR - complex. + + if ( ! isset( $information['other_data']['updated_data'] ) ) { + $information['other_data']['updated_data'] = array(); + } + + $plugins_info = MainWP_Child_Actions::get_instance()->get_current_plugins_info(); + $updated_plugins = array(); + // Upgrade via WP. // @see wp-admin/update.php. - $result = $premiumUpgrader->bulk_upgrade( $mwp_premium_updates_to_do_slugs ); - if ( ! empty( $result ) ) { - foreach ( $result as $plugin => $info ) { - if ( ! empty( $info ) ) { - $information['upgrades'][ $plugin ] = true; + $results = $premiumUpgrader->bulk_upgrade( $mwp_premium_updates_to_do_slugs ); + if ( ! empty( $results ) ) { + foreach ( $results as $plugin => $result ) { + if ( ! empty( $result ) ) { + + if ( is_wp_error( $result ) ) { + $slug = $plugin; + + $update_info = array(); + if ( is_array( $mwp_premium_updates_to_do ) ) { + $update_info = array_filter( + $mwp_premium_updates_to_do, + function ( $e ) use ( $slug ) { + return isset( $e['slug'] ) && $e['slug'] === $slug; + } + ); + if ( $update_info ) { + $update_info = current( $update_info ); + } + if ( ! is_array( $update_info ) ) { + $update_info = array(); + } + } + + $problematic_update = false; + $error = $result->get_error_message(); + $errors_codes = $result->get_error_codes(); + if ( is_array( $errors_codes ) && in_array( 'mainwp_update_error_code', $errors_codes ) ) { + $problematic_update = true; + } + + $old_info = isset( $plugins_info[ $slug ] ) ? $plugins_info[ $slug ] : array(); + + if ( ! is_array( $old_info ) ) { + $old_info = array(); + } + $name = isset( $old_info['Name'] ) ? $old_info['Name'] : ''; + + $info = array( + 'name' => $name, + 'old_version' => isset( $old_info['Version'] ) ? $old_info['Version'] : '', + 'slug' => $slug, + 'success' => 0, + ); + + if ( empty( $info['old_version'] ) && ! empty( $update_info['Version'] ) ) { + $info['old_version'] = $update_info['Version']; + } + + if ( $problematic_update ) { + $info['rollback'] = 1; + } + + if ( ! empty( $error ) ) { + $info['error'] = $error; + } + + $current_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug ); + if ( ! is_array( $current_info ) ) { + $current_info = array(); + } + + if ( ! empty( $current_info['Version'] ) ) { + $info['version'] = $current_info['Version']; + } + + if ( empty( $info['name'] ) && ! empty( $current_info['Name'] ) ) { + $info['name'] = $current_info['Name']; + } + + if ( empty( $info['name'] ) ) { + $info['name'] = $slug; + } + + $information['other_data']['updated_data'][ $slug ] = $info; + + } else { + $information['upgrades'][ $plugin ] = true; + } + $updated_plugins[ $slug ] = 1; // to prevent try next and incorrect. } } } // Upgrade via callback. foreach ( $mwp_premium_updates_to_do as $update ) { + + if ( isset( $update['slug'] ) && isset( $updated_plugins[ $update['slug'] ] ) ) { + continue; + } + $slug = ( isset( $update['slug'] ) ? $update['slug'] : $update['Name'] ); if ( isset( $update['url'] ) ) { - $installer = new \WP_Upgrader(); - $result = $installer->run( + + $installer = new \WP_Upgrader(); + + $hook_extra = array(); + + if ( 'plugin' === $update['type'] && false !== strpos( $slug, '/' ) ) { + $hook_extra = array( + 'plugin' => $slug, + 'temp_backup' => array( + 'slug' => dirname( $slug ), + 'src' => WP_PLUGIN_DIR, + 'dir' => 'plugins', + ), + ); + } + + $result = $installer->run( array( 'package' => $update['url'], 'destination' => ( 'plugin' === $update['type'] ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/themes' ), 'clear_destination' => true, 'clear_working' => true, - 'hook_extra' => array(), + 'hook_extra' => $hook_extra, ) ); - $information['upgrades'][ $slug ] = ( ! is_wp_error( $result ) && ! empty( $result ) ); + + $success = ! is_wp_error( $result ) && ! empty( $result ); + $information['upgrades'][ $slug ] = $success; + + $problematic_update = false; + + $error = ''; + + if ( is_wp_error( $result ) ) { + $error = $result->get_error_message(); + $errors_codes = $result->get_error_codes(); + if ( is_array( $errors_codes ) && in_array( 'mainwp_update_error_code', $errors_codes ) ) { + $problematic_update = true; + } + } + + $name = ! empty( $update['name'] ) ? $update['name'] : ''; + + if ( empty( $name ) ) { + $name = ! empty( $update['Name'] ) ? $update['Name'] : ''; + } + + $old_info = array(); + + if ( 'plugin' === $update['type'] ) { + $old_info = isset( $plugins_info[ $slug ] ) ? $plugins_info[ $slug ] : array(); + if ( ! is_array( $old_info ) ) { + $old_info = array(); + } + } + + $info = array( + 'name' => $name, + 'old_version' => isset( $old_info['version'] ) ? $old_info['version'] : '', + 'slug' => $slug, + 'success' => $success ? 1 : 0, + ); + + if ( $problematic_update ) { + $info['rollback'] = 1; + } + + if ( ! empty( $error ) ) { + $info['error'] = $error; + } + + if ( 'plugin' === $update['type'] ) { + $current_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug ); + if ( ! is_array( $current_info ) ) { + $current_info = array(); + } + + if ( ! empty( $current_info['Version'] ) ) { + $info['version'] = $current_info['Version']; + } + + if ( empty( $info['name'] ) && ! empty( $current_info['Name'] ) ) { + $info['name'] = $current_info['Name']; + } + } + + if ( empty( $info['version'] ) && ! empty( $update['version'] ) ) { + $info['version'] = $update['version']; + } + + if ( empty( $info['name'] ) ) { + $info['name'] = $slug; + } + + $information['other_data']['updated_data'][ $slug ] = $info; + } elseif ( isset( $update['callback'] ) ) { if ( is_array( $update['callback'] ) && isset( $update['callback'][0] ) && isset( $update['callback'][1] ) ) { $update_result = call_user_func( @@ -1191,4 +1392,25 @@ public function upgrade_translation() { //phpcs:ignore -- NOSONAR - complex. $information['sync'] = MainWP_Child_Stats::get_instance()->get_site_stats( array(), false ); MainWP_Helper::write( $information ); } + + + /** + * Method enable_pre_auto_rollback_hooking(). + */ + public static function enable_pre_auto_rollback_hooking() { + add_filter( 'upgrader_install_package_result', array( static::get_class_name(), 'upgrader_auto_rollback_hooking' ), 99, 2 ); + } + + /** + * Method upgrader_auto_rollback_hooking(). + * + * @param array|WP_Error $result Result from WP_Upgrader::install_package(). + * @param array $hook_extra Extra arguments passed to hooked filters. + */ + public static function upgrader_auto_rollback_hooking( $result, $hook_extra = array() ) { + if ( is_wp_error( $result ) && is_array( $hook_extra ) && ! empty( $hook_extra['temp_backup'] ) ) { + $result->add( 'mainwp_update_error_code', 'Update error.' ); + } + return $result; + } } diff --git a/class/class-mainwp-child.php b/class/class-mainwp-child.php index 0fc323f7..d567472f 100644 --- a/class/class-mainwp-child.php +++ b/class/class-mainwp-child.php @@ -30,7 +30,7 @@ class MainWP_Child { * * @var string MainWP Child plugin version. */ - public static $version = '5.1-RC1'; // NOSONAR - not IP. + public static $version = '5.1'; // NOSONAR - not IP. /** * Private variable containing the latest MainWP Child update version. diff --git a/class/class-mainwp-clone-install.php b/class/class-mainwp-clone-install.php index eccaf5de..f1297f87 100644 --- a/class/class-mainwp-clone-install.php +++ b/class/class-mainwp-clone-install.php @@ -440,13 +440,40 @@ public function get_config_contents() { } elseif ( $this->check_zip_support() ) { $zip = new \ZipArchive(); $zipRes = $zip->open( $this->file ); - if ( $zipRes ) { - $content = $zip->getFromName( 'clone/config.txt' ); - $zip->close(); - return $content; + // Check if the ZIP file was successfully opened. + if ( true !== $zipRes ) { + // Try to get the content of 'clone/config.txt' from the ZIP file. + $content = $zip->getFromName( 'clone/config.txt' ); + // Check if the content was successfully retrieved. + if ( false !== $content ) { + $zip->close(); + return $content; + } + } else { + $err = ''; + // Handle errors based on the result code. + switch ( $zipRes ) { + case \ZipArchive::ER_NOENT: + $err = "ZIP file does not exist.\n"; + break; + case \ZipArchive::ER_NOZIP: + $err = "Not a ZIP archive or corrupted.\n"; + break; + case \ZipArchive::ER_OPEN: + $err = "Failed to open the ZIP file.\n"; + break; + case \ZipArchive::ER_READ: + $err = "Failed to read the ZIP file.\n"; + break; + default: + $err = "Failed to open the ZIP file. Error code: $zipRes\n"; + break; + } + if ( $err ) { + error_log( $err ); //phpcs:ignore -- NOSONAR - for dev. + } } - return false; } else { $zip = new \PclZip( $this->file ); diff --git a/class/class-mainwp-clone-page.php b/class/class-mainwp-clone-page.php index 8b244a1c..81c12ee6 100644 --- a/class/class-mainwp-clone-page.php +++ b/class/class-mainwp-clone-page.php @@ -69,9 +69,9 @@ public static function print_scripts() { $ui = $wp_scripts->query( 'jquery-ui-core' ); $version = $ui->ver; if ( MainWP_Helper::starts_with( $version, '1.10' ) ) { - wp_enqueue_style( 'jquery-ui-style', plugins_url( '/css/1.10.4/jquery-ui.min.css', __DIR__ ), array(), '1.10', 'all' ); + wp_enqueue_style( 'jquery-ui-style', plugins_url( 'css/1.10.4/jquery-ui.min.css', __DIR__ ), array(), '1.10', 'all' ); } else { - wp_enqueue_style( 'jquery-ui-style', plugins_url( '/css/1.11.1/jquery-ui.min.css', __DIR__ ), array(), '1.11', 'all' ); + wp_enqueue_style( 'jquery-ui-style', plugins_url( 'css/1.11.1/jquery-ui.min.css', __DIR__ ), array(), '1.11', 'all' ); } } diff --git a/class/class-mainwp-clone.php b/class/class-mainwp-clone.php index 7afe2f88..47e292c5 100644 --- a/class/class-mainwp-clone.php +++ b/class/class-mainwp-clone.php @@ -411,6 +411,9 @@ public function clone_backup_create() { // Send request to the childsite! + $timeout = 20 * 60 * 60; + MainWP_Helper::set_limit( $timeout ); + /** * The installed version of WordPress. * @@ -743,7 +746,7 @@ public function clone_backup_extract() { * @uses \MainWP\Child\MainWP_Helper::get_mainwp_dir() */ private function clone_backup_get_file( $file, &$testFull ) { - if ( '' === $file ) { + if ( empty( $file ) ) { $dirs = MainWP_Helper::get_mainwp_dir( 'backup', false ); $backupdir = $dirs[0]; $files = glob( $backupdir . 'download-*' ); diff --git a/class/class-mainwp-connect.php b/class/class-mainwp-connect.php index 1b8a6a73..4f73c68d 100644 --- a/class/class-mainwp-connect.php +++ b/class/class-mainwp-connect.php @@ -111,13 +111,11 @@ public function register_site() { // phpcs:ignore -- NOSONAR - Current complexit } // Check if the user exists and if yes, check if it's Administartor user. - if ( isset( $_POST['user'] ) ) { - if ( empty( $_POST['user'] ) || ! $this->login( wp_unslash( $_POST['user'] ) ) ) { - MainWP_Helper::instance()->error( esc_html__( 'Unexisting administrator user. Please verify that it is an existing administrator.', 'mainwp-child' ) ); - } - if ( ! MainWP_Helper::is_admin() ) { - MainWP_Helper::instance()->error( esc_html__( 'User is not an administrator. Please use an administrator user to establish the connection.', 'mainwp-child' ) ); - } + if ( empty( $_POST['user'] ) || ! $this->login( wp_unslash( $_POST['user'] ) ) ) { + MainWP_Helper::instance()->error( esc_html__( 'Unexisting administrator user. Please verify that it is an existing administrator.', 'mainwp-child' ) ); + } + if ( ! MainWP_Helper::is_admin() ) { + MainWP_Helper::instance()->error( esc_html__( 'User is not an administrator. Please use an administrator user to establish the connection.', 'mainwp-child' ) ); } // Update the mainwp_child_pubkey option. diff --git a/class/class-mainwp-helper.php b/class/class-mainwp-helper.php index 5b8e5bed..ea98a842 100644 --- a/class/class-mainwp-helper.php +++ b/class/class-mainwp-helper.php @@ -60,6 +60,27 @@ public static function write( $value ) { die( '' . base64_encode( $output ) . '' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions,WordPress.Security.EscapeOutput -- base64_encode function is used for backwards compatibility. } + /** + * Method write_feedback() + * + * Send response feedback data to be sent to the MainWP Dashboard. + * + * @param mixed $value Contains information to be send. + * @param mixed $action action send message. + */ + public static function write_feedback( $value, $action = '' ) { + /** + * Action: process send feedback message. + * + * @since 5.1 + */ + do_action( 'mainwp_child_before_send_feedback_message', $value, $action ); + + $output = wp_json_encode( $value ); + + echo '' . base64_encode( $output ) . ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions,WordPress.Security.EscapeOutput -- base64_encode function is used for backwards compatibility. + } + /** * Method send() diff --git a/mainwp-child.php b/mainwp-child.php index d62ef32f..091b42fa 100644 --- a/mainwp-child.php +++ b/mainwp-child.php @@ -12,7 +12,7 @@ * Author: MainWP * Author URI: https://mainwp.com * Text Domain: mainwp-child - * Version: 5.1-RC1 + * Version: 5.1 * Requires at least: 5.4 * Requires PHP: 7.4 */ diff --git a/readme.txt b/readme.txt index f3f5e299..3a68e30e 100644 --- a/readme.txt +++ b/readme.txt @@ -7,7 +7,7 @@ Plugin URI: https://mainwp.com Requires at least: 6.2 Tested up to: 6.5.3 Requires PHP: 7.4 -Stable tag: 5.1-RC1 +Stable tag: 5.1 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -111,6 +111,14 @@ We have an extensive FAQ with more questions and answers [here](https://mainwp.c == Changelog == += 5.1 - 6-18-2024 = + +* Fixed: An issue with submitting Time Capsule settings. +* Added: Integrated a Rollback feature to revert plugins to the last stable version in case of update errors. +* Updated: Addressed coding standard issues found by SonarCloud. + +[See Video Changelog](https://youtu.be/OtqrgU8q5RA) + = 5.0.1.1 - 4-23-2024 = * Added: Support for the RunCloud Hub plugin in the Cache Control.