Changeset 2242


Ignore:
Timestamp:
Mar 2, 2008, 6:53:23 PM (16 years ago)
Author:
patdenice
Message:

Enhance plugins administtration

Location:
trunk
Files:
7 added
1 deleted
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/admin/include/functions_plugins.inc.php

    r2135 r2242  
    108108}
    109109
     110/**
     111 * Sort plugins by status
     112 */
     113function sort_plugins_by_state($plugins, $db_plugins_by_id)
     114{
     115  $active_plugins = array();
     116  $inactive_plugins = array();
     117  $not_installed = array();
     118
     119  foreach($plugins as $plugin_id => $plugin)
     120  {
     121    if (isset($db_plugins_by_id[$plugin_id]))
     122    {
     123      $db_plugins_by_id[$plugin_id]['state'] == 'active' ?
     124        $active_plugins[$plugin_id] = $plugin : $inactive_plugins[$plugin_id] = $plugin;
     125    }
     126    else
     127    {
     128      $not_installed[$plugin_id] = $plugin;
     129    }
     130  }
     131  return $active_plugins + $inactive_plugins + $not_installed;
     132}
     133
     134
     135/**
     136 * Retrieve PEM server datas
     137 * @param bool (true for retrieve new extensions)
     138 */
     139function check_server_plugins($newext=false)
     140{
     141  global $fs_plugins;
     142 
     143  foreach($fs_plugins as $plugin_id => $fs_plugin)
     144  {
     145    if (!empty($fs_plugin['uri']) and strpos($fs_plugin['uri'] , 'extension_view.php?eid='))
     146    {
     147      list( , $extension) = explode('extension_view.php?eid=', $fs_plugin['uri']);
     148      if (!is_numeric($extension)) continue;
     149      $plugins_to_check[] = $extension;
     150      $fs_plugins[$plugin_id]['extension'] = $extension;
     151    }
     152  }
     153 
     154  $url = PEM_URL . '/uptodate.php?version=' . rawurlencode(PHPWG_VERSION) . '&extensions=' . implode(',', $plugins_to_check);
     155  $url .= $newext ? '&newext=Plugin' : '';
     156
     157  if (!empty($plugins_to_check) and $source = @file_get_contents($url))
     158  {
     159    return @unserialize($source);
     160  }
     161  return false;
     162}
     163
     164
     165/**
     166 * Extract plugin files from archive
     167 * @param string - install or upgrade
     168 *  @param string - archive URL
     169  * @param string - destination path
     170 */
     171function extract_plugin_files($action, $source, $dest)
     172{
     173  global $archive;
     174  if ($archive = tempnam( PHPWG_PLUGINS_PATH, 'zip'))
     175  {
     176    if (@copy(PEM_URL . str_replace(' ', '%20', $source), $archive))
     177    {
     178      $zip = new PclZip($archive);
     179      if ($list = $zip->listContent())
     180      {
     181        foreach ($list as $file)
     182        {
     183          // we search main.inc.php in archive
     184          if (basename($file['filename']) == 'main.inc.php'
     185            and (!isset($main_filepath) or strlen($file['filename']) < strlen($main_filepath)))
     186          {
     187            $main_filepath = $file['filename'];
     188          }
     189        }
     190     
     191        if (isset($main_filepath))
     192        {
     193          $root = dirname($main_filepath); // main.inc.php path in archive
     194          if ($action == 'upgrade') $extract_path = PHPWG_PLUGINS_PATH . $dest;
     195          else $extract_path = PHPWG_PLUGINS_PATH . ($root == '.' ? 'extension_' . $dest : basename($root));
     196       
     197          if($result = $zip->extract(PCLZIP_OPT_PATH, $extract_path,
     198                                     PCLZIP_OPT_REMOVE_PATH, $root,
     199                                     PCLZIP_OPT_REPLACE_NEWER))
     200          {
     201            foreach ($result as $file)
     202            {
     203              if ($file['stored_filename'] == $main_filepath)
     204              {
     205                $status = $file['status'];
     206                break;
     207              }
     208            }
     209          }
     210          else $status = 'extract_error';
     211        }
     212        else $status = 'archive_error';
     213      }
     214      else $status = 'archive_error';
     215    }
     216    else $status = 'dl_archive_error';
     217  }
     218  else $status = 'temp_path_error';
     219 
     220  @unlink($archive);
     221  return $status;
     222}
     223
     224
     225/**
     226 * delete $path directory
     227 * @param string - path
     228 */
     229function pm_deltree($path)
     230{
     231  if (is_dir($path))
     232  {
     233    $fh = opendir($path);
     234    while ($file = readdir($fh))
     235    {
     236      if ($file != '.' and $file != '..')
     237      {
     238        $pathfile = $path . '/' . $file;
     239        if (is_dir($pathfile)) pm_deltree($pathfile);
     240        else @unlink($pathfile);
     241      }
     242    }
     243    closedir($fh);
     244    return @rmdir($path);
     245  }
     246}
     247
     248
     249/**
     250 * send $path to trash directory
     251  * @param string - path
     252 */
     253function send_pm_trash($path)
     254{
     255  $trash_path = PHPWG_PLUGINS_PATH . 'trash';
     256  if (!is_dir($trash_path))
     257  {
     258    @mkdir($trash_path);
     259    $file = @fopen($trash_path . '/.htaccess', 'w');
     260    @fwrite($file, 'deny from all');
     261    @fclose($file);
     262  }
     263  while ($r = $trash_path . '/' . md5(uniqid(rand(), true)))
     264  {
     265    if (!is_dir($r))
     266    {
     267      @rename($path, $r);
     268      break;
     269    }
     270  }
     271}
     272
     273
     274/**
     275 * Sort functions
     276 */
     277function extension_name_compare($a, $b)
     278{
     279  return strcmp(strtolower($a['ext_name']), strtolower($b['ext_name']));
     280}
     281function extension_author_compare($a, $b)
     282{
     283  $r = strcmp(strtolower($a['author']), strtolower($b['author']));
     284  if ($r == 0) return extension_name_compare($a, $b);
     285  else return $r;
     286}
     287
    110288?>
  • trunk/admin/plugins.php

    r2216 r2242  
    2929}
    3030
    31 include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
     31include_once(PHPWG_ROOT_PATH.'admin/include/tabsheet.class.php');
    3232check_status(ACCESS_ADMINISTRATOR);
    3333
     
    3838// |                     perform requested actions                         |
    3939// +-----------------------------------------------------------------------+
    40 if ( isset($_GET['action']) and isset($_GET['plugin']) and !is_adviser() )
     40if (isset($_GET['action']) and isset($_GET['plugin']) and !is_adviser())
    4141{
    4242  $plugin_id = $_GET['plugin'];
     
    4444  if (!empty($crt_db_plugin))
    4545  {
    46     $crt_db_plugin=$crt_db_plugin[0];
     46      $crt_db_plugin = $crt_db_plugin[0];
    4747  }
    4848  else
     
    5252
    5353  $errors = array();
    54   $file_to_include = PHPWG_PLUGINS_PATH.$plugin_id.'/maintain.inc.php';
    55 
    56   switch ( $_GET['action'] )
     54  $file_to_include = PHPWG_PLUGINS_PATH . $plugin_id . '/maintain.inc.php';
     55
     56  switch ($_GET['action'])
    5757  {
    5858    case 'install':
    59       if ( !empty($crt_db_plugin))
     59      if (!empty($crt_db_plugin))
    6060      {
    6161        array_push($errors, 'CANNOT install - ALREADY INSTALLED');
     
    6363      }
    6464      $fs_plugins = get_fs_plugins();
    65       if ( !isset( $fs_plugins[$plugin_id] ) )
     65      if (!isset($fs_plugins[$plugin_id]))
    6666      {
    6767        array_push($errors, 'CANNOT install - NO SUCH PLUGIN');
    6868        break;
    6969      }
    70       if ( file_exists($file_to_include) )
    71       {
    72         include_once($file_to_include);
    73         if ( function_exists('plugin_install') )
     70      if (file_exists($file_to_include))
     71      {
     72        include_once($file_to_include);
     73        if (function_exists('plugin_install'))
    7474        {
    7575          plugin_install($plugin_id, $fs_plugins[$plugin_id]['version'], $errors);
     
    7979      {
    8080        $query = '
    81 INSERT INTO '.PLUGINS_TABLE.' (id,version) VALUES ("'
    82 .$plugin_id.'","'.$fs_plugins[$plugin_id]['version'].'"
     81INSERT INTO ' . PLUGINS_TABLE . ' (id,version) VALUES ("'
     82. $plugin_id . '","' . $fs_plugins[$plugin_id]['version'] . '"
    8383)';
    8484        pwg_query($query);
     
    8787
    8888    case 'activate':
    89       if ( !isset($crt_db_plugin) )
    90       {
    91         array_push($errors, 'CANNOT '. $_GET['action'] .' - NOT INSTALLED');
    92         break;
    93       }
    94       if ($crt_db_plugin['state']!='inactive')
    95       {
    96         array_push($errors, 'invalid current state '.$crt_db_plugin['state']);
    97         break;
    98       }
    99       if ( file_exists($file_to_include) )
    100       {
    101         include_once($file_to_include);
    102         if ( function_exists('plugin_activate') )
     89      if (!isset($crt_db_plugin))
     90      {
     91        array_push($errors, 'CANNOT ' . $_GET['action'] . ' - NOT INSTALLED');
     92        break;
     93      }
     94      if ($crt_db_plugin['state'] != 'inactive')
     95      {
     96        array_push($errors, 'invalid current state ' . $crt_db_plugin['state']);
     97        break;
     98      }
     99      if (file_exists($file_to_include))
     100      {
     101        include_once($file_to_include);
     102        if (function_exists('plugin_activate'))
    103103        {
    104104          plugin_activate($plugin_id, $crt_db_plugin['version'], $errors);
     
    108108      {
    109109        $query = '
    110 UPDATE '.PLUGINS_TABLE.' SET state="active" WHERE id="'.$plugin_id.'"';
     110UPDATE ' . PLUGINS_TABLE . ' SET state="active" WHERE id="' . $plugin_id . '"';
    111111        pwg_query($query);
    112112      }
     
    114114
    115115    case 'deactivate':
    116       if ( !isset($crt_db_plugin) )
    117       {
    118         die ('CANNOT '. $_GET['action'] .' - NOT INSTALLED');
    119       }
    120       if ($crt_db_plugin['state']!='active')
    121       {
    122         die('invalid current state '.$crt_db_plugin['state']);
     116      if (!isset($crt_db_plugin))
     117      {
     118        die ('CANNOT ' . $_GET['action'] . ' - NOT INSTALLED');
     119      }
     120      if ($crt_db_plugin['state'] != 'active')
     121      {
     122        die('invalid current state ' . $crt_db_plugin['state']);
    123123      }
    124124      $query = '
    125 UPDATE '.PLUGINS_TABLE.' SET state="inactive" WHERE id="'.$plugin_id.'"';
     125UPDATE ' . PLUGINS_TABLE . ' SET state="inactive" WHERE id="' . $plugin_id . '"';
    126126      pwg_query($query);
    127 
    128       @include_once($file_to_include);
    129       if ( function_exists('plugin_deactivate') )
    130       {
    131         plugin_deactivate($plugin_id);
     127      if (file_exists($file_to_include))
     128      {
     129        include_once($file_to_include);
     130        if (function_exists('plugin_deactivate'))
     131        {
     132          plugin_deactivate($plugin_id);
     133        }
    132134      }
    133135      break;
    134136
    135137    case 'uninstall':
    136       if ( !isset($crt_db_plugin) )
    137       {
    138         die ('CANNOT '. $_GET['action'] .' - NOT INSTALLED');
     138      if (!isset($crt_db_plugin))
     139      {
     140        die ('CANNOT ' . $_GET['action'] . ' - NOT INSTALLED');
    139141      }
    140142      $query = '
    141 DELETE FROM '.PLUGINS_TABLE.' WHERE id="'.$plugin_id.'"';
     143DELETE FROM ' . PLUGINS_TABLE . ' WHERE id="' . $plugin_id . '"';
    142144      pwg_query($query);
    143 
    144       @include_once($file_to_include);
    145       if ( function_exists('plugin_uninstall') )
    146       {
    147         plugin_uninstall($plugin_id);
     145      if (file_exists($file_to_include))
     146      {
     147        include_once($file_to_include);
     148        if (function_exists('plugin_uninstall'))
     149        {
     150          plugin_uninstall($plugin_id);
     151        }
     152      }
     153        break;
     154                       
     155    case 'delete':
     156                        if (!pm_deltree(PHPWG_PLUGINS_PATH . $plugin_id))
     157      {
     158        send_pm_trash(PHPWG_PLUGINS_PATH . $plugin_id);
    148159      }
    149160      break;
    150161  }
    151162  if (empty($errors))
    152   {
    153     // do the redirection so that we allow the plugins to load/unload
    154     redirect($my_base_url);
    155   }
    156   else
    157   {
     163        {
     164                $my_base_url .= isset($_GET['upgrade']) ?
     165      '&plugin='.$plugin_id.'&upgrade='.$_GET['upgrade'].'&reactivate=true':'';
     166
     167                $my_base_url .= isset($_GET['upgradestatus']) ?
     168      '&plugin='.$plugin_id.'&upgradestatus='.$_GET['upgradestatus']:'';
     169
     170                redirect($my_base_url);
     171    }
     172        else
     173        {
    158174    $page['errors'] = array_merge($page['errors'], $errors);
    159175  }
    160176}
     177
     178
     179// +-----------------------------------------------------------------------+
     180// |                     Sections definitions                              |
     181// +-----------------------------------------------------------------------+
     182if (empty($_GET['section']))
     183{
     184  $page['section'] = 'list';
     185}
     186else
     187{
     188  $page['section'] = $_GET['section'];
     189}
     190
     191$tab_link = $my_base_url . '&amp;section=';
     192
     193// TabSheet
     194$tabsheet = new tabsheet();
     195// TabSheet initialization
     196$tabsheet->add('list', l10n('plugins_tab_list'), $tab_link.'list');
     197$tabsheet->add('update', l10n('plugins_tab_update'), $tab_link.'update');
     198$tabsheet->add('new', l10n('plugins_tab_new'), $tab_link.'new');
     199// TabSheet selection
     200$tabsheet->select($page['section']);
     201// Assign tabsheet to template
     202$tabsheet->assign();
     203
     204$my_base_url .= '&section=' . $page['section'];
    161205
    162206
     
    167211uasort($fs_plugins, 'name_compare');
    168212$db_plugins = get_db_plugins();
    169 $db_plugins_by_id=array();
    170 foreach ($db_plugins as $db_plugin)
    171 {
    172   $db_plugins_by_id[$db_plugin['id']] = $db_plugin;
    173 }
    174 
    175 
    176 $template->set_filenames(array('plugins' => 'admin/plugins.tpl'));
    177 
    178 $num=0;
    179 foreach( $fs_plugins as $plugin_id => $fs_plugin )
    180 {
    181   $display_name = $fs_plugin['name'];
    182   if ( !empty($fs_plugin['uri']) )
    183   {
    184     $display_name='<a href="'.$fs_plugin['uri'].'">'.$display_name.'</a>';
    185   }
    186   $desc = $fs_plugin['description'];
    187   if (!empty($fs_plugin['author']))
    188   {
    189     $desc.= ' (<em>';
    190     if (!empty($fs_plugin['author uri']))
    191     {
    192       $desc.= '<a href="'.$fs_plugin['author uri'].'">'.$fs_plugin['author'].'</a>';
    193     }
    194     else
    195     {
    196       $desc.= $fs_plugin['author'];
    197     }
    198     $desc.= '</em>)';
    199   }
    200  
    201   $tpl_plugin =
    202       array(
    203         'NAME' => $display_name,
    204         'VERSION' => $fs_plugin['version'],
    205         'DESCRIPTION' => $desc,
    206         'actions' => array(),
    207         );
    208 
    209 
    210   $action_url = $my_base_url.'&amp;plugin='.$plugin_id;
    211   if ( isset($db_plugins_by_id[$plugin_id]) )
    212   { // already in the database
    213     // MAYBE TODO HERE: check for the version and propose upgrade action
    214     switch ($db_plugins_by_id[$plugin_id]['state'])
    215     {
    216       case 'active':
    217         $tpl_plugin['actions'][] =
    218             array(
    219               'U_ACTION' => $action_url . '&amp;action=deactivate',
    220               'L_ACTION' => l10n('Deactivate'),
    221             );
    222         break;
    223       case 'inactive':
    224         $tpl_plugin['actions'][] =
    225             array(
    226               'U_ACTION' => $action_url . '&amp;action=activate',
    227               'L_ACTION' => l10n('Activate'),
    228             );
    229         $tpl_plugin['actions'][] =
    230             array(
    231               'U_ACTION' => $action_url . '&amp;action=uninstall',
    232               'L_ACTION' => l10n('Uninstall'),
    233               'CONFIRM'  => true,
    234             );
    235         break;
    236     }
    237   }
    238   else
    239   {
    240     $tpl_plugin['actions'][] =
    241         array(
    242           'U_ACTION' => $action_url . '&amp;action=install',
    243           'L_ACTION' => l10n('Install'),
    244           'CONFIRM'  => true,
    245         );
    246   }
    247   $template->append('plugins', $tpl_plugin);
    248 }
    249 
    250 $missing_plugin_ids = array_diff(
    251     array_keys($db_plugins_by_id), array_keys($fs_plugins)
    252   );
    253 foreach( $missing_plugin_ids as $plugin_id )
    254 {
    255   $action_url = $my_base_url.'&amp;plugin='.$plugin_id;
    256 
    257   $template->append( 'plugins',
    258       array(
    259         'NAME' => $plugin_id,
    260         'VERSION' => $db_plugins_by_id[$plugin_id]['version'],
    261         'DESCRIPTION' => "ERROR: THIS PLUGIN IS MISSING BUT IT IS INSTALLED! UNINSTALL IT NOW !",
    262         'actions' => array ( array (
    263               'U_ACTION' => $action_url . '&amp;action=uninstall',
    264               'L_ACTION' => l10n('Uninstall'),
    265           ) )
    266         )
    267      );
    268 }
     213$db_plugins_by_id = array();
     214foreach ($db_plugins as $db_plugin) {
     215    $db_plugins_by_id[$db_plugin['id']] = $db_plugin;
     216}
     217
     218include(PHPWG_ROOT_PATH.'admin/plugins_'.$page['section'].'.php');
    269219
    270220$template->assign_var_from_handle('ADMIN_CONTENT', 'plugins');
     221
    271222?>
  • trunk/include/constants.php

    r2127 r2242  
    2929define('PHPWG_DOMAIN', 'phpwebgallery.net');
    3030define('PHPWG_URL', 'http://www.'.PHPWG_DOMAIN);
     31define('PEM_URL', PHPWG_URL . '/ext');
    3132define('PHPWG_DEFAULT_LANGUAGE', 'en_UK');
    3233define('PHPWG_DEFAULT_TEMPLATE', 'yoga/clear');
  • trunk/language/en_UK/admin.lang.php

    r2208 r2242  
    660660$lang['c13y_anomalies_ignored_count'] = '%d anomalies has been ignored.';
    661661
     662$lang['plugins_need_update'] = 'Plugins which need upgrade';
     663$lang['plugins_dontneed_update'] = 'Plugins up to date';
     664$lang['plugins_cant_check'] = 'Plugin versions can\'t be checked';
     665$lang['plugins_actual_version'] = 'Current<br>version';
     666$lang['plugins_new_version'] = 'Available<br>version';
     667$lang['plugins_auto_update'] = 'Automatic upgrade';
     668$lang['plugins_auto_install'] = 'Automatic installation';
     669$lang['plugins_download'] = 'Download file';
     670$lang['plugins_description'] = '<b>Version:</b> %s<br><br><b>Date:</b> %s<br><br>%s';
     671$lang['plugins_tab_list'] = 'Plugins list';
     672$lang['plugins_tab_update'] = 'Check for updates';
     673$lang['plugins_tab_new'] = 'Other plugins';
     674$lang['plugins_delete'] = 'Delete';
     675$lang['plugins_confirm_delete'] = 'Are you sure you want to delete this plugin?';
     676$lang['plugins_confirm_install'] = 'Are you sure you want to install this plugin?';
     677$lang['plugins_confirm_upgrade'] = 'Are you sur to install this upgrade? You must verify if this version does not need uninstallation.';
     678$lang['plugins_upgrade_ok'] = '%s has been successfully upgraded.';
     679$lang['plugins_install_ok'] = 'Plugin has been successfully copied';
     680$lang['plugins_install_need_activate'] = 'You might go to plugin list to install and activate it.';
     681$lang['plugins_temp_path_error'] = 'Can\'t create temporary file.';
     682$lang['plugins_dl_archive_error'] = 'Can\'t download archive.';
     683$lang['plugins_archive_error'] = 'Can\'t read or extract archive.';
     684$lang['plugins_extract_error'] = 'An error occured during extraction (%s).';
     685$lang['plugins_check_chmod'] = 'Please check "plugins" folder and sub-folders permissions (CHMOD).';
     686$lang['plugins_server_error'] = 'Can\'t connect to server.';
     687
    662688?>
  • trunk/language/fr_FR/admin.lang.php

    r2208 r2242  
    662662$lang['c13y_anomalies_ignored_count'] = '%d anomalies ont été ignorées.';
    663663
     664$lang['plugins_need_update'] = 'Plugins necessitant une mise à jour';
     665$lang['plugins_dontneed_update'] = 'Plugins à jour';
     666$lang['plugins_cant_check'] = 'Impossible de vérifier les plugins suivant';
     667$lang['plugins_actual_version'] = 'Version<br>actuelle';
     668$lang['plugins_new_version'] = 'Version<br>disponible';
     669$lang['plugins_auto_update'] = 'Mise à jour automatique';
     670$lang['plugins_auto_install'] = 'Installation automatique';
     671$lang['plugins_download'] = 'Télécharger le fichier';
     672$lang['plugins_description'] = '<b>Version:</b> %s<br><br><b>Date:</b> %s<br><br>%s';
     673$lang['plugins_tab_list'] = 'Liste des plugins';
     674$lang['plugins_tab_update'] = 'Vérifier les mises à jour';
     675$lang['plugins_tab_new'] = 'Autres plugins disponibles';
     676$lang['plugins_delete'] = 'Supprimer';
     677$lang['plugins_confirm_install'] = 'Etes-vous sûr de vouloir installer ce plugin?';
     678$lang['plugins_confirm_delete'] = 'Etes-vous sûr de vouloir supprimer ce plugin?';
     679$lang['plugins_confirm_upgrade'] = 'Etes-vous sur de vouloir installer cette mise à jour? Vous devez vérifiez que cette mise à jour ne nécessite pas de désinstallation.';
     680$lang['plugins_upgrade_ok'] = '%s a été mis à jour avec succès.';
     681$lang['plugins_install_ok'] = 'Le plugin a été copié avec succès.';
     682$lang['plugins_install_need_activate'] = 'Rendez-vous dans la liste des plugins pour l\'installer et l\'activer.';
     683$lang['plugins_temp_path_error'] = 'Impossible de créer un fichier temporaire.';
     684$lang['plugins_dl_archive_error'] = 'Impossible de récupérer l\'archive.';
     685$lang['plugins_archive_error'] = 'Impossible de lire ou d\'extraire l\'archive.';
     686$lang['plugins_extract_error'] = 'Une erreur est survenue pendant l\'extraction des fichiers (%s).';
     687$lang['plugins_check_chmod'] = 'Vérifiez les permissions du dossier "plugins" et de ses sous-dossiers (CHMOD).';
     688$lang['plugins_server_error'] = 'Impossible de se connecter au serveur.';
     689
    664690?>
  • trunk/template-common/default-layout.css

    r1900 r2242  
    5050  height: 4em;          /* legend height (don't set auto to be Gecko friendly)*/
    5151}
     52
     53/* Tooltips*/
     54.tooltip {
     55   position: relative;
     56}
     57
     58.tooltip span {
     59   display: none;
     60}
     61
     62.tooltip:hover {
     63        cursor: pointer;
     64        z-index: 500;
     65}
     66
     67.tooltip:hover span {
     68        display: inline;
     69        position: absolute;
     70        top: 30px;
     71        left: -50px;
     72        width: 400px;
     73
     74        font-size: 11px;
     75        text-decoration: none;
     76        text-align: justify;
     77        background-color: #FFFFCC;
     78        color: #444444;
     79       
     80        padding: 10px;
     81        border: 1px solid Black;
     82}
  • trunk/template/yoga/admin/default-layout.css

    r2116 r2242  
    1212
    1313TABLE.table2 TD, TABLE.table2 TH {
    14   padding: 0 1em;
     14  padding: 4px 8px;
    1515}
    1616
Note: See TracChangeset for help on using the changeset viewer.