source: extensions/EStat/lib/statDB.class.inc.php @ 32036

Revision 17862, 37.9 KB checked in by grum, 7 years ago (diff)

version 0.1.0b
. fix bug on thumb view
. fix SQLite compression managment & 'compress.method' config
. manage number of items per page by config

  • Property svn:executable set to *
Line 
1<?php
2
3include_once(PHPWG_PLUGINS_PATH.'GrumPluginClasses/classes/GPCCompress.class.inc.php');
4
5define('ASDF_DELETE_PACKED',   0x01);
6define('ASDF_DELETE_UNPACKED', 0x02);
7
8define('ASDF_EXIST_NO',       0x00);
9define('ASDF_EXIST_PACKED',   0x01);
10define('ASDF_EXIST_UNPACKED', 0x02);
11define('ASDF_EXIST_LOCK',     0x04);
12define('ASDF_EXIST_ALL', ASDF_EXIST_PACKED|ASDF_EXIST_UNPACKED);
13
14define('ASDF_FILE_EXT', '.db');
15define('ASDF_FILE_EXT_ZIP', ASDF_FILE_EXT.'.gz');
16define('ASDF_FILE_EXT_LOCK', '.lock');
17define('ASDF_FILE_ROOT_MONTH', 'M');
18define('ASDF_FILE_ROOT_GLOBAL', 'G');
19
20
21define('ASDF_DB_INFO',      0x01);
22define('ASDF_DB_NUM_ROWS',  0x02);
23define('ASDF_DB_LIST',  0x03);
24
25define('ASDF_DB_TYPE_TABLE', 0x01);
26define('ASDF_DB_TYPE_INDEX', 0x02);
27define('ASDF_DB_TYPE_TRIGGER', 0x03);
28define('ASDF_DB_TYPE_LOG', 0x04);
29define('ASDF_DB_TYPE_NFO', 0x05);
30
31define('ASDF_TRANSAC_COMMIT', 0x01);
32define('ASDF_TRANSAC_ROLLBACK', 0x02);
33
34define('ASDF_GET_ROWS', 0x01);
35define('ASDF_GET_COUNT', 0x02);
36
37define('ASDF_BUILD_DONE', 0x01);
38define('ASDF_BUILD_MISSING', 0x02);
39define('ASDF_BUILD_ALL', ASDF_BUILD_DONE|ASDF_BUILD_MISSING);
40
41define('ASDF_OPEN_READ', 0x01);
42define('ASDF_OPEN_WRITE', 0x02);
43
44define('ASDF_CLOSE_DO_NOTHING', 0x00);
45define('ASDF_CLOSE_DO_PACK', 0x01);
46define('ASDF_CLOSE_DO_DELETE', 0x02);
47
48/**
49 * main class for SQLite files managment
50 */
51class StatDB
52{
53  protected $fileRootName='';
54  protected $fileDir='';
55  protected $fileName='';
56  protected $dbHandle=null;
57  protected $transacOpen=false;
58  protected $ipCountryFile='';
59  protected $lckFileNumber=0;
60  //protected $logs=''; for debug
61  private $deleteUnpackedWhenClose=ASDF_CLOSE_DO_NOTHING;
62
63  /**
64   * constructor
65   *
66   * @param String $directory: directory where the sqlite file is saved
67   * @param String $fileName: file name (without extension)
68   */
69  public function __construct($directory='', $fileName='')
70  {
71    $this->setDirectory($directory);
72    $this->setFileName($fileName);
73  }
74
75  public function __destruct()
76  {
77   $this->close();
78  }
79
80
81  /**
82   * return informations about the current data file
83   *
84   * @return Array
85   */
86  public function getInfo()
87  {
88    $returned=array(
89      'dirName'       => $this->fileDir,
90      'fileName'      => $this->fileName,
91      'fileExist'     => $this->fileExist(),
92      'open'          => ($this->dbHandle==null)?false:true,
93      'unpackedFile'  => array(
94                            'fileName' => $this->getFileName(ASDF_EXIST_UNPACKED),
95                            'fileSize' => ($this->getFileName(ASDF_EXIST_UNPACKED, true)!='')?filesize($this->getFileName(ASDF_EXIST_UNPACKED)):0
96                          ),
97      'packedFile'    => array(
98                            'fileName' => $this->getFileName(ASDF_EXIST_PACKED),
99                            'fileSize' => ($this->getFileName(ASDF_EXIST_PACKED, true)!='')?filesize($this->getFileName(ASDF_EXIST_PACKED)):0
100                          )
101    );
102
103    if($this->dbHandle!=null)
104    {
105      $tables=$this->getDBInfo(ASDF_DB_TYPE_TABLE, null, ASDF_DB_LIST);
106
107      $returned['nbRecords']=array();
108      foreach($tables as $table)
109      {
110        $returned['nbRecords'][$table] = $this->getDBInfo(ASDF_DB_TYPE_TABLE, $table, ASDF_DB_NUM_ROWS);
111      }
112      $returned['db']=array(
113                        'integrity'    => $this->getPragma('integrity_check'),
114                        'pages'        => $this->getPragma('page_count'),
115                        'pageSize'     => $this->getPragma('page_size'),
116                        'unusedPages'  => $this->getPragma('freelist_count'),
117                        'version(s)'   => $this->getPragma('schema_version'),
118                        'version(u)'   => $this->getPragma('user_version')
119                      );
120
121
122      $logs=$this->getDBInfo(ASDF_DB_TYPE_LOG, null, null);
123      $returned['log']=array();
124      foreach($logs as $log)
125      {
126        $returned['log'][$log['key']] = $log['value'];
127      }
128
129
130      $nfos=$this->getDBInfo(ASDF_DB_TYPE_NFO, null, null);
131      $returned['nfo']=array();
132      foreach($nfos as $nfo)
133      {
134        $returned['nfo'][$nfo['key']] = $nfo['value'];
135      }
136
137    }
138    return($returned);
139  }
140
141  /**
142   * set the directory
143   *
144   * @param String $directory: the directory
145   * @return String: directory set
146   */
147  public function setDirectory($directory)
148  {
149    if(substr($directory,-1)!='/') $directory.='/';
150
151    // check if directory is valid
152    if(file_exists($directory) and !$this->isOpen())
153    {
154      $this->fileDir=$directory;
155    }
156    return($this->fileDir);
157  }
158
159  /**
160   * set the file name
161   *
162   * @param String $fileName: file name
163   * @return String: file name
164   */
165  public function setFileName($fileName)
166  {
167    if($this->fileName!=$fileName)
168    {
169      if($this->close()) $this->fileName=$fileName;
170    }
171    return($this->fileName);
172  }
173
174  /**
175   * return information, if set file exists or not
176   *
177   * @return Integer : 0x00 not exist (ASDF_EXIST_NO)
178   *                   0x01 exist, packed (ASDF_EXIST_PACKED)
179   *                   0x02 exist, unpacked ASDF_EXIST_UNPACKED
180   */
181  public function fileExist()
182  {
183    $returned=ASDF_EXIST_NO;
184    if($this->getFileName(ASDF_EXIST_UNPACKED)!='')
185      $returned=$returned | ASDF_EXIST_UNPACKED;
186
187    if($this->getFileName(ASDF_EXIST_PACKED)!='')
188      $returned=$returned | ASDF_EXIST_PACKED;
189
190    return($returned);
191  }
192
193  /**
194   * set the sqlite file name to retrieve IP country values
195   * $file must exist
196   *
197   * @param String $file
198   * @param Boolean $force: if true, $force the filename affectation even if file doesn't exist
199   * @return String: file name set
200   */
201  public function setIpCountryFile($file, $force=false)
202  {
203    if($force or file_exists($file))
204      $this->ipCountryFile=$file;
205    return($this->ipCountryFile);
206  }
207
208  /**
209   * return the sqlite file name used to retrieve IP country values
210   *
211   * @return String
212   */
213  public function getIpCountryFile()
214  {
215    return($this->ipCountryFile);
216  }
217
218
219  /**
220   * open the filename
221   * if $mode=ASDF_OPEN_WRITE and file doesn't exist, create it; if a packed file
222   * exist, try to unpack the packed file; in this case the unpacked file will be
223   * packed and removed automatically when the process will close
224   *
225   * if $mode=ASDF_OPEN_READ, and unpacked file doesn't exist, try to unpack
226   * packed file (if exists); in this case the unpacked file will be removed
227   * automatically when the process will close (if there's no lock
228   * files linked to the uncompressed file); a new lock file is created
229   *
230   * @param Integer $mode: READ or WRITE
231   * @return Boolean: true if opened, otherwise false
232   */
233  public function open($mode=ASDF_OPEN_READ)
234  {
235    $dbFile=$this->getFileName(ASDF_EXIST_UNPACKED, true);
236    $this->createLockFile();
237    $this->waitForUnpackedFile();
238
239    /*
240     * encountered bug: sometime, an unpacked file stay with a zero size length
241     * in this case, assume to unpack packed file
242     */
243    if($dbFile!='' and filesize($dbFile)==0)
244    {
245      unlink($dbFile);
246      $dbFile='';
247    }
248
249    if($dbFile=='')
250    {
251      // file doesn't exist, check if a packed file exist
252      $dbFileP=$this->getFileName(ASDF_EXIST_PACKED, true);
253      if($dbFileP=='' and $mode==ASDF_OPEN_READ) return(false); //no packed file to open for READ, exit
254
255      if($dbFileP!='')
256      {
257        $this->unpack(); //unpack the file
258        $this->deleteUnpackedWhenClose=ASDF_CLOSE_DO_DELETE; // the unpacked file will be deleted when the process will close
259      }
260    }
261    else
262    {
263      if($this->lckFileNumber>1 and $this->getFileName(ASDF_EXIST_PACKED, true)!='' and $mode==ASDF_OPEN_READ)
264        $this->deleteUnpackedWhenClose=ASDF_CLOSE_DO_DELETE;
265    }
266
267    switch($mode)
268    {
269      case ASDF_OPEN_READ:
270        $dbFile=$this->getFileName(ASDF_EXIST_UNPACKED, true);
271        if($dbFile=='') return(false); //big problem with unpacked file!?
272        $this->dbHandle=new SQLite3($dbFile, SQLITE3_OPEN_READONLY);
273        if($this->dbHandle) return(true);
274        break;
275      case ASDF_OPEN_WRITE:
276        $dbFile=$this->getFileName(ASDF_EXIST_UNPACKED);
277        //if file was unpacked, pack it before deleting
278        if($this->deleteUnpackedWhenClose==ASDF_CLOSE_DO_DELETE) $this->deleteUnpackedWhenClose=$this->deleteUnpackedWhenClose|ASDF_CLOSE_DO_PACK;
279        $this->dbHandle=new SQLite3($dbFile, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
280
281        if($this->dbHandle) $this->checkSchema();
282        return(true);
283        break;
284    }
285
286    return(false);
287  }
288
289  /**
290   * close the file
291   *
292   * @return Boolean : true if file is closed
293   */
294  public function close()
295  {
296    if($this->dbHandle==null) return(true);
297    $this->stopTransac();
298
299    if($this->dbHandle->close())
300    {
301      if(($this->deleteUnpackedWhenClose&ASDF_CLOSE_DO_PACK)==ASDF_CLOSE_DO_PACK)
302      {
303        $this->pack();
304      }
305
306      $this->deleteLockFile();
307      if(($this->deleteUnpackedWhenClose&ASDF_CLOSE_DO_DELETE)==ASDF_CLOSE_DO_DELETE and
308          !$this->fileIsLocked())
309      {
310        $this->delete(ASDF_DELETE_UNPACKED);
311      }
312
313      $this->deleteUnpackedWhenClose=ASDF_CLOSE_DO_NOTHING;
314      $this->dbHandle=null;
315      return(true);
316    }
317    return(false);
318  }
319
320  /**
321   * pack the data file in a .gz file
322   *
323   * @return Bool : false if a problem happened
324   */
325  public function pack()
326  {
327    $files=array();
328
329    $file=$this->getFileName(ASDF_EXIST_UNPACKED, true);
330
331    if($file!='')
332      return(GPCCompress::gzip($file, $this->getFileName(ASDF_EXIST_PACKED)));
333    return(false);
334  }
335
336  /**
337   * unpack the .gz file
338   *
339   * @return Boolean : false if an error occurs
340   */
341  public function unpack()
342  {
343    if($this->fileExist() & ASDF_EXIST_PACKED==ASDF_EXIST_PACKED)
344    {
345      $unpackedFile=$this->getFileName(ASDF_EXIST_UNPACKED);
346
347      // create a temp file
348      $fHandle=fopen($unpackedFile, 'w');
349      fclose($fHandle);
350
351      $returned=GPCCompress::gunzip($this->getFileName(ASDF_EXIST_PACKED), $unpackedFile.'.tmp');
352
353      unlink($unpackedFile);
354      if($returned)
355      {
356        rename($unpackedFile.'.tmp', $unpackedFile);
357      }
358
359      return($returned);
360    }
361  }
362
363  /**
364   * delete the file
365   *
366   * $mode can take this option
367   *  - ASDF_DELETE_PACKED : delete packed file if exists
368   *  - ASDF_DELETE_UNPACKED : delete unpacked file if exists
369   * option can be combined with a pipe
370   *
371   * @param Integer $mode : delete mode to apply
372   * @return Integer :
373   */
374  public function delete($mode)
375  {
376    $returned=0;
377    if(($mode & ASDF_DELETE_PACKED)==ASDF_DELETE_PACKED)
378    {
379      if($this->getFileName(ASDF_EXIST_PACKED, true)!='')
380      {
381        if(unlink($this->getFileName(ASDF_EXIST_PACKED))) $returned=ASDF_DELETE_PACKED;
382      }
383    }
384
385    if(($mode & ASDF_DELETE_UNPACKED)==ASDF_DELETE_UNPACKED)
386    {
387      $ok=true;
388      if($this->getFileName(ASDF_EXIST_UNPACKED, true)!='') $ok=unlink($this->getFileName(ASDF_EXIST_UNPACKED));
389      if($ok) $returned=$returned | ASDF_DELETE_UNPACKED;
390    }
391
392    return($returned);
393  }
394
395  /**
396   * return true is the file is open
397   *
398   * @return Boolean
399   */
400  public function isOpen()
401  {
402    return($this->dbHandle!=null);
403  }
404
405  /**
406   * return the file name
407   *
408   * @return String
409   */
410  public function getName()
411  {
412    return($this->fileName);
413  }
414  /**
415   * return the directory
416   *
417   * @return String
418   */
419  public function getDirectory()
420  {
421    return($this->fileDir);
422  }
423
424  /**
425   * return the physical file name
426   *
427   * @param Integer $mode: ask for PACKED or UNPACKED file name
428   * @return String: file name if file exists, otherwise ''
429   */
430  public function getFileName($mode=ASDF_EXIST_UNPACKED, $onlyIfExist=false)
431  {
432    $fileName='';
433    switch($mode)
434    {
435      case ASDF_EXIST_PACKED:
436        $fileName=$this->fileDir.$this->fileName.$this->fileRootName.ASDF_FILE_EXT_ZIP;
437        break;
438      case ASDF_EXIST_UNPACKED:
439        $fileName=$this->fileDir.$this->fileName.$this->fileRootName.ASDF_FILE_EXT;
440        break;
441      case ASDF_EXIST_LOCK:
442        $fileName=$this->fileDir.$this->fileName.$this->fileRootName.'-'.$this->lckFileNumber.ASDF_FILE_EXT_LOCK;
443        break;
444    }
445
446    if($onlyIfExist)
447    {
448      if($fileName!='' and file_exists($fileName)) return($fileName);
449      return('');
450    }
451    return($fileName);
452  }
453
454  /**
455   * begin a transaction
456   *
457   * usefull for performances when mass update are needed
458   *
459   * @return Boolean: true if the transaction is opened, otherwise false
460   */
461  public function startTransac()
462  {
463    if($this->dbHandle==null or $this->transacOpen) return(false);
464
465    if($this->dbHandle->exec("BEGIN TRANSACTION"))
466    {
467      $this->transacOpen=true;
468      return(true);
469    }
470    return(false);
471  }
472
473  /**
474   * stop an opened transaction
475   *
476   * @param Integer $mode: COMMIT (ASDF_TRANSAC_COMMIT) or ROLLBACK (ASDF_TRANSAC_ROLLBACK) the current transaction
477   * @return Boolean: true if the transaction is opened, otherwise false
478   */
479  public function stopTransac($mode=ASDF_TRANSAC_COMMIT)
480  {
481    if($this->dbHandle==null or !$this->transacOpen) return(false);
482
483    $returned=false;
484
485    switch($mode)
486    {
487      case ASDF_TRANSAC_COMMIT:
488        $returned=$this->dbHandle->exec("COMMIT TRANSACTION");
489        break;
490      case ASDF_TRANSAC_ROLLBACK:
491        $returned=$this->dbHandle->exec("ROLLBACK TRANSACTION");
492        break;
493    }
494
495    if($returned) $this->transacOpen=false;
496    return($returned);
497  }
498
499
500  /**
501   * set a value in the infos tables
502   * if key doesn't exist, insert it
503   *
504   * @param String $domain: domain to update
505   * @param String $key: key to update
506   * @param String $value: value to set
507   * @return Boolean
508   */
509  public function setInfoProperty($domain, $key, $value)
510  {
511    if($this->dbHandle==null) return(false);
512    $sql="REPLACE INTO info (domain, key, value)
513            VALUES('".$this->dbHandle->escapeString($domain)."',
514                   '".$this->dbHandle->escapeString($key)."',
515                   '".$this->dbHandle->escapeString($value)."');";
516    return($this->dbHandle->exec($sql));
517  }
518
519  /**
520   * get a value from the info tables
521   * if key doesn't exist, return null by default
522   *
523   * @param String $domain: domain to update
524   * @param String $key: key to update
525   * @param String $value: default value if not found
526   * @return Boolean
527   */
528  public function getInfoProperty($domain, $key, $value=null)
529  {
530    if($this->dbHandle==null) return($value);
531    $sql="SELECT value
532          FROM info
533          WHERE domain = '".$this->dbHandle->escapeString($domain)."'
534            AND key = '".$this->dbHandle->escapeString($key)."';";
535    $returned=$this->dbHandle->querySingle($sql);
536    if($returned==null) $returned=$value;
537    return($returned);
538  }
539
540  /**
541   * how the file lock system works:
542   *
543   * the lock system is used only when for uncompressed files, allowing to know
544   * if an uncompressed file can be deleted or not; locks function are called only
545   * in READ mode by function open() and close()
546   *
547   * createLockFile()
548   *  1/ Search for existing lock files
549   *  2/ If files are found, increase the $lckFileNumber with number of existing files + 1
550   *  3/ Create the lock file
551   *
552   * deleteLockFile()
553   *  delete the lock file associated with the current $lckFileNumber
554   *
555   * fileIsLocked()
556   *  return true if at least, one lock exists
557   *
558   *  ......................................
559   *          ......................................
560   *                   ..............
561   * -+-------+--------+------------+------+-------+-->
562   * (A)     (B)      (C)          (D)    (E)     (F)
563   *  ^       ^        ^            ^      ^       ^
564   *  p1      p2       p3           p3     p1      p2
565   *  start   start    start        end    end     end
566   *
567   * (A) process 1 start: createLockFile() => lock file '1' is created
568   * (B) process 2 start: createLockFile() => lock file '2' is created
569   * (C) process 3 start: createLockFile() => lock file '3' is created
570   * (D) process 3 end:   deleteLockFile() => lock file '3' is deleted
571   *                                          lock files '1' & '2' still exist (uncompressed SQLite file can't be deleted)
572   * (E) process 1 end:   deleteLockFile() => lock file '1' is deleted
573   *                                          lock file '2' still exist (uncompressed SQLite file can't be deleted)
574   * (F) process 2 end:   deleteLockFile() => lock file '2' is deleted
575   *                                          there's no lock files (uncompressed SQLite file can be deleted)
576   *
577   * @return Boolean: true if the lock file was created, otherwise false
578   */
579  protected function createLockFile()
580  {
581    $this->lckFileNumber=$this->getNumberOfLockFiles()+1;
582    $fHandle=fopen($this->getFileName(ASDF_EXIST_LOCK), 'w');
583    if($fHandle)
584    {
585      fclose($fHandle);
586      return(true);
587    }
588    $this->lckFileNumber=0;
589    return(false);
590  }
591
592  /**
593   * delete the current lock file
594   * return true if file is deleted (or file doesn't exists => there's no more file to delete...), otherwise return false
595   *
596   * the function also delete all lock files older than current timestamp+max_execution_time (if
597   * the file still exist, it means that a problem has occured at a moment)
598   *
599   * @return Boolean
600   */
601  protected function deleteLockFile()
602  {
603    $returned=true;
604    $file=$this->getFileName(ASDF_EXIST_LOCK);
605    if(file_exists($file)) $returned=unlink($file);
606
607    if($returned) $this->lckFileNumber=0;
608
609    $regExp="/\\".ASDF_FILE_EXT_LOCK.'/i';
610    scandir(dirName($this->fileDir));
611    $files=scandir($this->fileDir);
612    $timeOut=time()-ini_get('max_execution_time');
613
614    foreach($files as $file)
615    {
616      if(preg_match($regExp, $file, $result)>0)
617      {
618        if(filemtime($this->fileDir.$file)<$timeOut)
619        {
620          unlink($this->fileDir.$file);
621        }
622      }
623    }
624
625
626
627    return($returned);
628  }
629
630  /**
631   * return true if a lock file exists
632   *
633   * @return Boolean
634   */
635  protected function fileIsLocked()
636  {
637    if($this->getNumberOfLockFiles()>0) return(true);
638    return(false);
639  }
640
641  /**
642   * return true the number of existing lock files
643   *
644   * @return Integer: number of existing lock files
645   */
646  protected function getNumberOfLockFiles()
647  {
648    $returned=0;
649    $regExp='/'.$this->fileName.$this->fileRootName."-(\d+)\\".ASDF_FILE_EXT_LOCK.'/i';
650    $files=scandir($this->fileDir);
651    foreach($files as $file)
652    {
653      if(preg_match($regExp, $file, $result)>0)
654      {
655        $tmp=$result[1]*1;
656        if($tmp>$returned) $returned=$tmp;
657      }
658    }
659    return($returned);
660  }
661
662  /**
663   * if the unpacked exist with a '.tmp' extension, assume that an unpacking process
664   * is running and loop until the file doesn't exist anymore
665   *
666   * @param Integer $maxWait: maximum wait time in millisecond...
667   * @return Boolean: true if file was unpacked in the waiting time, otherwise false
668   */
669  protected function waitForUnpackedFile($maxWait=2000)
670  {
671    $tmpFile=$this->getFileName(ASDF_EXIST_UNPACKED).'.tmp';
672    $maxTimer=microtime(true)+$maxWait/1000;
673    while(file_exists($tmpFile))
674    {
675      if(microtime(true)>=$maxTimer) return(false);
676      usleep(5000); //wait 5milliseconds before next check
677    }
678    return(true);
679  }
680
681  /**
682   * check, create & update the schema of the database
683   *
684   * automatically called when the file is opened
685   *
686   * the table 'info' is created by default
687   */
688  protected function checkSchema()
689  {
690    if($this->dbHandle)
691    {
692      $result=$this->getDBInfo(ASDF_DB_TYPE_TABLE, 'info', ASDF_DB_INFO);
693      if(count($result)==0)
694      {
695        $this->dbHandle->exec(
696            "CREATE TABLE 'info' (
697                'domain' TEXT,
698                'key' TEXT,
699                'value' TEXT
700            );"
701          );
702        $this->dbHandle->exec("CREATE UNIQUE INDEX iInfo ON info ('domain', 'key');");
703
704        $this->dbHandle->exec("
705          INSERT INTO info (domain, key, value) VALUES ('nfo', 'create', '".date('Y-m-d H:i:s')."');
706          INSERT INTO info (domain, key, value) VALUES ('nfo', 'version', '00.00.00');
707
708          INSERT INTO info (domain, key, value) VALUES ('log', 'buildStatPeriod-count', '0');
709        ");
710      }
711      return(true);
712    }
713    return(false);
714  }
715
716  /**
717   * return table infos
718   *
719   * $mode=ASDF_DB_INFO
720   *    return the table structure, one row per field
721   *        'name' => field name
722   *        'type' => field type
723   *
724   * $mode=ASDF_DB_NUM_ROWS
725   *    return the number of rows in the table
726   *
727   * @param Integer $type: ASDF_DB_TYPE_TABLE, ASDF_DB_TYPE_INDEX, ASDF_DB_TYPE_TRIGGER
728   * @param String $name
729   * @param Integer $mode : ASDF_DB_INFO, ASDF_DB_NUM_ROWS
730   * @return :
731   */
732  protected function getDBInfo($type, $name, $mode=0)
733  {
734    if($this->dbHandle==null) return(null);
735
736    switch($type)
737    {
738      case ASDF_DB_TYPE_TABLE:
739          switch($mode)
740          {
741            case ASDF_DB_INFO:
742                $returned=array();
743                $result=$this->dbHandle->query("PRAGMA table_info('$name')");
744                if($result)
745                {
746                  while($row=$result->fetchArray(SQLITE3_ASSOC))
747                  {
748                    $returned[]=array(
749                      'name' => $row['name'],
750                      'type' => $row['type']
751                    );
752                  }
753                }
754              break;
755            case ASDF_DB_NUM_ROWS:
756                $returned=$this->dbHandle->querySingle("SELECT COUNT(*) FROM $name");
757              break;
758            case ASDF_DB_LIST:
759                $returned=array();
760                $result=$this->dbHandle->query("SELECT name FROM sqlite_master WHERE type='table';");
761                if($result)
762                {
763                  while($row=$result->fetchArray(SQLITE3_ASSOC))
764                  {
765                    $returned[]=$row['name'];
766                  }
767                }
768              break;
769          }
770        break;
771      case ASDF_DB_TYPE_INDEX:
772          $returned=$this->dbHandle->querySingle("SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name='$name'");
773        break;
774      case ASDF_DB_TYPE_TRIGGER:
775          $returned=$this->dbHandle->querySingle("SELECT COUNT(*) FROM sqlite_master WHERE type = 'trigger' AND name='$name'");
776        break;
777      case ASDF_DB_TYPE_LOG:
778          $returned=array();
779          if(count($this->getDBInfo(ASDF_DB_TYPE_TABLE, 'info', ASDF_DB_INFO))==0) return($returned);
780          $result=$this->dbHandle->query("SELECT key, value FROM info WHERE domain='log';");
781          if($result)
782          {
783            while($row=$result->fetchArray(SQLITE3_ASSOC))
784            {
785              $returned[]=$row;
786            }
787          }
788        break;
789      case ASDF_DB_TYPE_NFO:
790          $returned=array();
791          if(count($this->getDBInfo(ASDF_DB_TYPE_TABLE, 'info', ASDF_DB_INFO))==0) return($returned);
792          $result=$this->dbHandle->query("SELECT key, value FROM info WHERE domain='nfo';");
793          if($result)
794          {
795            while($row=$result->fetchArray(SQLITE3_ASSOC))
796            {
797              $returned[]=$row;
798            }
799          }
800        break;
801    }
802
803    return($returned);
804  }
805
806  /**
807   * return pragma info
808   *
809   * @param String $pragma: a sqlite pragma command
810   * @return: if single data is returned, return the data otherwise return an array
811   */
812  protected function getPragma($pragma)
813  {
814    $returned=array();
815
816    if($this->dbHandle==null) return($returned);
817
818    $sql="PRAGMA ".$pragma;
819    $result=$this->dbHandle->query($sql);
820    if($result)
821    {
822      while($row=$result->fetchArray(SQLITE3_ASSOC))
823      {
824        $returned[]=$row;
825      }
826      if(count($returned)==1) $returned=$returned[0][$pragma];
827    }
828    return($returned);
829  }
830
831
832
833  /**
834   * build a where clause from a filter array
835   * used by functions:
836   *  . getLogs()
837   *  . getStatIP()
838   *  . getStatCat()
839   *  . getStatImages()
840   *
841   * return a string:
842   *  . empty '' string if there's no item to filter
843   *  . a ready to use ' WHERE ' clause string
844   *
845   * @param Array $filter: a valid filter
846   * @param Array &$IPList: an array, used to return a ':IPn' list for requests
847   *                        filtering IP adress
848   * @return String
849   */
850  protected function buildWhereClause($filter, &$IPList)
851  {
852    $where=array();
853    $IPList=array();
854    $num=0;
855
856    if(!is_array($filter)) return('');
857
858    foreach($filter as $field => $param)
859    {
860      if(is_array($param))
861      {
862        switch($param['operator'])
863        {
864          case '=':
865          case '>':
866          case '>=':
867          case '<':
868          case '<=':
869          case '!=':
870            if($field=='IPadress')
871            {
872              $IPList[$num]=$param['value'];
873              $param['value']=':IP'.$num;
874              $num++;
875            }
876            $where[]=' '.$field.' '.$param['operator']." ".$param['value']." ";
877            break;
878          case 'in':
879            if($field=='IPadress')
880            {
881              foreach($param['value'] as $key=>$ipAdress)
882              {
883                $IPList[$num]=$ipAdress;
884                $param[$key]['value']=':IP'.$num;
885                $num++;
886              }
887            }
888            $where[]=' '.$field." IN (".implode(',', $param['value']).") ";
889            break;
890          case 'between':
891          case 'not between':
892            if($field=='IPadress')
893            {
894              $IPList[$num]=$param['minValue'];
895              $param['minValue']=':IP'.$num;
896              $num++;
897              $IPList[$num]=$param['maxValue'];
898              $param['maxValue']=':IP'.$num;
899              $num++;
900            }
901            $where[]=' '.$field.' '.strtoupper($param['operator']).' '.$param['minValue']." AND ".$param['maxValue']." ";
902            break;
903        }
904      }
905    }
906
907    if(count($where)>0)
908    {
909      $where=" WHERE ".implode(' AND ', $where)." ";
910    }
911    else
912    {
913      $where='';
914    }
915
916    return($where);
917  }
918
919
920
921  /**
922   * build an ORDER BY clause
923   * used by functions:
924   *  . getLogs()
925   *  . getStatIP()
926   *  . getStatCat()
927   *  . getStatImages()
928   *
929   * return a string:
930   *  . empty '' string if there's no limits
931   *  . a ready to use ' ORDER BY ' clause string
932   *
933   * @param Array $filter: a valid order by list
934   * @return String
935   */
936  protected function buildOrderByClause($orderBy)
937  {
938    $order=array();
939    foreach($orderBy as $sort)
940    {
941      $order[]=' '.$sort['id'].' '.($sort['direction']=='A'?'ASC':'DESC');
942    }
943
944    if(count($order)>0)
945    {
946      $orderBy=" ORDER BY ".implode(',', $order);
947    }
948    else
949    {
950      $orderBy='';
951    }
952
953    return($orderBy);
954  }
955
956
957
958  /**
959   * build a LIMIT clause
960   * used by functions:
961   *  . getLogs()
962   *  . getStatIP()
963   *  . getStatCat()
964   *  . getStatImages()
965   *
966   * return a string:
967   *  . empty '' string if there's no limits
968   *  . a ready to use ' LIMIT x,y ' clause string
969   *
970   * @param Integer $page: page number
971   * @param Integer $nbItemsPage: number of items per page
972   * @return String
973   */
974  protected function buildLimitClause($page, $nbItemsPage)
975  {
976    if($page<=0 or $nbItemsPage<=0)
977    {
978      $limit='';
979    }
980    else
981    {
982      $limit=' LIMIT '.(($page-1)*$nbItemsPage).', '.$nbItemsPage;
983    }
984
985    return($limit);
986  }
987
988
989  /**
990   * to be used with IP filter: replace the given operator with 'between' or 'not between'
991   *
992   * @param Array $value
993   * @return Array
994   */
995  protected function checkIPFilter($value)
996  {
997    if(is_array($value))
998    {
999      if(isset($value['operator']))
1000      {
1001        if($value['operator']=='=' or
1002           $value['operator']=='>' or
1003           $value['operator']=='>=' or
1004           $value['operator']=='<' or
1005           $value['operator']=='<=' or
1006           $value['operator']=='like')
1007        {
1008          $value['operator']='between';
1009          $value['minValue']=$value['value'];
1010          $value['maxValue']=$value['value'];
1011          $value['value']=null;
1012        }
1013        elseif($value['operator']=='!=' or
1014               $value['operator']=='not like')
1015        {
1016          $value['operator']='not between';
1017          $value['minValue']=$value['value'];
1018          $value['maxValue']=$value['value'];
1019          $value['value']=null;
1020        }
1021      }
1022    }
1023    return($value);
1024  } // checkIPFilter
1025
1026
1027
1028  /**
1029   * to be used with Date filter: replace the given operator with 'between' or 'not between'
1030   *
1031   * @param Array $value
1032   * @return Array
1033   */
1034  protected function checkDateFilter($value)
1035  {
1036    if(is_array($value))
1037    {
1038      if(isset($value['operator']))
1039      {
1040        if($value['operator']=='=' or
1041           $value['operator']=='like')
1042        {
1043          $value['operator']='between';
1044          $value['minValue']=$value['value'];
1045          $value['maxValue']=$value['value'];
1046          $value['value']=null;
1047        }
1048        elseif($value['operator']=='!=' or
1049               $value['operator']=='not like')
1050        {
1051          $value['operator']='not between';
1052          $value['minValue']=$value['value'];
1053          $value['maxValue']=$value['value'];
1054          $value['value']=null;
1055        }
1056      }
1057    }
1058    return($value);
1059  } // checkDateFilter
1060
1061
1062
1063
1064  /**
1065   * check if value is a valid operator array
1066   * if not, return a null value otherwise return the operator
1067   *
1068   * validity of value are checked according to the format; valid format are:
1069   *  - 'date'    => 'YYYY-MM-DD (HH:II(:SS))'
1070   *  - 'IP'      => 'xxx.xxx.xxx.xxx' for IPv4, 'xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' for IPv6
1071   *  - 'string'  => a string
1072   *  - 'integer' => any positive integer value
1073   *
1074   * @param Array $value
1075   * @param String $format
1076   * @return Array:
1077   */
1078  protected function getOperator($value, $format)
1079  {
1080    $returned=null;
1081    if($value==null) return(null);
1082    if(is_array($value))
1083    {
1084      if(isset($value['operator']))
1085      {
1086        switch($value['operator'])
1087        {
1088          case '=':
1089          case '>':
1090          case '>=':
1091          case '<':
1092          case '<=':
1093          case '!=':
1094          case 'like':
1095          case 'not like':
1096            if(isset($value['value']))
1097            {
1098              switch($format)
1099              {
1100                case 'date':
1101                  switch($value['operator'])
1102                  {
1103                    case '>':
1104                    case '<=':
1105                      $value['value']=$this->checkValueDate($value['value'], 'E');
1106                      break;
1107                    case '<':
1108                    case '>=':
1109                    default:
1110                      $value['value']=$this->checkValueDate($value['value'], 'B');
1111                      break;
1112                  }
1113                  if($value['value']==null) return(null);
1114                  $value['value']=$value['value'];
1115                  break;
1116                case 'IP':
1117                  // for IP, replace internaly the operator with a 'between'
1118                  $value['value']=$this->checkValueIP($value['value'], 0);
1119                  if($value['value']==null) return(null);
1120                  break;
1121                case 'string':
1122                  if(!is_string($value['value']) or $value['value']=='') return(null);
1123                  $value['value']="'".$value['value']."'";
1124                  break;
1125                case 'integer':
1126                  if(!is_int($value['value']) or $value['value']<0) return(null);
1127                  break;
1128              }
1129              $returned=$value;
1130            }
1131            break;
1132          case 'in':
1133            if(isset($value['value']) and is_array($value['value']))
1134            {
1135              $returned=array();
1136              foreach($value['value'] as $val)
1137              {
1138                switch($format)
1139                {
1140                  case 'date':
1141                    $val=$this->checkValueDate($val);
1142                    if($val!=null) $returned[]=$val;
1143                    break;
1144                  case 'IP':
1145                    $val=$this->checkValueIP($val, 0);
1146                    if($val!=null) $returned[]=$val;
1147                    break;
1148                  case 'string':
1149                    if(is_string($val) and $val!='') $returned[]="'".$val."'";
1150                    break;
1151                  case 'integer':
1152                    if(is_int($val*1) and $val>=0) $returned[]=$val;
1153                    break;
1154                }
1155              }
1156              if(count($returned)==0) return(null);
1157              $value['value']=$returned;
1158              $returned=$value;
1159            }
1160            break;
1161          case 'between':
1162          case 'not between':
1163            if(isset($value['minValue']) and  isset($value['maxValue']))
1164            {
1165              switch($format)
1166              {
1167                case 'date':
1168                  $value['minValue']=$this->checkValueDate($value['minValue'], 'B');
1169                  if($value['minValue']==null) return(null);
1170                  $value['maxValue']=$this->checkValueDate($value['maxValue'], 'E');
1171                  if($value['maxValue']==null) return(null);
1172                  break;
1173                case 'IP':
1174                  $value['minValue']=$this->checkValueIP($value['minValue'], 0);
1175                  if($value['minValue']==null) return(null);
1176                  $value['maxValue']=$this->checkValueIP($value['maxValue'], 255);
1177                  if($value['maxValue']==null) return(null);
1178                  break;
1179                case 'string':
1180                  if(!is_string($value['minValue']) or $value['minValue']=='' or
1181                     !is_string($value['maxValue']) or $value['maxValue']=='') return(null);
1182
1183                  $value['minValue']="'".$value['minValue']."'";
1184                  $value['maxValue']="'".$value['maxValue']."'";
1185                  break;
1186                case 'integer':
1187                  if(!is_int($value['minValue']) or $value['minValue']<0 or
1188                     !is_int($value['maxValue']) or $value['maxValue']<0) return(null);
1189                  break;
1190              }
1191              if($value['minValue']>$value['maxValue']) return(null);
1192              $returned=$value;
1193            }
1194            break;
1195        }
1196      }
1197    }
1198    return($returned);
1199  }
1200
1201  /**
1202   * check if date is Ok
1203   * return the date completed
1204   *      (YYYY-MM-DD => YYYY-MM-DD 00:00:00) if $mode='B'
1205   *  or  (YYYY-MM-DD => YYYY-MM-DD 23:59:59) if $mode='E'
1206   *  or  null if not valid
1207   *
1208   * @param String $value
1209   * @param String $mode: 'B' for begin or 'E' for end
1210   * @return String
1211   */
1212  protected function checkValueDate($value, $mode='B')
1213  {
1214    $returned=null;
1215    $result=array();
1216    if(preg_match('/^(\d{4}-\d{2}-\d{2})(?:(\s{1}\d{2}:\d{2})(:\d{2})?)?$/', trim($value), $result)>0)
1217    {
1218      $returned=$result[1];
1219      if(isset($result[2]))
1220      {
1221        $returned.=$result[2];
1222        if(isset($result[3]))
1223        {
1224          $returned.=$result[3];
1225        }
1226        else
1227        {
1228          $returned.=($mode=='B')?':00':':59';
1229        }
1230      }
1231      else
1232      {
1233        $returned.=($mode=='B')?' 00:00:00':' 23:59:59';
1234      }
1235    }
1236    return(strtotime($returned));
1237  }
1238
1239  /**
1240   * check if IP is Ok (IPv4 or IPv6)
1241   * return the IP or null
1242   *
1243   * @param String $value
1244   * @param Integer $completeCode
1245   * @return String
1246   */
1247  protected function checkValueIP($value, $completeCode=0)
1248  {
1249    $returned=self::IPBinaryEncode($value, $completeCode);
1250    if($returned=="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") return(null);
1251    return($returned);
1252  }
1253
1254
1255
1256
1257
1258
1259
1260  /**
1261   * return an IP adress with format 'xxx.xxx.xxx.xxx' as a binary encoded string
1262   *
1263   * @param String $IP: text IP string
1264   * @param Integer $completeCode
1265   * @return String: a binary string
1266   */
1267  static public function IPBinaryEncode($IP, $completeCode=0)
1268  {
1269    $value=trim($IP);
1270    if(preg_match('/\d{1,3}(\.\d{1,3}(\.\d{1,3}(\.\d{1,3})?)?)?/i',$value)>0)
1271    {
1272      //IPv4
1273      $tmp=explode('.',$value);
1274      while(count($tmp)<4) $tmp[]=$completeCode;
1275
1276      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));
1277    }
1278    elseif(preg_match('/[a-f0-9]{0,4}:[a-f0-9]{0,4}:[a-f0-9]{0,4}:[a-f0-9]{0,4}:[a-f0-9]{0,4}:[a-f0-9]{0,4}:[a-f0-9]{0,4}:[a-f0-9]{0,4}/i',$value))
1279    {
1280      //IPv6
1281    }
1282    else
1283    {
1284      return("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
1285    }
1286  }
1287
1288  /**
1289   * return an IP adress with format 'xxx.xxx.xxx.xxx' from a binary encoded string
1290   *
1291   * @param String $IP: binary encoded string
1292   * @return String: a text IP string
1293   */
1294  static public function IPBinaryDecode($IP)
1295  {
1296    $returned='';
1297    if(strlen($IP)==16)
1298    {
1299      if(substr($IP,0,12)=="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
1300      {
1301        //IPv4
1302        $returned=sprintf('%d.%d.%d.%d', ord(substr($IP,12,1)), ord(substr($IP,13,1)), ord(substr($IP,14,1)), ord(substr($IP,15,1)));
1303      }
1304      else
1305      {
1306        //IPv6
1307      }
1308    }
1309    else if(strlen($IP)==4)
1310    {
1311      $returned=sprintf('%d.%d.%d.%d', ord(substr($IP,0,1)), ord(substr($IP,1,1)), ord(substr($IP,2,1)), ord(substr($IP,3,1)));
1312    }
1313    else
1314    {
1315    }
1316    return($returned);
1317  }
1318
1319
1320  /**
1321    * Convert the given data into an unsigned Short integer (16bit)
1322    *
1323    * @param String $data       : representing the unsigned Short
1324    * @param String $endianType : the byte order
1325    * @return UShort
1326    */
1327  static public function toUShort($data)
1328  {
1329    if(strlen($data)>=2)
1330      return(ord($data{0})*256 + ord($data{1}));
1331    return(0);
1332  }
1333
1334  /**
1335    * Convert the given data into an unsigned Long integer (32bit)
1336    *
1337    * @param String $data       : representing the unsigned Long
1338    * @param String $endianType : the byte order
1339    * @return ULong
1340    */
1341  static public function toULong($data)
1342  {
1343    if(strlen($data)>=4)
1344        return(ord($data{0})*16777216 +
1345                ord($data{1})*65536 +
1346                ord($data{2})*256 +
1347                ord($data{3}));
1348    return(0);
1349  }
1350
1351
1352/*
1353  for debug
1354
1355  private function log($data)
1356  {
1357    if($this->logs=='') return(false);
1358    $tH=fopen(dirName($this->fileDir).'/logs.log', 'a');
1359    if($tH)
1360    {
1361      $time=microtime(true);
1362      fwrite($tH, sprintf("%s %f - %s\n", $this->logs, $time, $data));
1363
1364      fclose($tH);
1365    }
1366  }
1367*/
1368
1369
1370} // StatDB
1371
1372
1373/*
1374 used to display binary values when debug is necessary...
1375
1376function toHexDump($data)
1377  {
1378    $returned="";
1379    $maxItems=strlen($data);
1380
1381    $tmp=array();
1382    for($i=0;$i<$maxItems;$i++)
1383    {
1384      $tmp[]=sprintf("%02x", ord($data{$i}));
1385    }
1386    $returned.=implode(" ", $tmp);
1387    return($returned);
1388  }
1389*/
1390
1391
1392
1393?>
Note: See TracBrowser for help on using the repository browser.