Index: extensions/EStat/admin.php
===================================================================
--- extensions/EStat/admin.php	(revision 17737)
+++ extensions/EStat/admin.php	(revision 17758)
@@ -27,5 +27,4 @@
   $config=Array();
   GPCCore::loadConfig('estat', $config);
-
 
   if($config['installed']!=ESTAT_VERSION2)
Index: extensions/EStat/admin/estat_admin.tpl
===================================================================
--- extensions/EStat/admin/estat_admin.tpl	(revision 17737)
+++ extensions/EStat/admin/estat_admin.tpl	(revision 17758)
@@ -5,71 +5,65 @@
   var translateString={
       {/literal}
-        dateTime:'{"estat_datetime"|@translate}',
-        ip_user:'{"estat_ip_user"|@translate}',
-        ipAdress:'{"estat_ipAdress"|@translate}',
-        country:'{"estat_country"|@translate}',
-        album:'{"estat_category"|@translate}',
-        image:'{"estat_image"|@translate}',
-        tags:'{"estat_tags"|@translate}',
-        section:'{"estat_section"|@translate}',
-        browser:'{"estat_browser"|@translate}',
-        os:'{"estat_os"|@translate}',
-        type:'{"estat_type"|@translate}',
-        engine:'{"estat_engine"|@translate}',
-        item_nb:'{"estat_item_nb"|@translate}',
-        items_nb:'{"estat_items_nb"|@translate}',
-        events:'{"estat_events"|@translate}',
-        ok:'{"estat_ok"|@translate}',
-        cancel:'{"estat_cancel"|@translate}',
-        eraseFilter:'{"estat_eraseFilter"|@translate}',
-        sortBy:'{"estat_sortBy"|@translate}',
-        ip_nb:'{"estat_ip_nb"|@translate}',
-        ips_nb:'{"estat_ips_nb"|@translate}',
-        category_nb:'{"estat_category_nb"|@translate}',
-        categories_nb:'{"estat_categories_nb"|@translate}',
-        image_nb:'{"estat_image_nb"|@translate}',
-        images_nb:'{"estat_images_nb"|@translate}',
-        dataExport:'{"estat_dataExport"|@translate}',
+        dateTime:"{'estat_datetime'|@translate}",
+        ip_user:"{'estat_ip_user'|@translate}",
+        ipAdress:"{'estat_ipAdress'|@translate}",
+        country:"{'estat_country'|@translate}",
+        album:"{'estat_category'|@translate}",
+        image:"{'estat_image'|@translate}",
+        tags:"{'estat_tags'|@translate}",
+        section:"{'estat_section'|@translate}",
+        browser:"{'estat_browser'|@translate}",
+        os:"{'estat_os'|@translate}",
+        type:"{'estat_type'|@translate}",
+        engine:"{'estat_engine'|@translate}",
+        item_nb:"{'estat_item_nb'|@translate}",
+        items_nb:"{'estat_items_nb'|@translate}",
+        events:"{'estat_events'|@translate}",
+        ok:"{'estat_ok'|@translate}",
+        cancel:"{'estat_cancel'|@translate}",
+        eraseFilter:"{'estat_eraseFilter'|@translate}",
+        sortBy:"{'estat_sortBy'|@translate}",
+        dataExport:"{'estat_dataExport'|@translate}",
 
-        allYear:'{"estat_allYear"|@translate}',
-        year:'{"estat_year"|@translate}',
-        viewedPages:'{"estat_viewedPages"|@translate}',
-        viewedAlbums:'{"estat_viewedAlbums"|@translate}',
-        viewedImages:'{"estat_viewedImages"|@translate}',
-        uniqueIP:'{"estat_uniqueIP"|@translate}',
-        visits:'{"estat_visits"|@translate}',
-        period:'{"estat_period"|@translate}',
+        allYear:"{'estat_allYear'|@translate}",
+        year:"{'estat_year'|@translate}",
+        viewedPages:"{'estat_viewedPages'|@translate}",
+        viewedAlbums:"{'estat_viewedAlbums'|@translate}",
+        viewedImages:"{'estat_viewedImages'|@translate}",
+        uniqueIP:"{'estat_uniqueIP'|@translate}",
+        visits:"{'estat_visits'|@translate}",
+        period:"{'estat_period'|@translate}",
 
-        fileFormatCSV:'{"estat_fileFormatCSV"|@translate}',
-        fileFormatODS:'{"estat_fileFormatODS"|@translate}',
-        fileFormatSQLite:'{"estat_fileFormatSQLite"|@translate}',
+        fileFormatCSV:"{'estat_fileFormatCSV'|@translate}",
+        fileFormatODS:"{'estat_fileFormatODS'|@translate}",
+        fileFormatSQLite:"{'estat_fileFormatSQLite'|@translate}",
 
-        fileCompressionNone:'{"estat_fileCompressionNone"|@translate}',
-        fileCompressionZIP:'{"estat_fileCompressionZIP"|@translate}',
-        fileCompressionGZ:'{"estat_fileCompressionGZ"|@translate}',
-        fileCompressionBZ2:'{"estat_fileCompressionBZ2"|@translate}',
+        fileCompressionNone:"{'estat_fileCompressionNone'|@translate}",
+        fileCompressionZIP:"{'estat_fileCompressionZIP'|@translate}",
+        fileCompressionGZ:"{'estat_fileCompressionGZ'|@translate}",
+        fileCompressionBZ2:"{'estat_fileCompressionBZ2'|@translate}",
 
-        downloadError:'{"estat_downloadError"|@translate}',
-        waitForDownload:'{"estat_waitForDownload"|@translate}',
+        downloadError:"{'estat_downloadError'|@translate}",
+        waitForDownload:"{'estat_waitForDownload'|@translate}",
 
-        total:'{"estat_total"|@translate}',
-        ua_Mobile:'{"ua_Mobile"|@translate}',
-        ua_Computer:'{"ua_Computer"|@translate}',
-        ua_Console:'{"ua_Console"|@translate}',
-        ua_Unknown:'{"ua_Unknown"|@translate}',
-        ua_Crawler:'{"ua_Crawler"|@translate}',
-        ua_Other:'{"ua_Other"|@translate}',
+        total:"{'estat_total'|@translate}",
+        ua_Mobile:"{'ua_Mobile'|@translate}",
+        ua_Computer:"{'ua_Computer'|@translate}",
+        ua_Console:"{'ua_Console'|@translate}",
+        ua_Unknown:"{'ua_Unknown'|@translate}",
+        ua_Crawler:"{'ua_Crawler'|@translate}",
+        ua_Other:"{'ua_Other'|@translate}",
       {literal}
 
         filter:{
       {/literal}
-          dateTime:'{"estat_datetime"|@translate}',
-          ipAdress:'{"estat_ipAdress"|@translate}',
-          country:'{"estat_country"|@translate}',
-          section:'{"estat_section"|@translate}',
-          browser:'{"estat_browser"|@translate}',
-          os:'{"estat_os"|@translate}',
-          type:'{"estat_type"|@translate}',
-          engine:'{"estat_engine"|@translate}'
+          dateTime:"{'estat_datetime'|@translate}",
+          ipAdress:"{'estat_ipAdress'|@translate}",
+          country:"{'estat_country'|@translate}",
+          section:"{'estat_section'|@translate}",
+          browser:"{'estat_browser'|@translate}",
+          os:"{'estat_os'|@translate}",
+          type:"{'estat_type'|@translate}",
+          engine:"{'estat_engine'|@translate}"
       {literal}
         }
@@ -78,18 +72,19 @@
     inputFilterBoxLang={
     {/literal}
-      'Operator':'{"filter_operator"|@translate}',
-      'Value':'{"filter_value"|@translate}',
-      'MinValue':'{"filter_minValue"|@translate}',
-      'MaxValue':'{"filter_maxValue"|@translate}',
-      '>':'{"filter_gt"|@translate}',
-      '<':'{"filter_lt"|@translate}',
-      '>=':'{"filter_ge"|@translate}',
-      '<=':'{"filter_le"|@translate}',
-      '=':'{"filter_equal"|@translate}',
-      '!=':'{"filter_notEqual"|@translate}',
-      'between':'{"filter_between"|@translate}',
-      'and':'{"filter_and"|@translate}',
-      'like':'{"filter_like"|@translate}',
-      'not like':'{"filter_notLike"|@translate}',
+      'Operator':"{'filter_operator'|@translate}",
+      'Value':"{'filter_value'|@translate}",
+      'MinValue':"{'filter_minValue'|@translate}",
+      'MaxValue':"{'filter_maxValue'|@translate}",
+      '>':"{'filter_gt'|@translate}",
+      '<':"{'filter_lt'|@translate}",
+      '>=':"{'filter_ge'|@translate}",
+      '<=':"{'filter_le'|@translate}",
+      '=':"{'filter_equal'|@translate}",
+      '!=':"{'filter_notEqual'|@translate}",
+      'between':"{'filter_between'|@translate}",
+      'not between':"{'filter_notbetween'|@translate}",
+      'and':"{'filter_and'|@translate}",
+      'like':"{'filter_like'|@translate}",
+      'not like':"{'filter_notLike'|@translate}",
       'invalidParam':"{'filter_invalid'|@translate}"
     {literal}
@@ -97,8 +92,8 @@
     inputExportBoxLang={
     {/literal}
-      'export':'{"export_dialogTitle"|@translate}',
-      'name':'{"export_Name"|@translate}',
-      'format':'{"export_Format"|@translate}',
-      'compression':'{"export_Compression"|@translate}'
+      'export':"{'export_dialogTitle'|@translate}",
+      'name':"{'export_Name'|@translate}",
+      'format':"{'export_Format'|@translate}",
+      'compression':"{'export_Compression'|@translate}"
     {literal}
     };
Index: extensions/EStat/estat_ajax.php
===================================================================
--- extensions/EStat/estat_ajax.php	(revision 17737)
+++ extensions/EStat/estat_ajax.php	(revision 17758)
@@ -475,5 +475,5 @@
 
       $gStatFile=new StatDBGlobal($this->fileStatDir, self::FILE_GLOBAL);
-      $gStatFile->setIpCountryFile(ESTAT_PATH.'data/ipCountry.db');
+      $gStatFile->setIpCountryFile($this->fileStatDir.'/'.self::FILE_COUNTRY.'.db', true);
       $gStatFile->open(ASDF_OPEN_WRITE);
       if($gStatFile->getInfoProperty('log', 'startImport')==null)
@@ -575,5 +575,5 @@
 
       // prepare an EStat_IdList to buid a list of unique Id
-      $idList=new EStat_IdList(array('catId', 'imageId', 'tagsId', 'userId', 'IPadress'));
+      $idList=new EStat_IdList(array('catId', 'imageId', 'tagsId', 'userId'));
       // $idAssoc will get the label associated to the id
       $idAssoc=array(
@@ -581,6 +581,5 @@
         'imageId'=>array(),
         'tagsId'=>array(),
-        'userId'=>array(),
-        'IPadress'=>array()
+        'userId'=>array()
       );
 
@@ -612,8 +611,4 @@
         $this->prepareIdList($idAssoc, 'userId', "SELECT id, username AS name FROM ".USERS_TABLE." WHERE id IN (".implode(',', $idList->getItems('userId')).") ORDER BY id;");
 
-      // for reverse DNS
-      if($this->config['logs.ipCountry']=='y')
-        $this->getIpInfos($idAssoc, $idList->getItems('IPadress'));
-
       // complete the data
       foreach($returned['rows'] as $key=>$row)
@@ -636,5 +631,5 @@
           'date' => date('Y-m-d H:i:s', $row['date']),
           'IpUserId' => ($row['userId']==2)?$row['IPadress']:$this->getId($idAssoc, 'userId', $row['userId'], $row['IPadress'], 'name'),
-          //'country' => $row['country'],
+          'country' => $row['country'],
           'catId' => GPCCore::getUserLanguageDesc($this->getId($idAssoc, 'catId', $row['catId'], ($row['catId']==0)?'-':'?')),
           'imageId' => $this->getId($idAssoc, 'imageId', $row['imageId'], ($row['imageId']==0)?'-':'?', 'name'),
@@ -702,5 +697,4 @@
         if($row['userId']==2)
         {
-          $ipInfo=$this->getId($idAssoc, 'IPadress', $tmp['IpUserId'], '?');
           if($this->config['logs.reverseDNS']=='y')
           {/*
@@ -716,7 +710,7 @@
           }
 
-          if($ipInfo['country']!='--' and $ipInfo['country']!='XA')
+          if($row['country']!='--' and $row['country']!='XA')
           {
-            $tmp['IpUserId'].='<span class="geoip">[<a href="http://www.geoiptool.com/fr/?IP='.$tmp['IpUserId'].'">'.$ipInfo['country'].'</a>]</span>';
+            $tmp['IpUserId'].='<span class="geoip">[<a href="http://www.geoiptool.com/fr/?IP='.$tmp['IpUserId'].'">'.$row['country'].'</a>]</span>';
           }
           else
Index: extensions/EStat/estat_ajax_export.php
===================================================================
--- extensions/EStat/estat_ajax_export.php	(revision 17737)
+++ extensions/EStat/estat_ajax_export.php	(revision 17758)
@@ -238,5 +238,5 @@
 
       // prepare an EStat_IdList to buid a list of unique Id
-      $idList=new EStat_IdList(array('catId', 'imageId', 'tagsId', 'userId', 'IPadress'));
+      $idList=new EStat_IdList(array('catId', 'imageId', 'tagsId', 'userId'));
       // $idAssoc will get the label associated to the id
       $idAssoc=array(
@@ -244,6 +244,5 @@
         'imageId'=>array(),
         'tagsId'=>array(),
-        'userId'=>array(),
-        'IPadress'=>array()
+        'userId'=>array()
       );
 
@@ -256,6 +255,5 @@
             'imageId' => $row['imageId'],
             'tagsId' => $row['tagsId'],
-            'userId' => $row['userId'],
-            'IPadress' => $row['IPadress']
+            'userId' => $row['userId']
           )
         );
@@ -274,8 +272,4 @@
       if(count($idList->getItems('userId')) > 0)
         $this->prepareIdList($idAssoc, 'userId', "SELECT id, username AS name FROM ".USERS_TABLE." WHERE id IN (".implode(',', $idList->getItems('userId')).") ORDER BY id;");
-
-      // for reverse DNS
-      if($this->config['logs.ipCountry']=='y')
-        $this->getIpInfos($idAssoc, $idList->getItems('IPadress'));
 
       $data=array();
@@ -283,6 +277,4 @@
       foreach($result as $row)
       {
-        $ipInfo=$this->getId($idAssoc, 'IPadress', $row['IPadress'], '?');
-
         $userAgentNfo=GPCUserAgent::getProperties(
             array(
@@ -298,5 +290,5 @@
         if($userAgentNfo[UA_DATA_OS_NAME]=='Unknown') $userAgentNfo[UA_DATA_OS_NAME]='ua_Unknown';
 
-        $country=l10n('estat_country_'.strtoupper($ipInfo['country']));
+        $country=l10n('estat_country_'.strtoupper($row['country']));
         if(isset($lang[$country]))
         {
@@ -314,5 +306,5 @@
           'UserId' => $row['userId'],
           'UserName' => $this->getId($idAssoc, 'userId', $row['userId'], '?', 'name'),
-          'countryId' => $ipInfo['country'],
+          'countryId' => $row['country'],
           'country' => $country,
           'catId' => $row['catId'],
@@ -345,8 +337,8 @@
           $export->setOptions(
               array(
-                'separator' => ';',
-                'decimalDot' => ',',
-                'useQuotes' => true,
-                'lineFeed' => 'unix'
+                'separator' => $this->config['export.csv.separator'],
+                'decimalDot' => $this->config['export.csv.decimalDot'],
+                'useQuotes' => ($this->config['export.csv.useQuotes']=='y')?true:false,
+                'lineFeed' => $this->config['export.csv.lineFeed'],
               )
             );
@@ -386,7 +378,7 @@
               array(
                 'sheetName' => l10n('estat_history'),
-                'fileTitle' => '',
+                'fileTitle' => $this->config['export.ods.fileTitle'],
                 'fileSubject' => sprintf(l10n('estat_fileODSSubject'), l10n('estat_history')),
-                'keywords' => '',
+                'keywords' => $this->config['export.ods.keywords'],
                 'comments' => sprintf(l10n('estat_fileODSComments_1'), date('Y-m-d@H:i:s'), ESTAT_VERSION, PHPWG_VERSION)
               )
@@ -460,9 +452,9 @@
       }
       $export->setFileName($name.$extension);
-      $export->setFileDir(GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY);
+      $export->setFileDir($this->fileExportDir);
 
       if($export->write($data))
       {
-        $file=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY.$export->getFileName();
+        $file=$this->fileExportDir.$export->getFileName();
 
         if($compression!='none')
@@ -623,8 +615,8 @@
           $export->setOptions(
               array(
-                'separator' => ';',
-                'decimalDot' => ',',
-                'useQuotes' => true,
-                'lineFeed' => 'unix'
+                'separator' => $this->config['export.csv.separator'],
+                'decimalDot' => $this->config['export.csv.decimalDot'],
+                'useQuotes' => ($this->config['export.csv.useQuotes']=='y')?true:false,
+                'lineFeed' => $this->config['export.csv.lineFeed'],
               )
             );
@@ -646,7 +638,7 @@
               array(
                 'sheetName' => l10n('estat_ipAdress'),
-                'fileTitle' => '',
+                'fileTitle' => $this->config['export.ods.fileTitle'],
                 'fileSubject' => sprintf(l10n('estat_fileODSSubject'), l10n('estat_by_ip')),
-                'keywords' => '',
+                'keywords' => $this->config['export.ods.keywords'],
                 'comments' => sprintf(l10n('estat_fileODSComments_1'), date('Y-m-d@H:i:s'), ESTAT_VERSION, PHPWG_VERSION)
               )
@@ -668,5 +660,5 @@
           $export->setOptions(
               array(
-                'tableName' => 'history'
+                'tableName' => 'ipAdress'
               )
             );
@@ -684,9 +676,9 @@
       }
       $export->setFileName($name.$extension);
-      $export->setFileDir(GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY);
+      $export->setFileDir($this->fileExportDir);
 
       if($export->write($data))
       {
-        $file=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY.$export->getFileName();
+        $file=$this->fileExportDir.$export->getFileName();
 
         if($compression!='none')
@@ -852,8 +844,8 @@
           $export->setOptions(
               array(
-                'separator' => ';',
-                'decimalDot' => ',',
-                'useQuotes' => true,
-                'lineFeed' => 'unix'
+                'separator' => $this->config['export.csv.separator'],
+                'decimalDot' => $this->config['export.csv.decimalDot'],
+                'useQuotes' => ($this->config['export.csv.useQuotes']=='y')?true:false,
+                'lineFeed' => $this->config['export.csv.lineFeed'],
               )
             );
@@ -873,8 +865,8 @@
           $export->setOptions(
               array(
-                'sheetName' => l10n('estat_ipAdress'),
-                'fileTitle' => '',
-                'fileSubject' => sprintf(l10n('estat_fileODSSubject'), l10n('estat_by_ip')),
-                'keywords' => '',
+                'sheetName' => l10n('estat_category'),
+                'fileTitle' => $this->config['export.ods.fileTitle'],
+                'fileSubject' => sprintf(l10n('estat_fileODSSubject'), l10n('estat_by_category')),
+                'keywords' => $this->config['export.ods.keywords'],
                 'comments' => sprintf(l10n('estat_fileODSComments_1'), date('Y-m-d@H:i:s'), ESTAT_VERSION, PHPWG_VERSION)
               )
@@ -895,5 +887,5 @@
           $export->setOptions(
               array(
-                'tableName' => 'history'
+                'tableName' => 'album'
               )
             );
@@ -910,9 +902,9 @@
       }
       $export->setFileName($name.$extension);
-      $export->setFileDir(GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY);
+      $export->setFileDir($this->fileExportDir);
 
       if($export->write($data))
       {
-        $file=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY.$export->getFileName();
+        $file=$this->fileExportDir.$export->getFileName();
 
         if($compression!='none')
@@ -1090,8 +1082,8 @@
           $export->setOptions(
               array(
-                'separator' => ';',
-                'decimalDot' => ',',
-                'useQuotes' => true,
-                'lineFeed' => 'unix'
+                'separator' => $this->config['export.csv.separator'],
+                'decimalDot' => $this->config['export.csv.decimalDot'],
+                'useQuotes' => ($this->config['export.csv.useQuotes']=='y')?true:false,
+                'lineFeed' => $this->config['export.csv.lineFeed'],
               )
             );
@@ -1113,8 +1105,8 @@
           $export->setOptions(
               array(
-                'sheetName' => l10n('estat_ipAdress'),
-                'fileTitle' => '',
-                'fileSubject' => sprintf(l10n('estat_fileODSSubject'), l10n('estat_by_ip')),
-                'keywords' => '',
+                'sheetName' => l10n('estat_image'),
+                'fileTitle' => $this->config['export.ods.fileTitle'],
+                'fileSubject' => sprintf(l10n('estat_fileODSSubject'), l10n('estat_by_image')),
+                'keywords' => $this->config['export.ods.keywords'],
                 'comments' => sprintf(l10n('estat_fileODSComments_1'), date('Y-m-d@H:i:s'), ESTAT_VERSION, PHPWG_VERSION)
               )
@@ -1137,5 +1129,5 @@
           $export->setOptions(
               array(
-                'tableName' => 'history'
+                'tableName' => 'image'
               )
             );
@@ -1154,9 +1146,9 @@
       }
       $export->setFileName($name.$extension);
-      $export->setFileDir(GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY);
+      $export->setFileDir($this->fileExportDir);
 
       if($export->write($data))
       {
-        $file=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY.$export->getFileName();
+        $file=$this->fileExportDir.$export->getFileName();
 
         if($compression!='none')
@@ -1185,5 +1177,5 @@
     private function ajax_estat_export_admin_loadFile($name, $deleteFile=true)
     {
-      $file=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::EXPORT_DIRECTORY.$name;
+      $file=$this->fileExportDir.$name;
       if(!file_exists($file))
         do_error(404, "Requested file not readable - $name");
Index: extensions/EStat/estat_install.class.inc.php
===================================================================
--- extensions/EStat/estat_install.class.inc.php	(revision 17737)
+++ extensions/EStat/estat_install.class.inc.php	(revision 17758)
@@ -26,7 +26,5 @@
     public function install()
     {
-      if(!file_exists($this->fileStatDir))
-          mkdir($this->fileStatDir, 0755, true);
-
+      $this->checkDirectories();
       $this->initConfig();
       $this->loadConfig();
@@ -51,5 +49,5 @@
     public function uninstall()
     {
-      //GPCCore::rmDir(dirname(GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::DATA_DIRECTORY));
+      //GPCCore::rmDir(dirname($this->fileStatDir));
       $this->deleteConfig();
       GPCCore::unregister($this->getPluginName());
@@ -59,4 +57,5 @@
     public function activate()
     {
+      $this->checkDirectories();
       $this->initConfig();
       $this->loadConfig();
@@ -70,6 +69,6 @@
       switch($this->config['installed'])
       {
-        case '01.00.00':
-          //$this->updateFrom_010000();
+        case '00.01.00b':
+          //$this->updateFrom_000100b();
         default:
           // nothing to do...
@@ -90,10 +89,10 @@
 
     /**
-     * update from release 1.0.0
+     * update from release 0.1.0b
      *
      *  -- dummy --
      *
      */
-    private function updateFrom_010000()
+    private function updateFrom_000100b()
     {
       return(false);
@@ -123,4 +122,30 @@
     }
 
+    /**
+     * create the local directories & copy index.php files
+     *
+     */
+    private function checkDirectories()
+    {
+      if(!file_exists($this->fileStatDir))
+          mkdir($this->fileStatDir, 0755, true);
+
+      if(!file_exists($this->fileExportDir))
+          mkdir($this->fileExportDir, 0755, true);
+
+
+      if(!file_exists($this->fileStatDir.'/index.php'))
+        copy(ESTAT_PATH.'index.php', $this->fileStatDir.'/index.php');
+
+      if(!file_exists(dirname($this->fileStatDir).'/index.php'))
+        copy(ESTAT_PATH.'index.php', dirname($this->fileStatDir).'/index.php');
+
+      if(!file_exists($this->fileExportDir.'/index.php'))
+        copy(ESTAT_PATH.'index.php', $this->fileExportDir.'/index.php');
+
+      if(!file_exists(dirname($this->fileExportDir).'/index.php'))
+        copy(ESTAT_PATH.'index.php', dirname($this->fileExportDir).'/index.php');
+    }
+
   } //class
 
Index: extensions/EStat/estat_root.class.inc.php
===================================================================
--- extensions/EStat/estat_root.class.inc.php	(revision 17737)
+++ extensions/EStat/estat_root.class.inc.php	(revision 17758)
@@ -57,4 +57,5 @@
 
     protected $fileStatDir='';
+    protected $fileExportDir='';
 
     public function __construct($prefixeTable, $filelocation)
@@ -63,6 +64,7 @@
       $this->setPluginNameFiles("estat");
       parent::__construct($prefixeTable, $filelocation);
+      $this->loadConfig();
       $this->section_name=$this->getPluginNameFiles();
-      $this->fileStatDir=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::DATA_DIRECTORY;
+      $this->setFileStatDir();
     }
 
@@ -72,10 +74,32 @@
     public function initConfig()
     {
-      //global $user;
+      global $user;
+
+      $lang='';
+      if(isset($user['language'])) $lang=$user['language'];
+
+      switch($lang)
+      {
+        case 'fr_FR':
+          $csvExport=array(
+            'export.csv.separator' => ';',
+            'export.csv.decimalDot' => ','
+          );
+          break;
+        default:
+          $csvExport=array(
+            'export.csv.separator' => ',',
+            'export.csv.decimalDot' => '.'
+          );
+          break;
+      }
+
       $this->config=array(
+        'installed' => '00.00.00',
         'plugin.newInstall' => 'y',
         'plugin.active' => 'n',
         'plugin.historyImported' => 'n',
         'plugin.ipCountryBuild' => 'n',
+        'plugin.pathKey' => '',          // key to be used to know path where are stored the SQLite files
 
         'build.delay' => '+24hours',     // delay between 2 consolidation
@@ -86,4 +110,11 @@
 
         'global.itemPerPage' => 250,
+
+        'export.csv.separator' => $csvExport['export.csv.separator'],
+        'export.csv.decimalDot' => $csvExport['export.csv.decimalDot'],
+        'export.csv.useQuotes' => 'y',
+        'export.csv.lineFeed' => 'unix',
+        'export.ods.fileTitle' => '',
+        'export.ods.keywords' => '',
 
         'logs.reverseDNS' => 'n',
@@ -233,4 +264,87 @@
 
 
+
+
+
+    /**
+     * set the value for $fileStatDir & $fileExportDir
+     * $rootPath is the root path where are stored the SQLite data files
+     *  example: (...)/local/plugins/EStat/Data/
+     *
+     * This path is known is public and can be found by everyone. To forbid the
+     * download of files stored in this directory, a random key is added in the
+     * path
+     *  example: (...)/local/plugins/EStat/Data-ABCDEF0123456789/
+     *
+     * The random key is stored in config file:
+     *  . if key is not set, try to find the directory
+     *  . if key is set but directory doesn't exist, try to find the directory
+     *  . if there's no key and no directory found, the key is created and saved
+     *
+     * the $fileStatDir provide the complete path with the key
+     * the $fileExportDir path gets the same key than $fileStatDir
+     *
+     * @return String: $fileStatDir value
+     */
+    protected function setFileStatDir()
+    {
+      $rootPath=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.self::DATA_DIRECTORY;
+
+      $path=substr($rootPath,0,-1).'-'.$this->config['plugin.pathKey'];
+      if(file_exists($path) and $this->config['plugin.pathKey']!='')
+      {
+        $this->fileStatDir=$path;
+        $this->fileExportDir=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.substr(self::EXPORT_DIRECTORY,0,-1).'-'.$this->config['plugin.pathKey'].'/';
+        return($this->fileStatDir);
+      }
+
+      // key is empty or directory doesn't exist, try to find the diretory
+      $path=$this->findDirectory(substr($rootPath,0,-1).'-');
+      if(file_exists($path))
+      {
+        $this->fileStatDir=$path;
+        $path=explode('-', basename($path)); // /xxx/xxx/xxx/data-abcdef012 => array('data', 'abcdef012')
+        $this->config['plugin.pathKey']=$path[1];
+        $this->saveConfig();
+        $this->fileExportDir=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.substr(self::EXPORT_DIRECTORY,0,-1).'-'.$this->config['plugin.pathKey'].'/';
+        return($this->fileStatDir);
+      }
+
+      // no directory found, create the key
+      $this->config['plugin.pathKey']=strtoupper(md5(date('Ymd-His').rand(0,65536)));
+      $this->saveConfig();
+      $this->fileStatDir=substr($rootPath,0,-1).'-'.$this->config['plugin.pathKey'];
+      $this->fileExportDir=GPCCore::getPiwigoSystemPath().'/'.PWG_LOCAL_DIR.substr(self::EXPORT_DIRECTORY,0,-1).'-'.$this->config['plugin.pathKey'].'/';
+      return($this->fileStatDir);
+    }
+
+    /**
+     * try to find a directory in the $path
+     *  exemple:
+     *     $path = /local/plugins/EStat/Data-
+     *     => try to find all directory macthing with /local/plugins/EStat/Data-*
+     *
+     * @param String $path:
+     * @return String: found path or empty string if nothing is found
+     */
+    private function findDirectory($path)
+    {
+      $dirname=dirname($path);
+      if(!file_exists($dirname)) return('');
+      $baseName=baseName($path);
+      $dirContent=scandir($dirname);
+      foreach($dirContent as $file)
+      {
+        if($file!='.' and $file!='..' and is_dir($dirname.'/'.$file) and strpos($file, $baseName)!==false)
+        {
+          return($dirname.'/'.$file);
+        }
+      }
+      return('');
+    }
+
+
+
+
     /*
      * -------------------------------------------------------------------------
@@ -357,8 +471,6 @@
     }
 
-
-
-
   } //class
+
 
 
Index: extensions/EStat/js/estatPM.js
===================================================================
--- extensions/EStat/js/estatPM.js	(revision 17737)
+++ extensions/EStat/js/estatPM.js	(revision 17758)
@@ -600,6 +600,6 @@
                           regexp:'\d{1,3}(?:\.\d{1,3}){0,3}'
                         },
-                      defaultOperator:'=',
-                      filterOperators:['=', '!=', 'between']
+                      defaultOperator:'like',
+                      filterOperators:['like', 'not like', 'between', 'not between']
                     },
                   group:true,
@@ -665,6 +665,6 @@
             footerString:
               {
-                singular:translateString.ip_nb,
-                plural:translateString.ips_nb,
+                singular:translateString.item_nb,
+                plural:translateString.items_nb,
                 waitForDownload:translateString.waitForDownload
               },
@@ -939,6 +939,6 @@
             footerString:
               {
-                singular:translateString.category_nb,
-                plural:translateString.categories_nb,
+                singular:translateString.item_nb,
+                plural:translateString.items_nb,
                 waitForDownload:translateString.waitForDownload
               },
@@ -1110,6 +1110,6 @@
             footerString:
               {
-                singular:translateString.image_nb,
-                plural:translateString.images_nb,
+                singular:translateString.item_nb,
+                plural:translateString.items_nb,
                 waitForDownload:translateString.waitForDownload
               },
@@ -1252,5 +1252,5 @@
                         },
                       defaultOperator:'between',
-                      filterOperators:['>', '<', '>=', '<=', '=', '!=', 'between']
+                      filterOperators:['>', '<', '>=', '<=', '=', '!=', 'between', 'not between']
                     },
                   sortable:'asc',
@@ -1267,6 +1267,6 @@
                           regexp:'\d{1,3}(?:\.\d{1,3}){0,3}'
                         },
-                      defaultOperator:'=',
-                      filterOperators:['=', '!=', 'between']
+                      defaultOperator:'like',
+                      filterOperators:['like', 'not like', 'between', 'not between']
                     },
                   sortable:'asc',
@@ -1412,5 +1412,4 @@
             saveButtonClick: function (event, data)
               {
-                $('#iHistoryTable').dynamicTable('waitForDownload', true);
                 $.dialogExport(
                   {
Index: extensions/EStat/language/en_UK/plugin.lang.php
===================================================================
--- extensions/EStat/language/en_UK/plugin.lang.php	(revision 17737)
+++ extensions/EStat/language/en_UK/plugin.lang.php	(revision 17758)
@@ -89,16 +89,10 @@
 $lang['estat_allPeriods']='All periods';
 
-
-$lang['estat_item_nb']='% event';
-$lang['estat_items_nb']='% events';
-$lang['estat_ip_nb']='% IP adress';
-$lang['estat_ips_nb']='% IP adresses';
-$lang['estat_category_nb']='% album';
-$lang['estat_categories_nb']='% albums';
-$lang['estat_image_nb']='% photo';
-$lang['estat_images_nb']='% photos';
+$lang['estat_item_nb']='% item';
+$lang['estat_items_nb']='% items';
+
 $lang['estat_country_nb']='%s country';
 $lang['estat_countries_nb']='%s countries';
-$lang['estat_items']='Events';
+
 $lang['estat_ok']='Ok';
 $lang['estat_cancel']='Cancel';
Index: extensions/EStat/language/fr_FR/plugin.lang.php
===================================================================
--- extensions/EStat/language/fr_FR/plugin.lang.php	(revision 17737)
+++ extensions/EStat/language/fr_FR/plugin.lang.php	(revision 17758)
@@ -89,16 +89,11 @@
 $lang['estat_allPeriods']='Toutes périodes';
 
-
-$lang['estat_item_nb']='% événement';
-$lang['estat_items_nb']='% événements';
-$lang['estat_ip_nb']='% adresse IP';
-$lang['estat_ips_nb']='% adresses IP';
-$lang['estat_category_nb']='% album';
-$lang['estat_categories_nb']='% albums';
-$lang['estat_image_nb']='% photo';
-$lang['estat_images_nb']='% photos';
+$lang['estat_item_nb']='% élément';
+$lang['estat_items_nb']='% éléments';
+
 $lang['estat_country_nb']='%s pays';
 $lang['estat_countries_nb']='%s pays';
-$lang['estat_items']='Evénements';
+
+
 $lang['estat_ok']='Ok';
 $lang['estat_cancel']='Annuler';
Index: extensions/EStat/lib/statDB.class.inc.php
===================================================================
--- extensions/EStat/lib/statDB.class.inc.php	(revision 17737)
+++ extensions/EStat/lib/statDB.class.inc.php	(revision 17758)
@@ -193,9 +193,10 @@
    *
    * @param String $file
+   * @param Boolean $force: if true, $force the filename affectation even if file doesn't exist
    * @return String: file name set
    */
-  public function setIpCountryFile($file)
-  {
-    if(file_exists($file))
+  public function setIpCountryFile($file, $force=false)
+  {
+    if($force or file_exists($file))
       $this->ipCountryFile=$file;
     return($this->ipCountryFile);
@@ -728,4 +729,5 @@
             break;
           case 'between':
+          case 'not between':
             if($field=='IPadress')
             {
@@ -737,5 +739,5 @@
               $num++;
             }
-            $where[]=' '.$field." BETWEEN ".$param['minValue']." AND ".$param['maxValue']." ";
+            $where[]=' '.$field.' '.strtoupper($param['operator']).' '.$param['minValue']." AND ".$param['maxValue']." ";
             break;
         }
@@ -824,4 +826,41 @@
   }
 
+
+  /**
+   * to be used with IP filter: replace the given operator with 'between' or 'not between'
+   *
+   * @param Array $value
+   * @return Array
+   */
+  protected function checkIPFilter($value)
+  {
+    if(is_array($value))
+    {
+      if(isset($value['operator']))
+      {
+        if($value['operator']=='=' or
+           $value['operator']=='>' or
+           $value['operator']=='>=' or
+           $value['operator']=='<' or
+           $value['operator']=='<=' or
+           $value['operator']=='like')
+        {
+          $value['operator']='between';
+          $value['minValue']=$value['value'];
+          $value['maxValue']=$value['value'];
+          $value['value']=null;
+        }
+        elseif($value['operator']=='!=' or
+               $value['operator']=='not like')
+        {
+          $value['operator']='not between';
+          $value['minValue']=$value['value'];
+          $value['maxValue']=$value['value'];
+          $value['value']=null;
+        }
+      }
+    }
+    return($value);
+  } // checkIPFilter
 
 
@@ -856,4 +895,6 @@
           case '<=':
           case '!=':
+          case 'like':
+          case 'not like':
             if(isset($value['value']))
             {
@@ -866,5 +907,6 @@
                   break;
                 case 'IP':
-                  $value['value']=$this->checkValueIP($value['value']);
+                  // for IP, replace internaly the operator with a 'between'
+                  $value['value']=$this->checkValueIP($value['value'], 0);
                   if($value['value']==null) return(null);
                   break;
@@ -893,5 +935,5 @@
                     break;
                   case 'IP':
-                    $val=$this->checkValueIP($val);
+                    $val=$this->checkValueIP($val, 0);
                     if($val!=null) $returned[]=$val;
                     break;
@@ -910,4 +952,5 @@
             break;
           case 'between':
+          case 'not between':
             if(isset($value['minValue']) and  isset($value['maxValue']))
             {
@@ -921,7 +964,7 @@
                   break;
                 case 'IP':
-                  $value['minValue']=$this->checkValueIP($value['minValue']);
+                  $value['minValue']=$this->checkValueIP($value['minValue'], 0);
                   if($value['minValue']==null) return(null);
-                  $value['maxValue']=$this->checkValueIP($value['maxValue']);
+                  $value['maxValue']=$this->checkValueIP($value['maxValue'], 255);
                   if($value['maxValue']==null) return(null);
                   break;
@@ -981,9 +1024,10 @@
    *
    * @param String $value
+   * @param Integer $completeCode
    * @return String
    */
-  protected function checkValueIP($value)
-  {
-    $returned=self::IPBinaryEncode($value);
+  protected function checkValueIP($value, $completeCode=0)
+  {
+    $returned=self::IPBinaryEncode($value, $completeCode);
     if($returned=="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") return(null);
     return($returned);
@@ -1000,13 +1044,16 @@
    *
    * @param String $IP: text IP string
+   * @param Integer $completeCode
    * @return String: a binary string
    */
-  static public function IPBinaryEncode($IP)
+  static public function IPBinaryEncode($IP, $completeCode=0)
   {
     $value=trim($IP);
-    if(preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/i',$value)>0)
+    if(preg_match('/\d{1,3}(\.\d{1,3}(\.\d{1,3}(\.\d{1,3})?)?)?/i',$value)>0)
     {
       //IPv4
       $tmp=explode('.',$value);
+      while(count($tmp)<4) $tmp[]=$completeCode;
+
       return("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".chr($tmp[0]+0).chr($tmp[1]+0).chr($tmp[2]+0).chr($tmp[3]+0));
     }
@@ -1088,7 +1135,21 @@
 
 
-
-
-
+/*
+ used to display binary values when debug is necessary...
+
+function toHexDump($data)
+  {
+    $returned="";
+    $maxItems=strlen($data);
+
+    $tmp=array();
+    for($i=0;$i<$maxItems;$i++)
+    {
+      $tmp[]=sprintf("%02x", ord($data{$i}));
+    }
+    $returned.=implode(" ", $tmp);
+    return($returned);
+  }
+*/
 
 
Index: extensions/EStat/lib/statDBGlobal.class.inc.php
===================================================================
--- extensions/EStat/lib/statDBGlobal.class.inc.php	(revision 17737)
+++ extensions/EStat/lib/statDBGlobal.class.inc.php	(revision 17758)
@@ -223,5 +223,5 @@
             SELECT uaType, IPadress, catId, $year, $month, SUM(visits), country
             FROM period.statIP
-            GROUP BY uaType, IPadress, catId;
+            GROUP BY uaType, IPadress, catId, country;
 
         REPLACE INTO main.statUA (uaData, uaValue, uaVersion, year, month, visits)
@@ -486,4 +486,5 @@
     if(!isset($filter['country'])) $filter['country']=null;
 
+    $filter['IPadress']=$this->checkIPFilter($filter['IPadress']);
     // check filter values - getOperator check and 'clean' the filter
     $filter['IPadress']=$this->getOperator($filter['IPadress'], 'IP');
@@ -543,5 +544,4 @@
       case ASDF_GET_ROWS:
           $sql=$select." FROM statip ".$where.$groupBy.$orderBy.$limit;
-
           $sqlStm=$this->dbHandle->prepare($sql);
           foreach($IPList as $num=>$IP)
@@ -1245,5 +1245,5 @@
             );
           $this->dbHandle->exec("CREATE UNIQUE INDEX iIP ON statIP ('uaType', 'IPadress', 'catId', 'year', 'month');");
-          $this->dbHandle->exec("CREATE UNIQUE INDEX iIPPeriod ON statIP ('year', 'month', 'IPadress', 'uaType');");
+          $this->dbHandle->exec("CREATE INDEX iIPPeriod ON statIP ('year', 'month', 'IPadress', 'uaType');");
         }
 
Index: extensions/EStat/lib/statDBMonth.class.inc.php
===================================================================
--- extensions/EStat/lib/statDBMonth.class.inc.php	(revision 17737)
+++ extensions/EStat/lib/statDBMonth.class.inc.php	(revision 17758)
@@ -78,4 +78,6 @@
    *
    * missing values are set to default value
+   * country is defined automatically after buildIPCountry call; set to '' while
+   * call is not made
    *
    * @param Array $log : a record
@@ -109,5 +111,5 @@
       // insert log
       $sql="INSERT INTO logs
-            (date, IPadress, userId, catId, imageId, tagsId, section,
+            (date, IPadress, country, userId, catId, imageId, tagsId, section,
              userAgent, uaBrowser, uaBrowserVersion, uaEngine, uaEngineVersion,
              uaOS, uaOSVersion, uaType, screenSizeW, screenSizeH)
@@ -115,4 +117,5 @@
                       //"'".$this->dbHandle->escapeString($value['IPadress'])."', ".
                       ":IP, ".
+                      "'', ".
                       $value['userId'].", ".
                       $value['catId'].", ".
@@ -254,4 +257,5 @@
     if(!isset($filter['uaType'])) $filter['uaType']=null;
 
+    $filter['IPadress']=$this->checkIPFilter($filter['IPadress']);
     // check filter values - getOperator check and 'clean' the filter
     $filter['date']=$this->getOperator($filter['date'], 'date');
@@ -296,4 +300,17 @@
         array('id'=>'id', 'direction'=>'A')
       );
+    // to optimize query, if date is the first field selected for order by,
+    // all other fields are removed; exception: if IPadress is the second one this
+    // field is kept
+    $tmp=$orderBy;
+    if($tmp[0]['id']=='date')
+    {
+      $orderBy=array($tmp[0]);
+      if($tmp[1]['id']=='IPadress')
+      {
+        $orderBy[]=$tmp[1];
+      }
+    }
+
     //build ORDER BY clause
     $orderBy=$this->buildOrderByClause($orderBy);
@@ -310,8 +327,9 @@
     {
       case ASDF_GET_ROWS:
-          $sql="SELECT id, date, IPadress, userId, catId, imageId, tagsId, section,
+          $sql="SELECT id, date, IPadress, country, userId, catId, imageId, tagsId, section,
                        userAgent, uaBrowser, uaBrowserVersion, uaEngine, uaEngineVersion,
                        uaOS, uaOSVersion, uaType, screenSizeW, screenSizeH
                 FROM logs $where $orderBy $limit;";
+
           $sqlStm=$this->dbHandle->prepare($sql);
           foreach($IPList as $num=>$IP)
@@ -576,4 +594,5 @@
     if(!isset($filter['country'])) $filter['country']=null;
 
+    $filter['IPadress']=$this->checkIPFilter($filter['IPadress']);
     // check filter values - getOperator check and 'clean' the filter
     $filter['IPadress']=$this->getOperator($filter['IPadress'], 'IP');
@@ -1248,4 +1267,10 @@
         DROP TABLE IF EXISTS tmpIp;
 
+        -- update the logs table for rows with unset country
+        -- use a simple sub select here, assuming that it's quick enough for the update
+        UPDATE logs
+        SET country=(SELECT sip.country FROM statip sip WHERE logs.IPadress = sip.IPadress)
+        WHERE country='';
+
         -- commit changes
         COMMIT TRANSACTION;
@@ -1285,4 +1310,5 @@
                   'date' INTEGER,
                   'IPadress' BLOB,
+                  'country' TEXT,
                   'userId' INTEGER,
                   'catId' INTEGER,
@@ -1302,6 +1328,8 @@
               );"
             );
-          $this->dbHandle->exec("CREATE INDEX iDate ON logs ('date');");
-          $this->dbHandle->exec("CREATE INDEX iByIP ON logs ('IPadress');");
+          $this->dbHandle->exec("CREATE INDEX iDateIP ON logs ('date', 'IPadress');");
+          $this->dbHandle->exec("CREATE INDEX iIPCountry ON logs ('IPadress', 'country');");
+          $this->dbHandle->exec("CREATE INDEX iUAB ON logs ('uaType', 'uaBrowser', 'uaEngine');");
+          $this->dbHandle->exec("CREATE INDEX iUAO ON logs ('uaOS');");
         }
 
