source: extensions/EStat/lib/statDBCountry.class.inc.php

Last change on this file was 17737, checked in by grum, 12 years ago

First commit for EStat files

  • Property svn:executable set to *
File size: 8.7 KB
Line 
1<?php
2
3require_once('statDB.class.inc.php');
4
5/**
6 * specific class used to build SQLite country database managment from a binary
7 * encoded file
8 *
9 * binary file format is:
10 *  - header signature:  4 bytes = 'IPv4' or 'IPv6'
11 *  - file version:      2 bytes = integer value, file version
12 *  - number of records: 4 bytes = integer value
13 *  - records:           10 bytes if 'IPv4'
14 *                       34 bytes if 'IPv6'
15 *
16 *  a record is range of IP and a country code
17 *  - begin IP range: 4 bytes if 'IPv4', 16 bytes if 'IPv6'
18 *  - end IP range: 4 bytes if 'IPv4', 16 bytes if 'IPv6'
19 *  - country code: 2 bytes (ISO 3166 - http://www.iso.org/iso/country_names_and_code_elements)
20 */
21class StatDBCountry extends StatDB
22{
23  const HEADER_SIGNATURE_SIZE=4;
24  const VERSION_SIZE=2;
25  const NBRECORDS_SIZE=4;
26  const HEADER_SIZE=10;
27  const COUNTRY_SIZE=2;
28
29  /**
30   * constructor
31   *
32   * @param String $directory: directory where the sqlite file is saved
33   * @param String $fileName: file name (without extension)
34   */
35  public function __construct($directory='', $fileName='')
36  {
37    parent::__construct($directory, $fileName);
38  }
39
40
41  /**
42   * return country for the given IP list
43   *
44   * @param String|Array $ipList: array of IP or a unique IP
45   * @param Boolean $binary: if true (default value), IP must be given as binary
46   * @return String|Array: associative array of IP=>country, or a country (depends of $ipList)
47   *                        IP format returned is accorded with the $binary value
48   */
49  public function getIpCountry($ipList, $binary=true)
50  {
51    if(is_array($ipList))
52    {
53      $returned=array();
54
55      // encode IP in binary format if needed
56      if(!$binary)
57      {
58        foreach($ipList as $key=>$IP)
59        {
60          $ipList[self::IPBinaryEncode($IP)]='--';
61        }
62      }
63    }
64    else
65    {
66      $returned='--';
67
68      if(!$binary)
69        $ipList=self::IPBinaryEncode($ipList);
70
71      // build a list with a unique item...
72      $ipList=array($ipList=>'--');
73    }
74
75    if($this->dbHandle==null) return($returned);
76
77    foreach($ipList as $IP=>$val)
78    {
79      $sql="SELECT country
80            FROM ipList
81            WHERE :ip BETWEEN rangeStart AND rangeEnd;";
82
83      $sp=$this->dbHandle->prepare($sql);
84      $sp->bindValue(':ip', $IP, SQLITE3_BLOB);
85      $result=$sp->execute();
86
87      if($result)
88      {
89        if(!$binary) $IP=self::IPBinaryDecode($IP);
90        while($row=$result->fetchArray(SQLITE3_ASSOC))
91        {
92          if(is_array($returned))
93          {
94            $returned[$IP]=$row['country'];
95          }
96          else
97          {
98            $returned=$row['country'];
99          }
100        }
101      }
102    }
103
104    return($returned);
105  }
106
107
108  /**
109   * return an array with the following info:
110   *  'version' => (integer) the version of the binary file
111   *  'nbIP' => (integer) the number of IP range in the binary file
112   * return a negative value for file version if an error occurs
113   *
114   * @param String $fileName
115   * @return Array
116   */
117  public function getIpFileInfo($fileName)
118  {
119    $returned=array(
120        'fileVersion' => -4,
121        'nbRecords'   => 0
122      );
123
124
125    if(!file_exists($fileName))
126    {
127      $returned['fileVersion']=-2;
128      return($returned);
129    }
130
131    $fHandle=fopen($fileName, 'r');
132    if($fHandle)
133    {
134      $ipFormat=fread($fHandle, self::HEADER_SIGNATURE_SIZE);
135      switch($ipFormat)
136      {
137        case 'IPv4':
138        case 'IPv6':
139          $fileVersion=self::toUShort(fread($fHandle, self::VERSION_SIZE));
140          $nbRecords=self::toULong(fread($fHandle, self::NBRECORDS_SIZE));
141          break;
142        default:
143          $fileVersion=-3;
144          break;
145      }
146      fclose($fHandle);
147    }
148    return(
149      array(
150        'fileVersion' => $fileVersion,
151        'nbRecords'   => $nbRecords
152      )
153    );
154  }
155
156
157  /**
158   * load a binary file into the database
159   *
160   * @param String $fileName: file data to load in the database
161   * @param Integer $start: offset from loading starts
162   * @param Integer $numIp: number of IP to load (0=all)
163   * @return Integer: negative: error
164   *                  positive: number of IP loaded
165   */
166  public function loadIpFile($fileName, $start=0, $numIp=0)
167  {
168    $recordSize=0;
169    $IPSize=0;
170    $truncateTable=($start==0 or $numIp==0);
171    $completeIP='';
172
173    if($this->dbHandle==null) return(-1);
174    if(!file_exists($fileName)) return(-2);
175
176    $fHandle=fopen($fileName, 'r');
177    if($fHandle)
178    {
179      // check record size from IP binary format
180      $ipFormat=fread($fHandle, self::HEADER_SIGNATURE_SIZE);
181      switch($ipFormat)
182      {
183        case 'IPv4':
184          $IPSize=4;
185          $completeIP="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
186          break;
187        case 'IPv6':
188          $IPSize=16;
189          break;
190        default:
191          fclose($fHandle);
192          return(-3);
193          break;
194      }
195
196      $fileVersion=self::toUShort(fread($fHandle, self::VERSION_SIZE));
197
198      $recordSize=2*$IPSize+self::COUNTRY_SIZE;
199
200
201      // calculate start reading position & buffer size
202      if($numIp<=0)
203      {
204        $bufferSize=filesize($fileName)-self::HEADER_SIZE;
205      }
206      else
207      {
208        $bufferSize=$numIp*$recordSize;
209      }
210
211
212
213
214
215      $start=self::HEADER_SIZE+$start*$recordSize;
216
217
218      fseek($fHandle, $start);
219      $buffer=fread($fHandle, $bufferSize);
220      fclose($fHandle);
221
222      $buffer=str_split($buffer, $recordSize);
223
224      //start transaction
225      $this->startTransac();
226
227      if($truncateTable) $this->truncateTable();
228
229      if($this->getInfoProperty('log', 'startImport')==null)
230        $this->setInfoProperty('log', 'startImport', date('Y-m-d H:i:s'));
231
232      $this->setInfoProperty('nfo', 'ipFileVersion', $fileVersion);
233
234      foreach($buffer as $record)
235      {
236        $sql="INSERT INTO ipList (rangeStart, rangeEnd, country)
237                VALUES(:ips, :ipe, '".$this->dbHandle->escapeString(substr($record, -self::COUNTRY_SIZE))."')";
238
239        $sp=$this->dbHandle->prepare($sql);
240        $sp->bindValue(':ips', $completeIP.substr($record, 0, $IPSize), SQLITE3_BLOB);
241        $sp->bindValue(':ipe', $completeIP.substr($record, $IPSize, $IPSize), SQLITE3_BLOB);
242        $sp->execute();
243      }
244
245      if($numIp==0 or count($buffer)<$numIp)
246      {
247        // file is completely loaded !
248        $this->buildInfos();
249        $this->setInfoProperty('log', 'stopImport', date('Y-m-d H:i:s'));
250      }
251      else
252      {
253        $sql="
254          REPLACE INTO info (domain, key, value)
255          SELECT 'nfo', 'numberOfIP', COUNT(*)
256          FROM ipList;
257        ";
258        $this->dbHandle->exec($sql);
259      }
260
261      // commit all changes
262      $this->stopTransac();
263
264      return(count($buffer));
265    }
266
267    return(-4);
268  }
269
270
271  /**
272   * delete all the content of the ipList table
273   *
274   * @return Boolean
275   */
276  protected function truncateTable()
277  {
278    $value=$this->getInfoProperty('nfo', 'numberOfIP').'/'.$this->getInfoProperty('nfo', 'numberOfCountry');
279
280    $sql="
281      INSERT INTO info (domain, key, value)
282        VALUES('log', 'truncate(".date('Y-m-d H:i:s').")', '$value');
283
284      DELETE FROM ipList;
285    ";
286    return($this->dbHandle->exec($sql));
287  }
288
289
290  /**
291   * set the following info in 'nfo' domain:
292   *  - 'numberOfIP'
293   *  - 'numberOfCountry'
294   *  - 'importDate'
295   */
296  protected function buildInfos()
297  {
298    if($this->dbHandle==null) return(false);
299
300    $sql="
301        REPLACE INTO info (domain, key, value)
302          SELECT 'nfo', 'numberOfIP', COUNT(*)
303          FROM ipList;
304
305        REPLACE INTO info (domain, key, value)
306          SELECT 'nfo', 'numberOfCountry', COUNT(DISTINCT country)
307          FROM ipList;
308
309        REPLACE INTO info (domain, key, value)
310          VALUES ('nfo', 'importDate', '".date('Y-m-d')."');
311    ";
312
313    return($this->dbHandle->exec($sql));
314  }
315
316
317  /**
318   * check the database schema; update it if needed
319   *
320   * @return Boolean: true if OK, otherwise false
321   */
322  protected function checkSchema()
323  {
324    if(!parent::checkSchema()) return(false);
325
326    $version=$this->getInfoProperty('nfo', 'version');
327
328    switch($version)
329    {
330      case '00.00.00':
331        // version 00.00.00
332        //  file is just created...
333        $result=$this->getDBInfo(ASDF_DB_TYPE_TABLE, 'files', ASDF_DB_INFO);
334        if(count($result)==0)
335        {
336          $this->dbHandle->exec(
337              "CREATE TABLE 'ipList' (
338                  'rangeStart' BLOB,
339                  'rangeEnd' BLOB,
340                  'country' TEXT
341               );"
342            );
343          $this->dbHandle->exec("CREATE UNIQUE INDEX 'byRange' on iplist (rangeStart ASC)");
344        }
345
346        $this->setInfoProperty('nfo', 'version', '01.00.00');
347        $this->setInfoProperty('nfo', 'ipFileVersion', 0);
348        return(true);
349        break;
350      default:
351        break;
352    }
353    return(false);
354  }
355
356} // class
357
358
359?>
Note: See TracBrowser for help on using the repository browser.