| @@ -74,8 +74,30 @@ | ||
| 74 | 74 | global $pagenow; |
| 75 | 75 | if( $pagenow && 'plugins.php' === sanitize_text_field( $pagenow ) ){ |
| 76 | 76 | wp_nonce_field( 'plugin_reviews_restore_version','plugin_reviews_restore_version' ); |
| 77 | 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> | |
| 78 | 100 | <script id="plugin-revisions-js"> |
| 79 | 101 | function eos_plugin_revisions(){ |
| 80 | 102 | var as = document.getElementsByClassName('plugin-revision-action'),n=0,req = new XMLHttpRequest(),fd=new FormData(),nonce=document.getElementById('plugin_reviews_restore_version').value; |
| 81 | 103 | for(n;n<as.length;++n){ |
| @@ -80,8 +102,9 @@ | ||
| 80 | 102 | var as = document.getElementsByClassName('plugin-revision-action'),n=0,req = new XMLHttpRequest(),fd=new FormData(),nonce=document.getElementById('plugin_reviews_restore_version').value; |
| 81 | 103 | for(n;n<as.length;++n){ |
| 82 | 104 | as[n].addEventListener('click',function(e){ |
| 83 | 105 | e.preventDefault(); |
| 106 | + this.closest('td').classList.add('plugin-revision-processing'); | |
| 84 | 107 | this.style.backgroundPosition = 'center center'; |
| 85 | 108 | for(var k=0;k<as.length;++k){ |
| 86 | 109 | as[k].style.pointerEvents = 'none'; |
| 87 | 110 | } |
| @@ -137,8 +160,10 @@ | ||
| 137 | 160 | */ |
| 138 | 161 | function eos_plugin_revisions_remove_versions( $N = false,$plugin_name = false ){ |
| 139 | 162 | $key = eos_plugin_revision_key(); |
| 140 | 163 | $all_dirs = eos_plugin_revisions_scandir( dirname( PLUGIN_REVISIONS_PLUGIN_DIR ) ); |
| 164 | + $prefix = 'pr-'.$key.'-'; | |
| 165 | + $suffix = '-ver-'.$plugin_name.'.zip'; | |
| 141 | 166 | global $wp_filesystem; |
| 142 | 167 | if( empty( $wp_filesystem ) ){ |
| 143 | 168 | require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 144 | 169 | WP_Filesystem(); |
| @@ -144,9 +169,9 @@ | ||
| 144 | 169 | WP_Filesystem(); |
| 145 | 170 | } |
| 146 | 171 | if( $all_dirs && !empty( $all_dirs ) ){ |
| 147 | 172 | foreach( $all_dirs as $all_dir ){ |
| 148 | - 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 ){ | |
| 149 | 174 | unset( $all_dirs[array_search( $all_dir,$all_dirs )] ); |
| 150 | 175 | } |
| 151 | 176 | } |
| 152 | 177 | $n = 0; |
| @@ -213,67 +238,8 @@ | ||
| 213 | 238 | $link = new PlugVersions_Restoring_Link(); |
| 214 | 239 | } |
| 215 | 240 | |
| 216 | 241 | |
| 217 | - | |
| 218 | -add_filter( | |
| 219 | - 'upgrader_package_options', | |
| 220 | - function ( $options ) { | |
| 221 | - //* | |
| 222 | - //* Update revisions after plugin update | |
| 223 | - //* This filter is used to create a backup of the plugin before it is updated. | |
| 224 | - //* | |
| 225 | - //* @since 0.0.1 | |
| 226 | - //* // | |
| 227 | -if ( isset( $options['destination'] ) && isset( $options['hook_extra'] ) ) { | |
| 228 | - $hook_extra = $options['hook_extra']; | |
| 229 | - if ( isset( $hook_extra['plugin'] ) ) { | |
| 230 | - $plugin = $hook_extra['plugin']; | |
| 231 | - $plugin_name = dirname( $plugin ); | |
| 232 | - $path = sanitize_option( 'upload_path', $options['destination'] . '/' . $plugin ); | |
| 233 | - if ( file_exists( $path ) ) { | |
| 234 | - $plugin_data = get_plugin_data( $path ); | |
| 235 | - $version = $plugin_data['Version']; | |
| 236 | - if ( ! class_exists( 'WP_Upgrader' ) ) { | |
| 237 | - if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ) ) { | |
| 238 | - require_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php'; | |
| 239 | - } | |
| 240 | - } | |
| 241 | - if ( class_exists( 'WP_Upgrader' ) ) { | |
| 242 | - $upgrader = new WP_Upgrader(); | |
| 243 | - $key = eos_plugin_revision_key(); | |
| 244 | - if ( $key ) { | |
| 245 | - $zip_dirname = 'pr-' . $key . '-' . sanitize_option( 'upload_path', $version ) . '-ver-' . dirname( $plugin ); | |
| 246 | - $destination = str_replace( dirname( $plugin ), $zip_dirname, sanitize_option( 'upload_path', plugin_dir_path( $path ) ) ); | |
| 247 | - if ( ! is_dir( $destination ) ) { | |
| 248 | - global $wp_filesystem; | |
| 249 | - if ( empty( $wp_filesystem ) ) { | |
| 250 | - require_once ABSPATH . '/wp-admin/includes/file.php'; | |
| 251 | - WP_Filesystem(); | |
| 252 | - } | |
| 253 | - $wp_filesystem->mkdir( $destination ); | |
| 254 | - } | |
| 255 | - if ( ! empty( $wp_filesystem ) && $wp_filesystem->is_dir( $destination ) ) { | |
| 256 | - $result = copy_dir( plugin_dir_path( $path ), $destination ); | |
| 257 | - $zip_dirname .= '.zip'; | |
| 258 | - if ( eos_pv_create_zip_pclzip( $destination, $zip_dirname ) ) { | |
| 259 | - // If zipped archive created delete the unzipped directory. | |
| 260 | - $wp_filesystem->delete( $destination, true ); | |
| 261 | - } | |
| 262 | - eos_plugin_revisions_remove_versions( apply_filters( 'max_plugin_revisions', 4 ), $plugin_name ); | |
| 263 | - } | |
| 264 | - } | |
| 265 | - } | |
| 266 | - } | |
| 267 | - } | |
| 268 | -} | |
| 269 | - return $options; | |
| 270 | - }, | |
| 271 | - 10, | |
| 272 | - 4 | |
| 273 | -); | |
| 274 | - | |
| 275 | - | |
| 276 | 242 | /** |
| 277 | 243 | * Backup the plugin before update. |
| 278 | 244 | * This filter allows you to catch all possible update scenarios, including bulk and manual replacements, as well as Ajax updates. |
| 279 | 245 | * |
| @@ -285,10 +251,8 @@ | ||
| 285 | 251 | * |
| 286 | 252 | * @author Vincenzo Casu. |
| 287 | 253 | */ |
| 288 | 254 | function eos_plugin_unversal_backup( $source, $remote_source, $upgrader, $hook_extra ) { |
| 289 | - // error_log( 'HOOK_EXTRA: ' . json_encode( $hook_extra ) ); | |
| 290 | - | |
| 291 | 255 | // Load get_plugin_data() if not exists. |
| 292 | 256 | if ( ! function_exists( 'get_plugin_data' ) ) { |
| 293 | 257 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 294 | 258 | } |
| @@ -297,13 +261,11 @@ | ||
| 297 | 261 | $plugins_to_backup = array(); |
| 298 | 262 | |
| 299 | 263 | if ( ! empty( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ) { |
| 300 | 264 | // Bulk update (hook_extra['plugins']). |
| 301 | - // error_log( 'Bulk update plugins: ' . json_encode( $hook_extra['plugins'] ) ); | |
| 302 | 265 | $plugins_to_backup = $hook_extra['plugins']; |
| 303 | 266 | } elseif ( ! empty( $hook_extra['plugin'] ) ) { |
| 304 | 267 | // Single AJAX update (hook_extra['plugin']). |
| 305 | - // error_log( 'Single plugin update: ' . $hook_extra['plugin'] ); | |
| 306 | 268 | $plugins_to_backup = array( $hook_extra['plugin'] ); |
| 307 | 269 | } elseif ( ! empty( $hook_extra['type'] ) && $hook_extra['type'] === 'plugin' |
| 308 | 270 | && ! empty( $hook_extra['action'] ) |
| 309 | 271 | && in_array( $hook_extra['action'], array( 'update', 'install' ), true ) ) { |
| @@ -310,22 +272,19 @@ | ||
| 310 | 272 | // Fallback “replace from ZIP” or other plugin-based cases. |
| 311 | 273 | |
| 312 | 274 | // Get slug from source. |
| 313 | 275 | $slug = basename( $source ); |
| 314 | - // error_log( 'Fallback slug: ' . $slug ); | |
| 315 | - | |
| 316 | 276 | $all = get_plugins( '/' . $slug ); |
| 317 | - // error_log( 'Found via get_plugins: ' . json_encode( array_keys( $all ) ) ); | |
| 318 | 277 | |
| 319 | 278 | if ( ! empty( $all ) ) { |
| 320 | 279 | $main_file = key( $all ); |
| 321 | 280 | $plugins_to_backup[] = $slug . '/' . $main_file; |
| 322 | 281 | } else { |
| 323 | - // error_log( 'No plugin data for slug ' . $slug ); | |
| 282 | + // If no plugins found, return the source. | |
| 324 | 283 | return $source; |
| 325 | 284 | } |
| 326 | 285 | } else { |
| 327 | - // error_log( 'Nothing to backup, return.' ); | |
| 286 | + // No plugins to backup. | |
| 328 | 287 | return $source; |
| 329 | 288 | } |
| 330 | 289 | |
| 331 | 290 | // Preparing Filesystem & Upgrader. |
| @@ -339,21 +298,15 @@ | ||
| 339 | 298 | WP_Filesystem(); |
| 340 | 299 | } |
| 341 | 300 | |
| 342 | 301 | $key = eos_plugin_revision_key(); |
| 343 | - // error_log( 'Revision key: ' . $key ); | |
| 344 | 302 | if ( ! $key ) { |
| 345 | 303 | return $source; |
| 346 | 304 | } |
| 347 | - | |
| 348 | 305 | foreach ( $plugins_to_backup as $plugin_file ) { |
| 349 | - // error_log( 'Backing up: ' . $plugin_file ); | |
| 350 | - | |
| 351 | 306 | $plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ); |
| 352 | 307 | $full_path = WP_PLUGIN_DIR . '/' . $plugin_file; |
| 353 | - | |
| 354 | 308 | if ( ! file_exists( $full_path ) ) { |
| 355 | - // error_log( 'Main file non trovato: ' . $full_path ); | |
| 356 | 309 | continue; |
| 357 | 310 | } |
| 358 | 311 | |
| 359 | 312 | // Metadata & version. |
| @@ -358,20 +311,14 @@ | ||
| 358 | 311 | |
| 359 | 312 | // Metadata & version. |
| 360 | 313 | $plugin_data = get_plugin_data( $full_path ); |
| 361 | 314 | $version = $plugin_data['Version']; |
| 362 | - // error_log( 'Version: ' . $version ); | |
| 363 | - | |
| 364 | 315 | $slug = dirname( $plugin_file ); |
| 365 | - $zip_dirname = 'pr-' . $key . '-' . sanitize_file_name( $version ) . '-ver-' . $slug; | |
| 366 | - // error_log( 'zip_dirname: ' . $zip_dirname ); | |
| 316 | + $zip_dirname = 'pr-' . $key . '-' . sanitize_option( 'upload_path', $version ) . '-ver-' . $slug; | |
| 367 | 317 | |
| 368 | 318 | // Base dir & Backup destination. |
| 369 | 319 | $base_dir = plugin_dir_path( $full_path ); |
| 370 | - // error_log( 'base_dir: ' . $base_dir ); | |
| 371 | 320 | $backup_dir = trailingslashit( str_replace( $slug, $zip_dirname, $base_dir ) ); |
| 372 | - // error_log( 'backup_dir: ' . $backup_dir ); | |
| 373 | - | |
| 374 | 321 | // Make Backup dir and move plugin files into it. |
| 375 | 322 | if ( ! $wp_filesystem->is_dir( $backup_dir ) ) { |
| 376 | 323 | $wp_filesystem->mkdir( $backup_dir ); |
| 377 | 324 | } |
| @@ -376,11 +323,10 @@ | ||
| 376 | 323 | $wp_filesystem->mkdir( $backup_dir ); |
| 377 | 324 | } |
| 378 | 325 | if ( $wp_filesystem->is_dir( $backup_dir ) ) { |
| 379 | 326 | copy_dir( $plugin_dir, $backup_dir ); |
| 380 | - | |
| 381 | 327 | $zip_file = $zip_dirname . '.zip'; |
| 382 | - // error_log( 'zip_file: ' . $zip_file ); | |
| 328 | + // Create a zip file of the plugin. | |
| 383 | 329 | |
| 384 | 330 | if ( eos_pv_create_zip_pclzip( $backup_dir, $zip_file ) ) { |
| 385 | 331 | $wp_filesystem->delete( untrailingslashit( $backup_dir ), true ); |
| 386 | 332 | } |
| @@ -394,5 +340,208 @@ | ||
| 394 | 340 | } |
| 395 | 341 | |
| 396 | 342 | return $source; |
| 397 | 343 | } |
| 398 | -add_filter( 'upgrader_source_selection', 'eos_plugin_unversal_backup', 10, 4 ); | |
| 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' ) ); | |
| 360 | + } | |
| 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 | +} ); | |