Changeset 70346 in spip-zone


Ignore:
Timestamp:
Mar 3, 2013, 9:53:45 AM (6 years ago)
Author:
eric@…
Message:

Mise au point de la base de données
Mise au point des modes serveur et client

Location:
_plugins_/boussole/trunk
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • _plugins_/boussole/trunk/action/serveur_informer_boussole.php

    r70143 r70346  
    99function action_serveur_informer_boussole_dist(){
    1010
    11         // Securisation: argument attendu est l'alias de la boussole
    12         $securiser_action = charger_fonction('securiser_action', 'inc');
    13         $arg = $securiser_action();
    14         list($alias, $prefixe) = explode('-', $arg);
     11        // Securisation: aucune car c'est une action anonyme pouvant être appeler de l'extérieur
     12        $alias = _request('arg');
    1513
    1614        if ($alias) {
     
    3028                                else {
    3129                                        $page = recuperer_fond('informer', array('alias' => $alias, 'xml' => $xml));
     30                                        echo $page;
    3231                                        spip_log("Information fournie sur la boussole d'alias = $alias", 'boussole' . _LOG_INFO);
    3332                                }
  • _plugins_/boussole/trunk/base/boussole_declarer.php

    r70161 r70346  
    3232                "nom_objet"             => "text DEFAULT '' NOT NULL",
    3333                "slogan_objet"  => "text DEFAULT '' NOT NULL",
    34                 "description_objet"     => "text DEFAULT '' NOT NULL",
     34                "descriptif_objet"      => "text DEFAULT '' NOT NULL",
    3535                "logo_objet"    => "varchar(255) DEFAULT '' NOT NULL",
    3636                "maj"                   => "timestamp");
  • _plugins_/boussole/trunk/boussole_administrations.php

    r70161 r70346  
    33if (!defined("_ECRIRE_INC_VERSION")) return;
    44
     5/**
     6 * Installation du schéma de données propre au plugin en tenant compte des évolutions
     7 *
     8 * @param $nom_meta_base_version
     9 * @param $version_cible
     10 */
    511function boussole_upgrade($nom_meta_base_version, $version_cible){
    612
     
    814
    915        $maj['create'] = array(
    10                 array('maj_tables', array('spip_boussoles'))
     16                array('maj_tables', array('spip_boussoles', 'spip_boussoles_extras'))
    1117        );
    1218
    1319        // On ajoute la table des extras et on supprime toutes les boussoles
    14         // Seule la boussole SPIP sera réinstallée.
    15         // Pour les autres il faudra de toute façon adapter la boussole avant de la réinstaller
     20        // Seule la boussole SPIP sera réinstallée par défaut.
     21        // Pour les autres il faudra de toute façon adapter la boussole avant de les réinstaller
    1622        $maj['0.2'] = array(
    1723                array('maj_tables', array('spip_boussoles_extras')),
     
    2329
    2430        // On ajoute la boussole SPIP par defaut.
    25         // Cependant on ne teste ni la validite du fichier xml fourni ni la bonne insertion en bdd
    2631        if (!isset($GLOBALS['meta']['boussole_infos_spip'])) {
    2732                include_spip('inc/deboussoler');
    28                 $xml = 'http://zone.spip.org/trac/spip-zone/export/HEAD/_galaxie_/boussole.spip.org/boussole_spip.xml';
    29                 $url = boussole_localiser_xml($xml);
    30                 list($ok, $message) = boussole_ajouter($url);
     33                list($ok, $message) = boussole_ajouter('spip', 'spip');
     34                if (!$ok)
     35                        spip_log("Administrations - Erreur lors de l'ajout de la boussole spip : " . $message, 'boussole' . _LOG_ERREUR);
     36                else
     37                        spip_log("Administrations - Ajout de la boussole spip ok", 'boussole' . _LOG_INFO);
    3138        }
     39
     40        spip_log('Installation des tables du plugin','boussole' . _LOG_INFO);
     41
    3242}
    3343
     44/**
     45 * Suppression de l'ensemble du schéma de données propre au plugin
     46 *
     47 * @param $nom_meta_base_version
     48 */
    3449function boussole_vider_tables($nom_meta_base_version) {
    3550        // On nettoie les metas de mises a jour des boussoles
    36         nettoyer_donnees_boussole(true);
     51        nettoyer_donnees_boussole();
    3752
    3853        // on efface ensuite la table et la meta habituelle designant la version du plugin
     
    4156        effacer_meta($nom_meta_base_version);
    4257
    43         spip_log('Désinstallation des tables du plugin Boussole','boussole' . _LOG_INFO);
     58        spip_log('Désinstallation des tables du plugin','boussole' . _LOG_INFO);
    4459}
    4560
    46 function nettoyer_donnees_boussole($meta=false) {
     61/**
     62 * Suppression de l'ensemble des données des tables et metas propres au plugin boussole
     63 *
     64 */
     65function nettoyer_donnees_boussole() {
    4766        $alias = array();
    4867
     
    5574        }
    5675
    57         if (!$meta)
    58                 sql_delete('spip_boussoles');
     76        sql_delete('spip_boussoles');
     77}
    5978
    60 }
    6179?>
  • _plugins_/boussole/trunk/genie/boussole_actualiser_client.php

    r70136 r70346  
    44function genie_boussole_actualiser_client_dist($last) {
    55
    6         include_spip('inc/utils');
    76        include_spip('inc/deboussoler');
    87
    9         // On verifie que la boussole SPIP est bien ajoutee
    10         $meta_boussole = 'boussole_infos_spip';
    11         if (lire_meta($meta_boussole)) {
    12                 // On appelle donc la fonction d'actualisation
    13                 $xml = 'http://zone.spip.org/trac/spip-zone/export/HEAD/_galaxie_/boussole.spip.org/boussole_spip.xml';
    14                 if (!$url = boussole_localiser_xml($xml)) {
    15                         // Le fichier est introuvable
    16                         spip_log("ERREUR ACTUALISATION CRON : fichier xml introuvable", 'boussole' . _LOG_ERREUR);
    17                 }
    18                 else {
    19                         if (!boussole_valider_xml($url, $erreur)) {
    20                                 // Le fichier ne suit pas la DTD (boussole.dtd)
    21                                 spip_log("ERREUR ACTUALISATION CRON : fichier xml invalide", 'boussole' . _LOG_ERREUR);
    22                         }
    23                         else {
    24                                 // On insere la boussole dans la base
    25                                 // et on traite le cas d'erreur fichier ($retour['message_erreur']) non conforme
    26                                 // si c'est encore possible apres avoir valide le fichier avec la dtd
    27                                 list($ok, $message) = boussole_ajouter($url);
    28                        
    29                                 // Determination des messages de retour
    30                                 if (!$ok) {
    31                                         spip_log("ERREUR ACTUALISATION CRON : " . $message, 'boussole' . _LOG_ERREUR);
    32                                 }
    33                                 else {
    34                                         spip_log("ACTUALISATION CRON OK", 'boussole' . _LOG_INFO);
    35                                 }
    36                         }
     8        // Recherche des metas commençant par "boussole_infos" pour connaitre la liste des boussoles ajoutées par le client
     9        $boussoles = sql_allfetsel('valeur', 'spip_meta', array('nom LIKE ' . sql_quote('boussole_infos%')));
     10        if ($boussoles) {
     11                $infos = array_map('unserialize', array_map('reset', $boussoles));
     12                foreach($infos as $_infos) {
     13                        list($ok, $message) = boussole_ajouter($_infos['alias'], $_infos['serveur']);
     14                        if (!$ok)
     15                                spip_log("Actualisation périodique en erreur (boussole = " . $_infos['alias'] . ") : " . $message, 'boussole' . _LOG_ERREUR);
     16                        else
     17                                spip_log("Actualisation périodique ok (boussole = " . $_infos['alias'] . ")", 'boussole' . _LOG_INFO);
    3718                }
    3819        }
  • _plugins_/boussole/trunk/genie/boussole_actualiser_serveur.php

    r70143 r70346  
    66        include_spip('inc/cacher');
    77
     8        // Acquisition de la liste des boussoles disponibles sur le serveur
    89        $boussoles = array();
    910        $boussoles = pipeline('declarer_boussoles', $boussoles);
    1011
     12        // Regénération du cache de chaque boussole disponible
    1113        if ($boussoles) {
    12                 foreach($boussoles as $_alias => $_prefixe) {
    13                         boussole_cacher($_alias, $_prefixe);
     14                foreach($boussoles as $_alias => $_infos) {
     15                        boussole_cacher($_alias, $_infos['prefixe']);
    1416                }
    1517        }
  • _plugins_/boussole/trunk/genie/boussole_taches_generales_cron.php

    r70143 r70346  
    44function boussole_taches_generales_cron($taches_generales) {
    55
    6         // Ajout de la tache CRON de mise a jour reguliere des boussoles disponibles sur le site en mode client et serveur
    7         // Par defaut, toutes les 24h
     6        // Ajout des taches CRON de mise a jour périodique (toutes les 24h) :
     7        // - des boussoles disponibles pour le serveur
     8        // - des boussoles ajoutés pour le client
     9
    810        // -- Pour le CRON serveur on vérifie qu'une boussole est déclarée
    911        $boussoles = array();
     
    1214                $taches_generales['boussole_actualiser_serveur'] = 24*3600;
    1315
     16        // -- Pour le CRON client la vérification est faite dans la tache elle-même
    1417        $taches_generales['boussole_actualiser_client'] = 24*3600;
    1518
  • _plugins_/boussole/trunk/inc/deboussoler.php

    r62034 r70346  
    99 * Ajout de la boussole dans la base de donnees
    1010 *
    11  * @param string $url
    12  * @param string &$erreur
    13  * @return boolean
    14  */
    15 
    16 // $url => url ou path du fichier xml de description de la boussole
    17 // $erreur      => message d'erreur deja traduit
    18 function boussole_ajouter($url) {
     11 * @param string $boussole      Alias de la boussole
     12 * @param string $serveur       Alias du serveur fournissant les données sur la boussole
     13 * @return array
     14 */
     15function boussole_ajouter($boussole, $serveur='spip') {
    1916
    2017        // On initialise le message de sortie
     
    2219       
    2320        // On recupere les infos du fichier xml de description de la balise
    24         $infos = boussole_parser_xml($url);
     21        $infos = boussole_parser_xml($boussole, $serveur);
    2522        if (!$infos OR !$infos['boussole']['alias']){
    26                 $message = _T('boussole:message_nok_xml_invalide', array('fichier' => $url));
     23                $message = _T('boussole:message_nok_xml_invalide', array('fichier' => $boussole));
    2724                return array(false, $message);
    2825        }
     
    5956        }
    6057        // -- insertion de la nouvelle liste de sites pour cette boussole
    61         if (!$ids = sql_insertq_multi('spip_boussoles', $infos['sites'])) {
    62                 $message = _T('boussole:message_nok_ecriture_bdd');
     58        if (!sql_insertq_multi('spip_boussoles', $infos['sites'])) {
     59                $message = _T('boussole:message_nok_ecriture_bdd', array('table' => 'spip_boussoles'));
     60                return array(false, $message);
     61        }
     62        // -- insertion de la nouvelle liste des extras pour cette boussole
     63        if (sql_insertq_multi('spip_boussoles_extras', $infos['extras']) === false) {
     64                $message = _T('boussole:message_nok_ecriture_bdd', array('table' => 'spip_boussoles_extras'));
    6365                return array(false, $message);
    6466        }
    6567        // -- consignation des informations de mise a jour de cette boussole dans la table spip_meta
    6668        $infos['boussole']['nbr_sites'] = count($infos['sites']);
    67         $infos['boussole']['xml'] = $url;
     69        $infos['boussole']['serveur'] = $serveur;
    6870        $infos['boussole']['maj'] = date('Y-m-d H:i:s');
    6971        ecrire_meta($meta_boussole, serialize($infos['boussole']));
     
    7173        // On definit le message de retour ok (actualisation ou ajout)
    7274        if ($actualisation)
    73                 $message = _T('boussole:message_ok_boussole_actualisee', array('fichier' => $url));
     75                $message = _T('boussole:message_ok_boussole_actualisee', array('fichier' => $boussole));
    7476        else
    75                 $message = _T('boussole:message_ok_boussole_ajoutee', array('fichier' => $url));
     77                $message = _T('boussole:message_ok_boussole_ajoutee', array('fichier' => $boussole));
    7678       
    7779        return array(true, $message);
     
    8284 * Suppression de la boussole dans la base de donnees
    8385 *
    84  * @param int $aka_boussole
     86 * @param int $aka_boussole     alias de la boussole
    8587 * @return boolean
    8688 */
    87 
    88 // $aka_boussole        => alias de la boussole, par defaut, spip
    8989function boussole_supprimer($aka_boussole) {
    9090       
     
    9595        // On supprime les sites de cette boussole
    9696        sql_delete('spip_boussoles','aka_boussole='.sql_quote($aka_boussole));
     97        // On supprime les extras de cette boussole
     98        sql_delete('spip_boussoles_extras','aka_boussole='.sql_quote($aka_boussole));
    9799        // On supprime ensuite la meta consignant la derniere mise a jour de cette boussole
    98100        effacer_meta('boussole_infos_' . $aka_boussole);
     101
    99102        return true;
    100103}
     
    172175 * Les cles du tableau correspondent au nom des champs en base de donnees
    173176 *
    174  * @param string $url
     177 * @param string $boussole      Alias de la boussole
     178 * @param string $serveur       Alias du serveur fournissant les données sur la boussole
    175179 * @return array()
    176180 */
    177 
    178 // $url => url ou path du fichier xml de description de la boussole
    179 function boussole_parser_xml($url) {
     181function boussole_parser_xml($boussole, $serveur='spip') {
     182        global $serveurs_boussoles;
    180183
    181184        $infos = array();
    182185
    183         // Lire les donnees du fichier xml d'une boussole
    184         include_spip('inc/xml');
    185         $xml = spip_xml_load($url);
    186        
    187         // On recupere les infos de la balise boussole
    188         if (spip_xml_match_nodes(',^boussole,', $xml, $matches)){
    189                 $tag = array_keys($matches);
    190                 list($balise, $attributs) = spip_xml_decompose_tag($tag[0]);
    191                 $infos[$balise] = $attributs;
    192        
    193                 // On recupere les infos des balises groupe et site
    194                 if (spip_xml_match_nodes(',^groupe,', $xml, $groupes)){
    195                         $infos['sites'] = array();
     186        // Acquérir les informations de la boussole à partir du serveur
     187        include_spip('inc/distant');
     188        $action = str_replace('[arguments]', $boussole, $serveurs_boussoles[$serveur]);
     189        $page = recuperer_page($action);
     190
     191        $convertir = charger_fonction('simplexml_to_array', 'inc');
     192        $tableau = $convertir(simplexml_load_string($page), false);
     193
     194        if ($tableau['name'] == 'boussole') {
     195                $infos['sites'] = array();
     196                $infos['extras'] = array();
     197
     198                // Collecter les attributs pour la meta de la boussole
     199                $infos['boussole'] = $tableau['attributes'];
     200
     201                // Construire l'objet extras de la boussole
     202                $extra['aka_boussole'] = $infos['boussole']['alias'];
     203                $extra['type_objet'] = 'boussole';
     204                $extra['aka_objet'] = $infos['boussole']['alias'];
     205                $extra['logo_objet'] = $infos['boussole']['logo'];
     206                if (isset($tableau['children']['nom']))
     207                        $extra['nom_objet'] = '<multi>' . $tableau['children']['nom'][0]['children']['multi'][0]['text'] . '</multi>';
     208                if (isset($tableau['children']['slogan']))
     209                        $extra['slogan_objet'] = '<multi>' . $tableau['children']['slogan'][0]['children']['multi'][0]['text'] . '</multi>';
     210                if (isset($tableau['children']['description']))
     211                        $extra['descriptif_objet'] = '<multi>' . $tableau['children']['description'][0]['children']['multi'][0]['text'] . '</multi>';
     212                $infos['extras'][] = $extra;
     213
     214                // Collecter les informations des groupes
     215                if (isset($tableau['children']['groupe'])) {
    196216                        $rang_groupe = 0;
    197                         foreach (array_keys($groupes) as $_groupe){
    198                                 $site = array();
     217                        foreach ($tableau['children']['groupe'] as $_groupe) {
     218                                // Construire l'objet extras du groupe
     219                                $extra['aka_boussole'] = $infos['boussole']['alias'];
     220                                $extra['type_objet'] = 'groupe';
     221                                $extra['aka_objet'] = $_groupe['attributes']['type'];
     222                                $extra['logo_objet'] = '';
     223                                if (isset($_groupe['children']['nom']))
     224                                        $extra['nom_objet'] = '<multi>' . $_groupe['children']['nom'][0]['children']['multi'][0]['text'] . '</multi>';
     225                                $extra['slogan_objet'] = '';
     226                                $extra['descriptif_objet'] = '';
     227                                $infos['extras'][] = $extra;
     228
    199229                                // On consigne l'alias et le rang du groupe
    200                                 list($balise_groupe, $attributs_groupe) = spip_xml_decompose_tag($_groupe);
    201230                                $rang_groupe = ++$i;
    202231                                // On consigne l'alias et l'url de chaque site du groupe en cours de traitement
    203232                                $rang_site = 0;
    204                                 foreach (array_keys($groupes[$_groupe][0]) as $_site){
    205                                         // Alias de la boussole
    206                                         $site['aka_boussole'] = $infos['boussole']['alias'];
    207                                         // Infos du groupe
    208                                         $site['aka_groupe'] = $attributs_groupe['type'];
    209                                         $site['rang_groupe'] = $rang_groupe;
    210                                         // Infos du site
    211                                         list($balise_site, $attributs_site) = spip_xml_decompose_tag($_site);
    212                                         $site['aka_site'] = $attributs_site['alias'];
    213                                         $site['url_site'] = $attributs_site['src'];
    214                                         $site['rang_site'] = ++$rang_site;
    215                                         $site['affiche'] = 'oui';
    216                                         $site['id_syndic'] = 0;
    217                                         // On ajoute le site ainsi defini aux tableau des sites si celui-ci est actif
    218                                         if ($attributs_site['actif'] == 'oui')
    219                                                 $infos['sites'][] = $site;
     233                                if (isset($_groupe['children']['site'])) {
     234                                        foreach ($_groupe['children']['site'] as $_site){
     235                                                if ($_site['attributes']['actif'] == 'oui') {
     236                                                        // Alias de la boussole
     237                                                        $site['aka_boussole'] = $infos['boussole']['alias'];
     238                                                        // Infos du groupe
     239                                                        $site['aka_groupe'] = $_groupe['attributes']['type'];
     240                                                        $site['rang_groupe'] = $rang_groupe;
     241                                                        // Infos du site
     242                                                        $site['aka_site'] = $_site['attributes']['alias'];
     243                                                        $site['url_site'] = $_site['attributes']['src'];
     244                                                        $site['rang_site'] = ++$rang_site;
     245                                                        $site['affiche'] = 'oui';
     246                                                        $site['id_syndic'] = 0;
     247                                                        $infos['sites'][] = $site;
     248
     249                                                        // Construire l'objet extra du site
     250                                                        $extra['aka_boussole'] = $infos['boussole']['alias'];
     251                                                        $extra['type_objet'] = 'site';
     252                                                        $extra['aka_objet'] = $_site['attributes']['alias'];
     253                                                        $extra['logo_objet'] = $_site['attributes']['logo'];
     254                                                        if (isset($_site['children']['nom']))
     255                                                                $extra['nom_objet'] = '<multi>' . $_site['children']['nom'][0]['children']['multi'][0]['text'] . '</multi>';
     256                                                        if (isset($_site['children']['slogan']))
     257                                                                $extra['slogan_objet'] = '<multi>' . $_site['children']['slogan'][0]['children']['multi'][0]['text'] . '</multi>';
     258                                                        if (isset($_site['children']['description']))
     259                                                                $extra['descriptif_objet'] = '<multi>' . $_site['children']['description'][0]['children']['multi'][0]['text'] . '</multi>';
     260                                                        $infos['extras'][] = $extra;
     261                                                }
     262                                        }
    220263                                }
    221264                        }
  • _plugins_/boussole/trunk/inc/simplexml_to_array.php

    r70136 r70346  
    1414function inc_simplexml_to_array_dist($obj, $utiliser_namespace='false') {
    1515
     16        $tableau = array();
     17
    1618        // Cette fonction getDocNamespaces() est longue sur de gros xml. On permet donc
    1719        // de l'activer ou pas suivant le contenu supposé du XML
    18         if ($utiliser_namespace)
    19                 $namespace = $obj->getDocNamespaces(true);
    20         $namespace[NULL] = NULL;
     20        if ($obj) {
     21                if ($utiliser_namespace)
     22                        $namespace = $obj->getDocNamespaces(true);
     23                $namespace[NULL] = NULL;
    2124
    22         $children = array();
    23         $attributes = array();
    24         $name = strtolower((string)$obj->getName());
     25                $name = strtolower((string)$obj->getName());
     26                $text = trim((string)$obj);
     27                if( strlen($text) <= 0 ) {
     28                        $text = NULL;
     29                }
    2530
    26         $text = trim((string)$obj);
    27         if( strlen($text) <= 0 ) {
    28                 $text = NULL;
     31                $children = array();
     32                $attributes = array();
     33
     34                // get info for all namespaces
     35                if (is_object($obj)) {
     36                        foreach( $namespace as $ns=>$nsUrl ) {
     37                                // atributes
     38                                $objAttributes = $obj->attributes($ns, true);
     39                                foreach( $objAttributes as $attributeName => $attributeValue ) {
     40                                        $attribName = strtolower(trim((string)$attributeName));
     41                                        $attribVal = trim((string)$attributeValue);
     42                                        if (!empty($ns)) {
     43                                                $attribName = $ns . ':' . $attribName;
     44                                        }
     45                                        $attributes[$attribName] = $attribVal;
     46                                }
     47
     48                                // children
     49                                $objChildren = $obj->children($ns, true);
     50                                foreach( $objChildren as $childName=>$child ) {
     51                                        $childName = strtolower((string)$childName);
     52                                        if( !empty($ns) ) {
     53                                                $childName = $ns.':'.$childName;
     54                                        }
     55                                        $children[$childName][] = inc_simplexml_to_array_dist($child);
     56                                }
     57                        }
     58                }
     59
     60                $tableau = array(
     61                        'name'=>$name,
     62                        'text'=>$text,
     63                        'attributes'=>$attributes,
     64                        'children'=>$children
     65                );
    2966        }
    3067
    31         // get info for all namespaces
    32         if (is_object($obj)) {
    33                 foreach( $namespace as $ns=>$nsUrl ) {
    34                         // atributes
    35                         $objAttributes = $obj->attributes($ns, true);
    36                         foreach( $objAttributes as $attributeName => $attributeValue ) {
    37                                 $attribName = strtolower(trim((string)$attributeName));
    38                                 $attribVal = trim((string)$attributeValue);
    39                                 if (!empty($ns)) {
    40                                         $attribName = $ns . ':' . $attribName;
    41                                 }
    42                                 $attributes[$attribName] = $attribVal;
    43                         }
    44 
    45                         // children
    46                         $objChildren = $obj->children($ns, true);
    47                         foreach( $objChildren as $childName=>$child ) {
    48                                 $childName = strtolower((string)$childName);
    49                                 if( !empty($ns) ) {
    50                                         $childName = $ns.':'.$childName;
    51                                 }
    52                                 $children[$childName][] = inc_simplexml_to_array_dist($child);
    53                         }
    54                 }
    55         }
    56 
    57         return array(
    58                 'name'=>$name,
    59                 'text'=>$text,
    60                 'attributes'=>$attributes,
    61                 'children'=>$children
    62         );
     68        return $tableau;
    6369}
    6470
  • _plugins_/boussole/trunk/informer.html

    r70143 r70346  
    1 #CACHE{84600}
     1#CACHE{0}
    22[(#SET{contenu, #INCLURE{#ENV{xml}|copie_locale{modif}}})
    33][(#HTTP_HEADER{Content-Type: text/xml; charset=#CHARSET})
  • _plugins_/boussole/trunk/lang/boussole_fr.php

    r69128 r70346  
    106106        'message_nok_boussole_inconnue' => 'Aucune boussole ne correspond &agrave l\'alias « @alias@ »',
    107107        'message_nok_champ_obligatoire' => 'Ce champ est obligatoire',
    108         'message_nok_ecriture_bdd' => 'Erreur d\'écriture en base de données',
     108        'message_nok_ecriture_bdd' => 'Erreur d\'écriture en base de données (table @table@)',
    109109        'message_nok_xml_introuvable' => 'Le fichier « @fichier@ » est introuvable',
    110110        'message_nok_xml_invalide' => 'Le fichier XML « @fichier@ » de description de la boussole n\'est pas conforme à la DTD',
Note: See TracChangeset for help on using the changeset viewer.