Make WordPress Core

Changeset 12903


Ignore:
Timestamp:
01/29/2010 09:45:32 PM (15 years ago)
Author:
ryan
Message:

Integrate sitewide plugin handling into activate_plugins(), deactivate_plugins(), and plugins.php. fixes #11767 see #11644

Location:
trunk/wp-admin
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-admin/includes/ms.php

    r12902 r12903  
    743743
    744744/**
    745  * activate_sitewide_plugin()
    746  *
    747  * Activates a plugin site wide (for all blogs on an installation)
     745 * @deprecated
    748746 */
    749747function activate_sitewide_plugin() {
    750     if ( !isset( $_GET['sitewide'] ) )
    751         return false;
    752 
    753     /* Add the plugin to the list of sitewide active plugins */
    754     $active_sitewide_plugins = maybe_unserialize( get_site_option( 'active_sitewide_plugins' ) );
    755 
    756     /* Add the activated plugin to the list */
    757     $active_sitewide_plugins[ $_GET['plugin'] ] = time();
    758 
    759     /* Write the updated option to the DB */
    760     if ( !update_site_option( 'active_sitewide_plugins', $active_sitewide_plugins ) )
    761         return false;
    762 
    763     return true;
    764 }
    765 
    766 // @todo Throws warning if plugin is not set.  Kinda janky.
    767 //add_action( 'activate_' . $_GET['plugin'], 'activate_sitewide_plugin' );
     748    return false;
     749}
    768750
    769751/**
    770  * deactivate_sitewide_plugin()
    771  *
    772  * Deactivates a plugin site wide (for all blogs on an installation)
     752 * @deprecated
    773753 */
    774754function deactivate_sitewide_plugin( $plugin = false ) {
    775     if ( !$plugin )
    776         $plugin = $_GET['plugin'];
    777 
    778     /* Get the active sitewide plugins */
    779     $active_sitewide_plugins = (array) maybe_unserialize( get_site_option( 'active_sitewide_plugins' ) );
    780 
    781     /* Remove the plugin we are deactivating from the list of active sitewide plugins */
    782     foreach ( $active_sitewide_plugins as $plugin_file => $activation_time ) {
    783         if ( $plugin == $plugin_file )
    784             unset( $active_sitewide_plugins[ $plugin_file ] );
    785     }
    786 
    787     if ( !update_site_option( 'active_sitewide_plugins', $active_sitewide_plugins ) )
    788         wp_redirect( 'plugins.php?error=true' );
    789 
    790     return true;
    791 }
    792 // @todo Throws warning if plugin is not set.  Kinda janky.
    793 //add_action( 'deactivate_' . $_GET['plugin'], 'deactivate_sitewide_plugin' );
    794 add_action( 'deactivate_invalid_plugin', 'deactivate_sitewide_plugin' );
     755    return;
     756}
    795757
    796758/**
    797  * add_sitewide_activate_row()
    798  *
    799  * Adds the "Activate plugin site wide" row for each plugin in the inactive plugins list.
    800  */
    801 function add_sitewide_activate_row( $file, $plugin_data, $context ) {
    802     if ( !is_super_admin() )
    803         return false;
    804 
    805     if ( 'sitewide-active' == $context )
    806         return false;
    807 
    808     if ( is_plugin_active( $file ) )
    809         return false;
    810 
    811     echo '<tr><td colspan="5" style="background: #f5f5f5; text-align: right;">';
    812 
    813     echo '<a href="' . wp_nonce_url( admin_url( 'plugins.php?action=activate&amp;sitewide=1&amp;plugin=' . $file ), 'activate-plugin_' . $file ) . '" title="' . __( 'Activate this plugin for all blogs across the entire network' ) . '">&uarr; ' . sprintf( __( 'Activate %s Site Wide' ), strip_tags( $plugin_data["Title"] ) ) . '</a>';
    814     echo '</td></tr>';
    815 }
    816 add_action( 'after_plugin_row', 'add_sitewide_activate_row', 9, 3 );
    817 
    818 /**
    819  * is_wpmu_sitewide_plugin()
    820  *
    821  * Checks for "Site Wide Only: true" in the plugin header to see if this should
    822  * be activated as a site wide MU plugin.
     759 * @deprecated is_network_only_plugin()
    823760 */
    824761function is_wpmu_sitewide_plugin( $file ) {
    825     /* Open the plugin file for reading to check if this is a ms-plugin. */
    826     $fp = @fopen( WP_PLUGIN_DIR . '/' . $file, 'r' );
    827 
    828     /* Pull only the first 8kiB of the file in. */
    829     $plugin_data = @fread( $fp, 8192 );
    830 
    831     /* PHP will close file handle, but we are good citizens. */
    832     @fclose($fp);
    833 
    834     if ( preg_match( '|Site Wide Only:(.*)true$|mi', $plugin_data ) )
    835         return true;
    836 
    837     return false;
    838 }
    839 
     762    return is_network_only_plugin( $file );
     763}
    840764
    841765/**
    842  * list_activate_sitewide_plugins()
    843  *
    844  * Lists all the plugins that have been activated site wide.
    845  */
    846 function list_activate_sitewide_plugins() {
    847     $all_plugins = get_plugins();
    848 
    849     if ( !is_super_admin() )
    850         return false;
    851 
    852     $active_sitewide_plugins = maybe_unserialize( get_site_option( 'active_sitewide_plugins') );
    853     $context = 'sitewide-active';
    854 
    855     if ( $active_sitewide_plugins ) {
    856 ?>
    857         <h3><?php _e( 'Currently Active Site Wide Plugins' ) ?></h3>
    858 
    859         <p><?php _e( 'Plugins that appear in the list below are activate for all blogs across this installation.' ) ?></p>
    860 
    861         <table class="widefat" cellspacing="0" id="<?php echo $context ?>-plugins-table">
    862             <thead>
    863                 <tr>
    864                     <th scope="col" class="manage-column check-column">&nbsp;</th>
    865                     <th scope="col" class="manage-column"><?php _e('Plugin'); ?></th>
    866                     <th scope="col" class="manage-column"><?php _e('Description'); ?></th>
    867                 </tr>
    868             </thead>
    869 
    870             <tfoot>
    871                 <tr>
    872                     <th scope="col" class="manage-column check-column">&nbsp;</th>
    873                     <th scope="col" class="manage-column"><?php _e('Plugin'); ?></th>
    874                     <th scope="col" class="manage-column"><?php _e('Description'); ?></th>
    875                 </tr>
    876             </tfoot>
    877 
    878             <tbody class="plugins">
    879         <?php
    880             foreach ( (array) $active_sitewide_plugins as $plugin_file => $activated_time ) {
    881                 $action_links = array();
    882                 $action_links[] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&amp;sitewide=1&amp;plugin=' . $plugin_file, 'deactivate-plugin_' . $plugin_file ) . '" title="' . __('Deactivate this plugin site wide') . '">' . __('Deactivate') . '</a>';
    883 
    884                 if ( current_user_can('edit_plugins') && is_writable(WP_PLUGIN_DIR . '/' . $plugin_file) )
    885                     $action_links[] = '<a href="plugin-editor.php?file=' . $plugin_file . '" title="' . __('Open this file in the Plugin Editor') . '" class="edit">' . __('Edit') . '</a>';
    886 
    887                 $action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, $plugin_data, $context );
    888                 $action_links = apply_filters( "plugin_action_links_$plugin_file", $action_links, $plugin_file, $plugin_data, $context );
    889 
    890                 $plugin_data = $all_plugins[$plugin_file];
    891 
    892                 echo "
    893             <tr class='$context' style='background: #eef2ff;'>
    894                 <th scope='row' class='check-column'>&nbsp;</th>
    895                 <td class='plugin-title'><strong>{$plugin_data['Name']}</strong></td>
    896                 <td class='desc'><p>{$plugin_data['Description']}</p></td>
    897             </tr>
    898             <tr class='$context second' style='background: #eef2ff;'>
    899                 <td></td>
    900                 <td class='plugin-title'>";
    901                 echo '<div class="row-actions-visible">';
    902                 foreach ( $action_links as $action => $link ) {
    903                     $sep = end($action_links) == $link ? '' : ' | ';
    904                     echo "<span class='$action'>$link$sep</span>";
    905                 }
    906                 echo "</div></td>
    907                 <td class='desc'>";
    908                 $plugin_meta = array();
    909                 if ( !empty($plugin_data['Version']) )
    910                     $plugin_meta[] = sprintf(__('Version %s'), $plugin_data['Version']);
    911                 if ( !empty($plugin_data['Author']) ) {
    912                     $author = $plugin_data['Author'];
    913                     if ( !empty($plugin_data['AuthorURI']) )
    914                         $author = '<a href="' . $plugin_data['AuthorURI'] . '" title="' . __( 'Visit author homepage' ) . '">' . $plugin_data['Author'] . '</a>';
    915                     $plugin_meta[] = sprintf( __('By %s'), $author );
    916                 }
    917                 if ( ! empty($plugin_data['PluginURI']) )
    918                     $plugin_meta[] = '<a href="' . $plugin_data['PluginURI'] . '" title="' . __( 'Visit plugin site' ) . '">' . __('Visit plugin site') . '</a>';
    919 
    920                 $plugin_meta = apply_filters('plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $context);
    921                 echo implode(' | ', $plugin_meta);
    922                 echo "</td>
    923             </tr>\n";
    924 
    925                 do_action( 'after_plugin_row', $plugin_file, $plugin_data, $context );
    926                 do_action( "after_plugin_row_$plugin_file", $plugin_file, $plugin_data, $context );
    927             }
    928         ?>
    929             </tbody>
    930         </table>
    931 
    932         <p><?php _e( 'Plugins that are enabled site wide can only be disabled by a site administrator.' ) ?></p>
    933 
    934 <?php
    935     }
    936 }
    937 add_action( 'pre_current_active_plugins', 'list_activate_sitewide_plugins' );
    938 
    939 /**
    940  * sitewide_filter_inactive_plugins_list()
    941  *
    942  * Filters the inactive plugins list so that it doesn't include plugins that have
    943  * been activated site wide, and not for the specific blog.
    944  */
    945 function sitewide_filter_inactive_plugins_list( $inactive_plugins ) {
    946     $active_sitewide_plugins = (array) maybe_unserialize( get_site_option('active_sitewide_plugins') );
    947 
    948     foreach ( $active_sitewide_plugins as $sitewide_plugin => $activated_time ) {
    949         unset( $inactive_plugins[ $sitewide_plugin ] );
    950     }
    951 
    952     /* Now unset any sitewide only plugins if the user is not a site admin */
    953     if ( !is_super_admin() ) {
    954         foreach ( $inactive_plugins as $plugin_name => $activated_time ) {
    955             if ( is_wpmu_sitewide_plugin( $plugin_name ) )
    956                 unset( $inactive_plugins[ $plugin_name ] );
    957         }
    958     }
    959 
    960     return $inactive_plugins;
    961 }
    962 add_filter( 'all_plugins', 'sitewide_filter_inactive_plugins_list' );
    963 
    964 /**
    965  * sitewide_filter_active_plugins_list()
    966  *
    967  * Filters the active plugins list so that it doesn't include plugins that have
    968  * been activated site wide instead of the specific blog.
    969  */
    970 function sitewide_filter_active_plugins_list( $active_plugins ) {
    971     $active_sitewide_plugins = (array) maybe_unserialize( get_site_option('active_sitewide_plugins') );
    972 
    973     foreach ( $active_sitewide_plugins as $sitewide_plugin => $activated_time ) {
    974         unset( $active_plugins[ $sitewide_plugin ] );
    975     }
    976 
    977     return $active_plugins;
    978 }
    979 add_filter( 'all_plugins', 'sitewide_filter_active_plugins_list' );
    980 
    981 /**
    982  * check_is_wpmu_plugin_on_activate()
    983  *
    984  * When a plugin is activated, this will check if it should be activated site wide
    985  * only.
     766 * @deprecated
    986767 */
    987768function check_is_wpmu_plugin_on_activate() {
    988     /***
    989      * On plugin activation on a blog level, check to see if this is actually a
    990      * site wide MU plugin. If so, deactivate and activate it site wide.
    991      */
    992     if ( is_wpmu_sitewide_plugin( $_GET['plugin'] ) || isset( $_GET['sitewide'] ) ) {
    993         deactivate_plugins( $_GET['plugin'], true );
    994 
    995         /* Silently activate because the activate_* hook has already run. */
    996         if ( is_super_admin() ) {
    997             $_GET['sitewide'] = true;
    998             activate_sitewide_plugin( $_GET['plugin'], true );
    999         }
    1000     }
    1001 }
    1002 // @todo Throws warning if plugin is not set.  Kinda janky.
    1003 //add_action( 'activate_' . $_GET['plugin'], 'check_is_wpmu_plugin_on_activate' );
     769    return;
     770}
    1004771
    1005772/**
    1006  * check_wpmu_plugins_on_bulk_activate()
     773 * @deprecated
    1007774 */
    1008775function check_wpmu_plugins_on_bulk_activate( $plugins ) {
    1009     if ( $plugins ) {
    1010         foreach ( $plugins as $plugin ) {
    1011             if ( is_wpmu_sitewide_plugin( $plugin ) ) {
    1012                 deactivate_plugins( $plugin );
    1013 
    1014                 if ( is_super_admin() )
    1015                     activate_sitewide_plugin( $plugin );
    1016             }
    1017         }
    1018     }
     776    return;
    1019777}
    1020778
  • trunk/wp-admin/includes/plugin.php

    r12891 r12903  
    264264
    265265/**
     266 * Check whether the plugin is active for the entire network.
     267 *
     268 * @since 3.0.0
     269 *
     270 * @param string $plugin Base plugin path from plugins directory.
     271 * @return bool True, if active for the network, otherwise false.
     272 */
     273function is_plugin_active_for_network( $plugin ){
     274    if ( !is_multisite() )
     275        return false;
     276
     277    $plugins = get_site_option( 'active_sitewide_plugins');
     278    if ( isset($plugins[$plugin]) )
     279        return true;
     280
     281    return false;
     282}
     283
     284/**
     285 * Checks for "Site Wide Only: true" in the plugin header to see if this should
     286 * be activated as a network wide MU plugin.
     287 *
     288 * @since 3.0.0
     289 *
     290 * @todo Use API for getting arbitrary plugin headers.
     291 *
     292 * @param $file Plugin to check
     293 * $return bool True if plugin is network only, false otherwise.
     294 */
     295function is_network_only_plugin( $file ) {
     296    /* Open the plugin file for reading to check if this is a ms-plugin. */
     297    $fp = @fopen( WP_PLUGIN_DIR . '/' . $file, 'r' );
     298
     299    /* Pull only the first 8kiB of the file in. */
     300    $plugin_data = @fread( $fp, 8192 );
     301
     302    /* PHP will close file handle, but we are good citizens. */
     303    @fclose($fp);
     304
     305    if ( preg_match( '|Site Wide Only:(.*)true$|mi', $plugin_data ) )
     306        return true;
     307
     308    return false;
     309}
     310
     311/**
    266312 * Attempts activation of plugin in a "sandbox" and redirects on success.
    267313 *
     
    285331 * @param string $plugin Plugin path to main plugin file with plugin data.
    286332 * @param string $redirect Optional. URL to redirect to.
     333 * @param bool $network_wide Whether to enable the plugin for all sites in the network or just the current site.  Multisite only. Default is false.
    287334 * @return WP_Error|null WP_Error on invalid file or null on success.
    288335 */
    289 function activate_plugin( $plugin, $redirect = '' ) {
    290     $current = get_option( 'active_plugins', array() );
     336function activate_plugin( $plugin, $redirect = '', $network_wide = false) {
    291337    $plugin  = plugin_basename( trim( $plugin ) );
     338
     339    if ( is_multisite() && ( $network_wide || is_network_only_plugin($plugin) ) ) {
     340        $network_wide = true;
     341        $current = get_site_option( 'active_sitewide_plugins', array() );
     342    } else {
     343        $current = get_option( 'active_plugins', array() );
     344    }
    292345
    293346    $valid = validate_plugin($plugin);
     
    300353        ob_start();
    301354        @include(WP_PLUGIN_DIR . '/' . $plugin);
    302         $current[] = $plugin;
    303         sort($current);
    304355        do_action( 'activate_plugin', trim( $plugin) );
    305         update_option('active_plugins', $current);
     356        if ( $network_wide ) {
     357            $current[$plugin] = time();
     358            update_site_option( 'active_sitewide_plugins', $current );
     359        } else {
     360            $current[] = $plugin;
     361            sort($current);
     362            update_option('active_plugins', $current);
     363        }
    306364        do_action( 'activate_' . trim( $plugin ) );
    307365        do_action( 'activated_plugin', trim( $plugin) );
     
    324382 */
    325383function deactivate_plugins( $plugins, $silent = false ) {
     384    $network_current = get_site_option( 'active_sitewide_plugins', array() );
    326385    $current = get_option( 'active_plugins', array() );
     386    $do_blog = $do_network = false;
    327387
    328388    foreach ( (array) $plugins as $plugin ) {
     
    333393            do_action( 'deactivate_plugin', trim( $plugin ) );
    334394
    335         $key = array_search( $plugin, (array) $current );
    336 
    337         if ( false !== $key )
    338             array_splice( $current, $key, 1 );
     395        if ( is_plugin_active_for_network($plugin) ) {
     396            // Deactivate network wide
     397            $do_network = true;
     398            unset($network_current[$plugin]);
     399        } else {
     400            // Deactivate for this blog only
     401            $do_blog = true;
     402            $key = array_search( $plugin, (array) $current );
     403
     404            if ( false !== $key )
     405                array_splice( $current, $key, 1 );
     406        }
    339407
    340408        //Used by Plugin updater to internally deactivate plugin, however, not to notify plugins of the fact to prevent plugin output.
     
    345413    }
    346414
    347     update_option('active_plugins', $current);
     415    if ( $do_blog )
     416        update_option('active_plugins', $current);
     417    if ( $do_network )
     418        update_site_option( 'active_sitewide_plugins', $network_current );
    348419}
    349420
  • trunk/wp-admin/plugins.php

    r12848 r12903  
    2828    $default_status = 'all';
    2929$status = isset($_REQUEST['plugin_status']) ? $_REQUEST['plugin_status'] : $default_status;
    30 if ( !in_array($status, array('all', 'active', 'inactive', 'recent', 'upgrade', 'search')) )
     30if ( !in_array($status, array('all', 'active', 'inactive', 'recent', 'upgrade', 'network', 'search')) )
    3131    $status = 'all';
    3232if ( $status != $default_status && 'search' != $status )
     
    3939
    4040if ( !empty($action) ) {
     41    $network_wide = false;
     42    if ( isset($_GET['networkwide']) && is_multisite() && is_super_admin() )
     43        $network_wide = true;
     44
    4145    switch ( $action ) {
    4246        case 'activate':
     
    4650            check_admin_referer('activate-plugin_' . $plugin);
    4751
    48             $result = activate_plugin($plugin, 'plugins.php?error=true&plugin=' . $plugin);
     52            $result = activate_plugin($plugin, 'plugins.php?error=true&plugin=' . $plugin, $network_wide);
    4953            if ( is_wp_error( $result ) )
    5054                wp_die($result);
     
    7276            }
    7377
    74             activate_plugins($plugins, 'plugins.php?error=true');
     78            activate_plugins($plugins, 'plugins.php?error=true', $network_wide);
    7579
    7680            $recent = (array)get_option('recently_activated');
     
    341345$recently_activated = get_option('recently_activated', array());
    342346$upgrade_plugins = array();
     347$network_plugins = array();
    343348
    344349set_transient( 'plugin_slugs', array_keys($all_plugins), 86400 );
     
    354359foreach ( (array)$all_plugins as $plugin_file => $plugin_data) {
    355360
    356     //Translate, Apply Markup, Sanitize HTML
     361    // Translate, Apply Markup, Sanitize HTML
    357362    $plugin_data = _get_plugin_data_markup_translate($plugin_file, $plugin_data, false, true);
    358363    $all_plugins[ $plugin_file ] = $plugin_data;
    359364
    360     //Filter into individual sections
    361     if ( is_plugin_active($plugin_file) ) {
     365    // Filter into individual sections
     366    if ( is_plugin_active_for_network($plugin_file) && is_super_admin() ) {
     367        $network_plugins[ $plugin_file ] = $plugin_data;
     368    } elseif ( is_plugin_active($plugin_file) ) {
    362369        $active_plugins[ $plugin_file ] = $plugin_data;
    363370    } else {
     
    379386$total_recent_plugins = count($recent_plugins);
    380387$total_upgrade_plugins = count($upgrade_plugins);
     388$total_network_plugins = count($network_plugins);
    381389
    382390//Searching.
     
    409417$plugins = &$$plugin_array_name;
    410418
    411 //Paging.
     419// Paging.
    412420$total_this_page = "total_{$status}_plugins";
    413421$total_this_page = $$total_this_page;
     
    471479        $actions = array();
    472480        $is_active = is_plugin_active($plugin_file);
    473 
    474         if ( $is_active )
    475             $actions[] = '<a href="' . wp_nonce_url('plugins.php?action=deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page, 'deactivate-plugin_' . $plugin_file) . '" title="' . __('Deactivate this plugin') . '">' . __('Deactivate') . '</a>';
    476         else
     481        $is_active_for_network = is_plugin_active_for_network($plugin_file);
     482
     483        if ( $is_active_for_network && !is_super_admin() )
     484            continue;
     485
     486        if ( $is_active ) {
     487            if ( $is_active_for_network ) {
     488                if ( is_super_admin() )
     489                    $actions[] = '<a href="' . wp_nonce_url('plugins.php?action=deactivate&amp;networkwide=1&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page, 'deactivate-plugin_' . $plugin_file) . '" title="' . __('Deactivate this plugin') . '">' . __('Network Deactivate') . '</a>';
     490            } else {
     491                $actions[] = '<a href="' . wp_nonce_url('plugins.php?action=deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page, 'deactivate-plugin_' . $plugin_file) . '" title="' . __('Deactivate this plugin') . '">' . __('Deactivate') . '</a>';
     492            }
     493        } else {
    477494            $actions[] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page, 'activate-plugin_' . $plugin_file) . '" title="' . __('Activate this plugin') . '" class="edit">' . __('Activate') . '</a>';
    478 
    479         if ( current_user_can('edit_plugins') && is_writable(WP_PLUGIN_DIR . '/' . $plugin_file) )
     495            if ( is_multisite() && is_super_admin() )
     496                $actions[] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;networkwide=1&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page, 'activate-plugin_' . $plugin_file) . '" title="' . __('Activate this plugin for all sites in this network') . '" class="edit">' . __('Network Activate') . '</a>';
     497        }
     498
     499        if ( !is_multisite() && current_user_can('edit_plugins') && is_writable(WP_PLUGIN_DIR . '/' . $plugin_file) )
    480500            $actions[] = '<a href="plugin-editor.php?file=' . $plugin_file . '" title="' . __('Open this file in the Plugin Editor') . '" class="edit">' . __('Edit') . '</a>';
    481501
     
    593613    $status_links[] = "<li><a href='plugins.php?plugin_status=inactive' $class>" . sprintf( _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $total_inactive_plugins ), number_format_i18n( $total_inactive_plugins ) ) . '</a>';
    594614}
     615if ( ! empty($network_plugins) ) {
     616    $class = ( 'network' == $status ) ? ' class="current"' : '';
     617    $status_links[] = "<li><a href='plugins.php?plugin_status=network' $class>" . sprintf( _n( 'Network <span class="count">(%s)</span>', 'Network <span class="count">(%s)</span>', $total_network_plugins ), number_format_i18n( $total_network_plugins ) ) . '</a>';
     618}
    595619if ( ! empty($upgrade_plugins) ) {
    596620    $class = ( 'upgrade' == $status ) ? ' class="current"' : '';
Note: See TracChangeset for help on using the changeset viewer.