| @@ -1,96 +1,34 @@ | ||
| 1 | 1 | <?php |
| 2 | -defined( 'PLUGIN_REVISIONS_PLUGIN_DIR' ) || exit; | |
| 2 | +/** | |
| 3 | + * It includes the code for the backend. | |
| 3 | 4 | |
| 5 | + * @package Plugversions | |
| 6 | + */ | |
| 7 | +defined( 'PLUGIN_REVISIONS_PLUGIN_DIR' ) || exit; // Exit if not accessed from Plugversions. | |
| 8 | + | |
| 4 | 9 | if( wp_doing_ajax() ){ |
| 5 | - require_once PLUGIN_REVISIONS_PLUGIN_DIR.'/admin/pr-ajax-admin.php'; | |
| 10 | + //* Include the Ajax admin file. | |
| 11 | + require_once PLUGIN_REVISIONS_PLUGIN_DIR . '/admin/pr-ajax-admin.php'; | |
| 6 | 12 | } |
| 7 | 13 | |
| 8 | -//Actions triggered after plugin activation or after a new site of a multisite installation is created | |
| 9 | -register_activation_hook( __FILE__,function(){ | |
| 10 | - if( !eos_plugin_revision_key() ){ | |
| 14 | +register_activation_hook( __FILE__, function() { | |
| 15 | + /** | |
| 16 | + * Actions triggered after plugin activation or after a new site of a multisite installation is created | |
| 17 | + * | |
| 18 | + * @since 0.0.1 | |
| 19 | + */ | |
| 20 | + if( ! eos_plugin_revision_key() ){ | |
| 11 | 21 | update_site_option( 'plugin_revisions',array( 'time' => time() ) ); |
| 12 | 22 | } |
| 13 | 23 | } ); |
| 14 | 24 | |
| 15 | -add_filter( 'upgrader_package_options',function( $options){ | |
| 16 | - if( isset( $options['destination'] ) && isset( $options['hook_extra'] ) ){ | |
| 17 | - $hook_extra = $options['hook_extra']; | |
| 18 | - if( isset( $hook_extra['plugin'] ) ){ | |
| 19 | - $plugin = $hook_extra['plugin']; | |
| 20 | - $plugin_name = dirname( $plugin ); | |
| 21 | - $path = sanitize_option( 'upload_path',$options['destination'].'/'.$plugin ); | |
| 22 | - if( file_exists( $path ) ){ | |
| 23 | - $plugin_data = get_plugin_data( $path ); | |
| 24 | - $version = $plugin_data['Version']; | |
| 25 | - if( !class_exists( 'WP_Upgrader' ) ){ | |
| 26 | - if( file_exists( ABSPATH.'wp-admin/includes/class-wp-upgrader.php' ) ){ | |
| 27 | - require_once ABSPATH.'/wp-admin/includes/class-wp-upgrader.php'; | |
| 28 | - } | |
| 29 | - } | |
| 30 | - if( class_exists( 'WP_Upgrader' ) ){ | |
| 31 | - $upgrader = new WP_Upgrader(); | |
| 32 | - $key = eos_plugin_revision_key(); | |
| 33 | - if( $key ){ | |
| 34 | - $destination = str_replace( dirname( $plugin ),'pr-'.$key.'-'.sanitize_option( 'upload_path',$version ).'-ver-'.dirname( $plugin ),sanitize_option( 'upload_path',plugin_dir_path( $path )) ); | |
| 35 | - if( !is_dir( $destination ) ){ | |
| 36 | - global $wp_filesystem; | |
| 37 | - if( empty( $wp_filesystem ) ){ | |
| 38 | - require_once ( ABSPATH .'/wp-admin/includes/file.php' ); | |
| 39 | - WP_Filesystem(); | |
| 40 | - } | |
| 41 | - $wp_filesystem->mkdir( $destination ); | |
| 42 | - } | |
| 43 | - if( !empty( $wp_filesystem ) && $wp_filesystem->is_dir( $destination ) ){ | |
| 44 | - $result = copy_dir( plugin_dir_path( $path ),$destination ); | |
| 45 | - eos_plugin_revisions_remove_versions( apply_filters( 'max_plugin_revisions',4 ),$plugin_name ); | |
| 46 | - } | |
| 47 | - } | |
| 48 | - } | |
| 49 | - } | |
| 50 | - } | |
| 51 | - } | |
| 52 | - return $options; | |
| 53 | -},10,4 ); | |
| 54 | - | |
| 55 | -add_filter( 'all_plugins',function( $plugins ){ | |
| 56 | - //Remove plugin revisions from plugins table in the page wp-admin/plugins.php | |
| 57 | - $key = eos_plugin_revision_key(); | |
| 58 | - if( $key ){ | |
| 59 | - foreach( $plugins as $plugin => $arr ){ | |
| 60 | - if( false !== strpos( $plugin,'pr-'.$key.'-' ) ){ | |
| 61 | - unset( $plugins[$plugin] ); | |
| 62 | - $parent_plugin = str_replace( 'pr-'.$key.'-','',$plugin ); | |
| 63 | - $parent_pluginArr = explode( '-ver-',$parent_plugin ); | |
| 64 | - $parent_plugin = isset( $parent_pluginArr[1] ) ? $parent_pluginArr[1] : $parent_plugin; | |
| 65 | - add_filter( 'plugin_action_links_'.$parent_plugin,function( $actions, $plugin_file, $plugin_data, $context ){ | |
| 66 | - if( !current_user_can( 'activate_plugin' ) ) return $actions; | |
| 67 | - $key = eos_plugin_revision_key(); | |
| 68 | - $all_dirs = scandir( dirname( PLUGIN_REVISIONS_PLUGIN_DIR ) ); | |
| 69 | - if( $all_dirs && !empty( $all_dirs ) ){ | |
| 70 | - $new_action_links = ''; | |
| 71 | - $plugin = dirname( $plugin_file ); | |
| 72 | - foreach( $all_dirs as $dir ){ | |
| 73 | - if( false !== strpos( $dir,'pr-'.$key.'-' ) && substr( $dir,-strlen( $plugin ),strlen( $plugin ) ) === $plugin ){ | |
| 74 | - $ver = str_replace( 'pr-'.$key.'-','',$dir ); | |
| 75 | - $verArr = explode( '-ver-',$ver ); | |
| 76 | - $ver = $verArr[0]; | |
| 77 | - $new_action_links .= '<a class="plugin-revision-action" href="#" data-parent_plugin="'.esc_attr( $plugin_file ).'" data-dir="'.esc_attr( $dir ).'">'.sprintf( esc_html__( 'Replace with version: %s','plugversions' ),$ver ).'</a> '; | |
| 78 | - } | |
| 79 | - } | |
| 80 | - if( '' !== $new_action_links ){ | |
| 81 | - $actions['versions'] = '<span class="plugin-revision-wrp"><a href="#">'.esc_html__( 'Revisions','plugin-revisioons' ).'</a><span class="plugin-revisions-vers">'.rtrim( $new_action_links,' ' ).'</span></span>'; | |
| 82 | - } | |
| 83 | - } | |
| 84 | - return $actions; | |
| 85 | - },10,4 ); | |
| 86 | - } | |
| 87 | - } | |
| 88 | - } | |
| 89 | - return $plugins; | |
| 90 | -} ); | |
| 91 | - | |
| 92 | 25 | add_action( 'admin_head',function(){ |
| 26 | + /** | |
| 27 | + * Add style to properly show the revisions on the page of plugins | |
| 28 | + * | |
| 29 | + * @since 0.0.1 | |
| 30 | + */ | |
| 93 | 31 | global $pagenow; |
| 94 | 32 | if( $pagenow && 'plugins.php' === sanitize_text_field( $pagenow ) ){ |
| 95 | 33 | ?> |
| 96 | 34 | <style id="plugin-revisions-css" type="text/css"> |
| @@ -108,9 +46,10 @@ | ||
| 108 | 46 | min-width:200px; |
| 109 | 47 | min-width:max-content; |
| 110 | 48 | background:#fff; |
| 111 | 49 | margin-top:15px; |
| 112 | - padding:10px 10px | |
| 50 | + padding:10px 10px; | |
| 51 | + z-index:9 | |
| 113 | 52 | } |
| 114 | 53 | .plugin-revision-wrp:hover .plugin-revisions-vers a{ |
| 115 | 54 | display:block; |
| 116 | 55 | border-bottom:1px dashed; |
| @@ -123,13 +62,42 @@ | ||
| 123 | 62 | </style> |
| 124 | 63 | <?php |
| 125 | 64 | } |
| 126 | 65 | } ); |
| 66 | + | |
| 127 | 67 | add_action( 'admin_footer',function(){ |
| 68 | + /** | |
| 69 | + * Add JS to restore a revision via Ajax | |
| 70 | + * This script is added to the plugins.php and plugin-editor.php pages. | |
| 71 | + * | |
| 72 | + * @since 0.0.1 | |
| 73 | + */ | |
| 128 | 74 | global $pagenow; |
| 129 | 75 | if( $pagenow && 'plugins.php' === sanitize_text_field( $pagenow ) ){ |
| 130 | 76 | wp_nonce_field( 'plugin_reviews_restore_version','plugin_reviews_restore_version' ); |
| 131 | 77 | ?> |
| 78 | + <style id="plugin-revisions-js-css" type="text/css"> | |
| 79 | + .plugin-revision-processing{position:relative;cursor:wait;} | |
| 80 | + .plugin-revision-processing:after{ | |
| 81 | + content:' '; | |
| 82 | + position:absolute; | |
| 83 | + top:50%; | |
| 84 | + left:50%; | |
| 85 | + transform:translate(-50%,-50%); | |
| 86 | + background-color:rgba(255,255,255,0.8); | |
| 87 | + border-radius:50%; | |
| 88 | + box-shadow:0 0 5px rgba(0,0,0,0.2); | |
| 89 | + z-index:9999; | |
| 90 | + background-size:16px 16px; | |
| 91 | + background-image:url(<?php echo esc_url( PLUGIN_REVISIONS_PLUGIN_URL ); ?>/admin/assets/images/ajax-loader.gif ); | |
| 92 | + background-position:center center; | |
| 93 | + background-repeat:no-repeat; | |
| 94 | + width:16px; | |
| 95 | + height:16px; | |
| 96 | + display:block; | |
| 97 | + opacity:1 | |
| 98 | + } | |
| 99 | + </style> | |
| 132 | 100 | <script id="plugin-revisions-js"> |
| 133 | 101 | function eos_plugin_revisions(){ |
| 134 | 102 | var as = document.getElementsByClassName('plugin-revision-action'),n=0,req = new XMLHttpRequest(),fd=new FormData(),nonce=document.getElementById('plugin_reviews_restore_version').value; |
| 135 | 103 | for(n;n<as.length;++n){ |
| @@ -134,8 +102,9 @@ | ||
| 134 | 102 | var as = document.getElementsByClassName('plugin-revision-action'),n=0,req = new XMLHttpRequest(),fd=new FormData(),nonce=document.getElementById('plugin_reviews_restore_version').value; |
| 135 | 103 | for(n;n<as.length;++n){ |
| 136 | 104 | as[n].addEventListener('click',function(e){ |
| 137 | 105 | e.preventDefault(); |
| 106 | + this.closest('td').classList.add('plugin-revision-processing'); | |
| 138 | 107 | this.style.backgroundPosition = 'center center'; |
| 139 | 108 | for(var k=0;k<as.length;++k){ |
| 140 | 109 | as[k].style.pointerEvents = 'none'; |
| 141 | 110 | } |
| @@ -181,28 +150,20 @@ | ||
| 181 | 150 | </script> |
| 182 | 151 | <?php |
| 183 | 152 | } |
| 184 | 153 | } ); |
| 185 | -//Return revision key | |
| 186 | -function eos_plugin_revision_key(){ | |
| 187 | - $opts = get_site_option( 'plugin_revisions' ); | |
| 188 | - if( $opts && isset( $opts['time' ] ) ){ | |
| 189 | - $key = substr( md5( sanitize_text_field( $opts['time' ] ) ),0,8 ); | |
| 190 | - return $key; | |
| 191 | - } | |
| 192 | - else{ | |
| 193 | - $time = time(); | |
| 194 | - $opts['time'] = $time; | |
| 195 | - update_site_option( 'plugin_revisions',$opts ); | |
| 196 | - return substr( md5( $time ),0,8 ); | |
| 197 | - } | |
| 198 | - return false; | |
| 199 | -} | |
| 200 | 154 | |
| 201 | -//Remove all plugin revisions | |
| 155 | +/** | |
| 156 | + * Remove all plugin revisions | |
| 157 | + * This function removes all revisions of a specific plugin or all plugins if no plugin name is provided. | |
| 158 | + * | |
| 159 | + * @since 0.0.1 | |
| 160 | + */ | |
| 202 | 161 | function eos_plugin_revisions_remove_versions( $N = false,$plugin_name = false ){ |
| 203 | 162 | $key = eos_plugin_revision_key(); |
| 204 | 163 | $all_dirs = eos_plugin_revisions_scandir( dirname( PLUGIN_REVISIONS_PLUGIN_DIR ) ); |
| 164 | + $prefix = 'pr-'.$key.'-'; | |
| 165 | + $suffix = '-ver-'.$plugin_name.'.zip'; | |
| 205 | 166 | global $wp_filesystem; |
| 206 | 167 | if( empty( $wp_filesystem ) ){ |
| 207 | 168 | require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 208 | 169 | WP_Filesystem(); |
| @@ -208,9 +169,9 @@ | ||
| 208 | 169 | WP_Filesystem(); |
| 209 | 170 | } |
| 210 | 171 | if( $all_dirs && !empty( $all_dirs ) ){ |
| 211 | 172 | foreach( $all_dirs as $all_dir ){ |
| 212 | - if( substr( $all_dir,-strlen( $plugin_name ),strlen( $plugin_name ) ) !== $plugin_name ){ | |
| 173 | + if( 0 !== strpos( $all_dir,$prefix ) || substr( $all_dir,-strlen( $suffix ) ) !== $suffix ){ | |
| 213 | 174 | unset( $all_dirs[array_search( $all_dir,$all_dirs )] ); |
| 214 | 175 | } |
| 215 | 176 | } |
| 216 | 177 | $n = 0; |
| @@ -224,9 +185,13 @@ | ||
| 224 | 185 | } |
| 225 | 186 | } |
| 226 | 187 | } |
| 227 | 188 | |
| 228 | -//Remove plugin revisions for all plugins | |
| 189 | +/** | |
| 190 | + * Remove all revisions of all the plugins | |
| 191 | + * | |
| 192 | + * @since 0.0.1 | |
| 193 | + */ | |
| 229 | 194 | function eos_plugin_revisions_remove_all_versions(){ |
| 230 | 195 | $key = eos_plugin_revision_key(); |
| 231 | 196 | $all_dirs = eos_plugin_revisions_scandir( dirname( PLUGIN_REVISIONS_PLUGIN_DIR ) ); |
| 232 | 197 | global $wp_filesystem; |
| @@ -242,8 +207,14 @@ | ||
| 242 | 207 | } |
| 243 | 208 | } |
| 244 | 209 | } |
| 245 | 210 | |
| 211 | +/** | |
| 212 | + * Revisions scandir | |
| 213 | + * This function scans the plugin revisions directory and returns an array of files sorted by modification time. | |
| 214 | + * | |
| 215 | + * @since 0.0.1 | |
| 216 | + */ | |
| 246 | 217 | function eos_plugin_revisions_scandir( $dir ) { |
| 247 | 218 | $ignored = array('.', '..', '.svn', '.htaccess'); |
| 248 | 219 | $files = array(); |
| 249 | 220 | foreach( scandir( $dir ) as $file ){ |
| @@ -254,17 +225,323 @@ | ||
| 254 | 225 | $files = array_keys( $files ); |
| 255 | 226 | return ( $files ) ? $files : false; |
| 256 | 227 | } |
| 257 | 228 | |
| 258 | -add_filter( 'site_transient_update_plugins',function( $obj ){ | |
| 259 | - if( isset( $obj ) && is_object( $obj ) && isset( $obj->response ) ){ | |
| 260 | - $response = $obj->response; | |
| 261 | - $key = eos_plugin_revision_key(); | |
| 262 | - foreach( $response as $p => $arr ){ | |
| 263 | - if( false !== strpos( $p,'pr-'.$key.'-' ) && isset( $response[$p] ) ){ | |
| 264 | - unset( $response[$p] ); | |
| 265 | - } | |
| 229 | +add_action( 'admin_init', 'eos_pv_restore_revision_links' ); | |
| 230 | +/** | |
| 231 | + * Add links to restore previous revisions from zipped plugins. | |
| 232 | + * This function initializes the restoring link functionality for plugin revisions. | |
| 233 | + * | |
| 234 | + * @since 0.0.6 | |
| 235 | + */ | |
| 236 | +function eos_pv_restore_revision_links() { | |
| 237 | + require_once PLUGIN_REVISIONS_PLUGIN_DIR . '/admin/classes/class-plugversions-restoring-link.php'; | |
| 238 | + $link = new PlugVersions_Restoring_Link(); | |
| 239 | +} | |
| 240 | + | |
| 241 | + | |
| 242 | +/** | |
| 243 | + * Backup the plugin before update. | |
| 244 | + * This filter allows you to catch all possible update scenarios, including bulk and manual replacements, as well as Ajax updates. | |
| 245 | + * | |
| 246 | + * @param string $source Plugin source path. | |
| 247 | + * @param string $remote_source Plugin remote source path. | |
| 248 | + * @param object $upgrader Upgrader object. | |
| 249 | + * @param array $hook_extra Hook extra data. | |
| 250 | + * @return string | |
| 251 | + * | |
| 252 | + * @author Vincenzo Casu. | |
| 253 | + */ | |
| 254 | +function eos_plugin_unversal_backup( $source, $remote_source, $upgrader, $hook_extra ) { | |
| 255 | + // Load get_plugin_data() if not exists. | |
| 256 | + if ( ! function_exists( 'get_plugin_data' ) ) { | |
| 257 | + require_once ABSPATH . 'wp-admin/includes/plugin.php'; | |
| 258 | + } | |
| 259 | + | |
| 260 | + // Get plugins to backup. | |
| 261 | + $plugins_to_backup = array(); | |
| 262 | + | |
| 263 | + if ( ! empty( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ) { | |
| 264 | + // Bulk update (hook_extra['plugins']). | |
| 265 | + $plugins_to_backup = $hook_extra['plugins']; | |
| 266 | + } elseif ( ! empty( $hook_extra['plugin'] ) ) { | |
| 267 | + // Single AJAX update (hook_extra['plugin']). | |
| 268 | + $plugins_to_backup = array( $hook_extra['plugin'] ); | |
| 269 | + } elseif ( ! empty( $hook_extra['type'] ) && $hook_extra['type'] === 'plugin' | |
| 270 | + && ! empty( $hook_extra['action'] ) | |
| 271 | + && in_array( $hook_extra['action'], array( 'update', 'install' ), true ) ) { | |
| 272 | + // Fallback “replace from ZIP” or other plugin-based cases. | |
| 273 | + | |
| 274 | + // Get slug from source. | |
| 275 | + $slug = basename( $source ); | |
| 276 | + $all = get_plugins( '/' . $slug ); | |
| 277 | + | |
| 278 | + if ( ! empty( $all ) ) { | |
| 279 | + $main_file = key( $all ); | |
| 280 | + $plugins_to_backup[] = $slug . '/' . $main_file; | |
| 281 | + } else { | |
| 282 | + // If no plugins found, return the source. | |
| 283 | + return $source; | |
| 284 | + } | |
| 285 | + } else { | |
| 286 | + // No plugins to backup. | |
| 287 | + return $source; | |
| 288 | + } | |
| 289 | + | |
| 290 | + // Preparing Filesystem & Upgrader. | |
| 291 | + if ( ! class_exists( 'WP_Upgrader' ) ) { | |
| 292 | + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; | |
| 293 | + } | |
| 294 | + | |
| 295 | + global $wp_filesystem; | |
| 296 | + if ( empty( $wp_filesystem ) ) { | |
| 297 | + require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 298 | + WP_Filesystem(); | |
| 299 | + } | |
| 300 | + | |
| 301 | + $key = eos_plugin_revision_key(); | |
| 302 | + if ( ! $key ) { | |
| 303 | + return $source; | |
| 304 | + } | |
| 305 | + foreach ( $plugins_to_backup as $plugin_file ) { | |
| 306 | + $plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ); | |
| 307 | + $full_path = WP_PLUGIN_DIR . '/' . $plugin_file; | |
| 308 | + if ( ! file_exists( $full_path ) ) { | |
| 309 | + continue; | |
| 310 | + } | |
| 311 | + | |
| 312 | + // Metadata & version. | |
| 313 | + $plugin_data = get_plugin_data( $full_path ); | |
| 314 | + $version = $plugin_data['Version']; | |
| 315 | + $slug = dirname( $plugin_file ); | |
| 316 | + $zip_dirname = 'pr-' . $key . '-' . sanitize_option( 'upload_path', $version ) . '-ver-' . $slug; | |
| 317 | + | |
| 318 | + // Base dir & Backup destination. | |
| 319 | + $base_dir = plugin_dir_path( $full_path ); | |
| 320 | + $backup_dir = trailingslashit( str_replace( $slug, $zip_dirname, $base_dir ) ); | |
| 321 | + // Make Backup dir and move plugin files into it. | |
| 322 | + if ( ! $wp_filesystem->is_dir( $backup_dir ) ) { | |
| 323 | + $wp_filesystem->mkdir( $backup_dir ); | |
| 324 | + } | |
| 325 | + if ( $wp_filesystem->is_dir( $backup_dir ) ) { | |
| 326 | + copy_dir( $plugin_dir, $backup_dir ); | |
| 327 | + $zip_file = $zip_dirname . '.zip'; | |
| 328 | + // Create a zip file of the plugin. | |
| 329 | + | |
| 330 | + if ( eos_pv_create_zip_pclzip( $backup_dir, $zip_file ) ) { | |
| 331 | + $wp_filesystem->delete( untrailingslashit( $backup_dir ), true ); | |
| 332 | + } | |
| 333 | + | |
| 334 | + // Remove old versions of the plugin. | |
| 335 | + eos_plugin_revisions_remove_versions( | |
| 336 | + apply_filters( 'max_plugin_revisions', 4 ), | |
| 337 | + $slug | |
| 338 | + ); | |
| 339 | + } | |
| 340 | + } | |
| 341 | + | |
| 342 | + return $source; | |
| 343 | +} | |
| 344 | +add_filter( 'upgrader_source_selection', 'eos_plugin_unversal_backup', 10, 4 ); | |
| 345 | + | |
| 346 | +/** | |
| 347 | + * Rollback WordPress core to a specific version. | |
| 348 | + * This function allows you to rollback the WordPress core to a specified version. | |
| 349 | + * It downloads the specified version, unzips it, and replaces the current core files, | |
| 350 | + * while preserving the wp-content directory and wp-config.php file. | |
| 351 | + * | |
| 352 | + * @param string $version The version of WordPress to rollback to. Default is '6.4.3'. | |
| 353 | + * @return bool|WP_Error Returns true on success or WP_Error on failure. | |
| 354 | + * | |
| 355 | + * @since 1.0.0 | |
| 356 | + */ | |
| 357 | +function eos_pv_rollback_wordpress_core($version) { | |
| 358 | + if ( ! current_user_can('update_core') ) { | |
| 359 | + wp_die( esc_html__( 'You are not allowed to update the core.', 'plugversions' ) ); | |
| 266 | 360 | } |
| 267 | - $obj->response = $response; | |
| 268 | - } | |
| 269 | - return $obj; | |
| 270 | -} ); | |
| 361 | + | |
| 362 | + require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 363 | + require_once ABSPATH . 'wp-admin/includes/misc.php'; | |
| 364 | + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; | |
| 365 | + require_once ABSPATH . 'wp-admin/includes/update.php'; | |
| 366 | + | |
| 367 | + WP_Filesystem(); | |
| 368 | + global $wp_filesystem; | |
| 369 | + | |
| 370 | + // Put site in maintenance mode | |
| 371 | + $maintenance_string = '<?php $upgrading = ' . time() . '; ?>'; | |
| 372 | + $wp_filesystem->put_contents(ABSPATH . '.maintenance', $maintenance_string, FS_CHMOD_FILE); | |
| 373 | + | |
| 374 | + // Download WordPress version zip | |
| 375 | + $zip_url = "https://wordpress.org/wordpress-{$version}.zip"; | |
| 376 | + $tmp_file = download_url($zip_url); | |
| 377 | + | |
| 378 | + if ( is_wp_error($tmp_file) ) { | |
| 379 | + $wp_filesystem->delete(ABSPATH . '.maintenance'); | |
| 380 | + return new WP_Error('download_failed', 'Failed to download WordPress version.'); | |
| 381 | + } | |
| 382 | + | |
| 383 | + // Unzip into temp folder | |
| 384 | + $unzip_dir = WP_TEMP_DIR . "wordpress-rollback-{$version}"; | |
| 385 | + $result = unzip_file($tmp_file, $unzip_dir); | |
| 386 | + unlink($tmp_file); // Remove zip file | |
| 387 | + | |
| 388 | + if ( is_wp_error($result) ) { | |
| 389 | + $wp_filesystem->delete(ABSPATH . '.maintenance'); | |
| 390 | + return $result; | |
| 391 | + } | |
| 392 | + | |
| 393 | + // Copy files from the unzipped wordpress/ directory | |
| 394 | + $source = trailingslashit($unzip_dir) . 'wordpress'; | |
| 395 | + $dest = ABSPATH; | |
| 396 | + | |
| 397 | + $items = $wp_filesystem->dirlist($source); | |
| 398 | + foreach ($items as $item => $details) { | |
| 399 | + if ($item === 'wp-content' || $item === 'wp-config.php') { | |
| 400 | + continue; // Skip user files | |
| 401 | + } | |
| 402 | + $source_path = trailingslashit($source) . $item; | |
| 403 | + $dest_path = trailingslashit($dest) . $item; | |
| 404 | + | |
| 405 | + // Remove existing file or folder | |
| 406 | + if ($wp_filesystem->exists($dest_path)) { | |
| 407 | + $wp_filesystem->delete($dest_path, true); | |
| 408 | + } | |
| 409 | + | |
| 410 | + // Copy new one | |
| 411 | + $wp_filesystem->copy($source_path, $dest_path, true, FS_CHMOD_FILE); | |
| 412 | + } | |
| 413 | + | |
| 414 | + // Cleanup | |
| 415 | + $wp_filesystem->delete($unzip_dir, true); | |
| 416 | + $wp_filesystem->delete(ABSPATH . '.maintenance'); | |
| 417 | + | |
| 418 | + return true; | |
| 419 | +} | |
| 420 | + | |
| 421 | +add_action('admin_init', function() { | |
| 422 | + /** | |
| 423 | + * Handle rollback request for WordPress core. | |
| 424 | + * This function checks if the user has permission to update the core, | |
| 425 | + * verifies the nonce, and processes the rollback request. | |
| 426 | + * | |
| 427 | + * @since 1.0.0 | |
| 428 | + */ | |
| 429 | + | |
| 430 | + if (isset($_GET['rollback_wp']) && current_user_can('update_core') && check_admin_referer('eos_pv_core_rollback_nonce')) { | |
| 431 | + $version = sanitize_text_field($_GET['rollback_wp']); | |
| 432 | + // Validate version format | |
| 433 | + // Example: 6.4.3 | |
| 434 | + // Ensure it matches the expected format of major.minor.patch | |
| 435 | + // This regex checks for three groups of digits separated by dots. | |
| 436 | + if (!preg_match('/^\d+\.\d+\.\d+$/', $version)) { | |
| 437 | + wp_die(esc_html__('Invalid version format.', 'plugversions')); | |
| 438 | + } | |
| 439 | + | |
| 440 | + // Perform the rollback | |
| 441 | + $result = eos_pv_rollback_wordpress_core( $version ); | |
| 442 | + if (is_wp_error($result)) { | |
| 443 | + echo 'Error: ' . $result->get_error_message(); | |
| 444 | + } else { | |
| 445 | + esc_html_e( 'Rollback completed. Please manually check your site.', 'plugversions' ); | |
| 446 | + } | |
| 447 | + exit; | |
| 448 | + } | |
| 449 | +}); | |
| 450 | + | |
| 451 | +add_action('upgrader_process_complete', function ($upgrader, $hook_extra) { | |
| 452 | + /** | |
| 453 | + * Save the current WordPress core version after an update. | |
| 454 | + * This function saves the current WordPress version to an option after a core update. | |
| 455 | + * It prevents duplicates and limits the saved versions to the last 3. | |
| 456 | + * | |
| 457 | + * @param WP_Upgrader $upgrader The upgrader instance. | |
| 458 | + * @param array $hook_extra Extra data passed to the hook. | |
| 459 | + * | |
| 460 | + * @since 1.0.0 | |
| 461 | + */ | |
| 462 | + | |
| 463 | + // Check if this is a core update | |
| 464 | + if ( ! is_a( $upgrader, 'Core_Upgrader' ) || ! isset( $hook_extra['type'], $hook_extra['action'] ) ) { | |
| 465 | + return; | |
| 466 | + } | |
| 467 | + | |
| 468 | + // Check if the action is an update | |
| 469 | + // and the type is core. | |
| 470 | + // This ensures that we only save versions after a core update. | |
| 471 | + if ( | |
| 472 | + isset($hook_extra['type'], $hook_extra['action']) && | |
| 473 | + $hook_extra['type'] === 'core' && | |
| 474 | + $hook_extra['action'] === 'update' | |
| 475 | + ) { | |
| 476 | + $current_version = get_bloginfo('version'); | |
| 477 | + $option_name = 'eos_pv_core_versions'; | |
| 478 | + | |
| 479 | + // Get existing saved versions | |
| 480 | + $saved_versions = get_option($option_name, []); | |
| 481 | + | |
| 482 | + // Prevent duplicates | |
| 483 | + if (!in_array($current_version, $saved_versions, true)) { | |
| 484 | + array_unshift($saved_versions, $current_version); // Add to beginning | |
| 485 | + } | |
| 486 | + | |
| 487 | + // Optional: limit to last 3 versions | |
| 488 | + $saved_versions = array_slice($saved_versions, 0, 3); | |
| 489 | + | |
| 490 | + update_option($option_name, $saved_versions); | |
| 491 | + } | |
| 492 | +}, 10, 2); | |
| 493 | + | |
| 494 | +add_action('admin_notices', function () { | |
| 495 | + return; // Disable admin notices for now, as the core rollback functionality is still not ready. | |
| 496 | + /** | |
| 497 | + * Display rollback notice on the update-core page. | |
| 498 | + * This function displays a notice with a dropdown to select a previous WordPress core version | |
| 499 | + * for rollback, if available. | |
| 500 | + * | |
| 501 | + * @since 1.0.0 | |
| 502 | + */ | |
| 503 | + | |
| 504 | + // Only show on the update-core screen | |
| 505 | + | |
| 506 | + if (get_current_screen()->id !== 'update-core') return; | |
| 507 | + | |
| 508 | + $versions = get_option('eos_pv_core_versions', []); // Get saved versions | |
| 509 | + // Ensure versions are unique and sorted | |
| 510 | + $versions = array_unique($versions); | |
| 511 | + sort($versions); | |
| 512 | + | |
| 513 | + // If no versions are available, do not display the notice | |
| 514 | + if (empty($versions)) return; | |
| 515 | + | |
| 516 | + ?> | |
| 517 | + <div class="notice notice-info"> | |
| 518 | + <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> | |
| 519 | + <h3><?php esc_html_e( 'Rollback WordPress Core', 'plugversions' ); ?></h3> | |
| 520 | + <p><?php esc_html_e( 'Select a previously stored version to roll back to:', 'plugversions' ); ?></p> | |
| 521 | + <select name="eos_pv_version_to_rollback"> | |
| 522 | + <?php foreach ($versions as $version): ?> | |
| 523 | + <option value="<?php echo esc_attr($version); ?>"> | |
| 524 | + <?php echo esc_html($version); ?> | |
| 525 | + </option> | |
| 526 | + <?php endforeach; ?> | |
| 527 | + </select> | |
| 528 | + <?php wp_nonce_field('eos_pv_rollback_wp_core', 'eos_pv_nonce'); ?> | |
| 529 | + <input type="hidden" name="action" value="eos_pv_rollback_core"> | |
| 530 | + <button type="submit" class="button button-primary"><?php esc_html_e( 'Rollback', 'plugversions' ); ?></button> | |
| 531 | + </form> | |
| 532 | + </div> | |
| 533 | + <?php | |
| 534 | +}); | |
| 535 | + | |
| 536 | +add_filter( 'plugin_action_links_plugversions/plugversions.php', function( $links ) { | |
| 537 | + /** | |
| 538 | + * Add plugin action link for Annual Protection Plan. | |
| 539 | + * | |
| 540 | + * @param array $links Array of plugin action links | |
| 541 | + * @return array Modified array of plugin action links | |
| 542 | + */ | |
| 543 | + $plan_link = '<a href="https://shop.josemortellaro.com/downloads/plugin-update-rescue-annual-protection-plan/" style="color: #d63638; font-weight: bold;">' . esc_html__( 'Protection Plan', 'plugversions' ) . '</a>'; | |
| 544 | + // Add to the beginning of the links array | |
| 545 | + array_unshift( $links, $plan_link ); | |
| 546 | + return $links; | |
| 547 | +} ); | |