miniActiveRecordClass: ar.php.txt

File ar.php.txt, 46.8 KB (added by mlalonde, 3 years ago)

Active Record Class in PHP5

Line 
1<?php
2require_once dirname(dirname(__FILE__)) . '/config.inc.php';
3
4/* Required Pear Files*/
5require_once 'PEAR.php';
6require_once 'MDB2.php';
7
8/* Required files */
9require_once APPROOT . 'msgController.class.php';
10
11/**
12 * tableRender defines appropriate renders for
13 * the table class to using for rendering it's data
14 *
15 * @package default
16 * @author Matthieu Lalonde
17 */
18interface tableRenderer
19{
20    /**
21     * Called when ever the first overload is received by the table class
22     *
23     * @param (tableAbstract object) &$myThis: reference to the table class for access to table information
24     * @return void
25     * @author Matthieu Lalonde
26     */
27    public  function _init($myThis);
28   
29    /**
30     * Returns the inited state if the render implements such a state
31     *
32     * @param BOOL $mode: allows false to be passed so to reset the init state of the renderer
33     * @return void
34     * @author Matthieu Lalonde
35     */
36    public  function isInited($mode = -1);
37   
38    /**
39     * Outputs the current overloaded data from the tableAbstract.
40     * The tableAbstract uses this function for __toString if it is available
41     *
42     * @param (BOOL) $asText
43     * @return void
44     * @author Matthieu Lalonde
45     */
46    public  function Output($asText = false);
47       
48    /**
49     * Called when ever the tableAbstract overloads with __set()
50     *
51     * @param (string) $node: node name
52     * @param (string) $value: node value
53     * @param (array) $myFields: fields definition
54     * @param (string) $id: passed ID if rendering multiple nodes at the same time
55     * @return void
56     * @author Matthieu Lalonde
57     */
58    public  function onSet($node, $value, $myFields, $myRow);
59   
60    /**
61     * Called whenever the tableAbstract overloads with __unset()
62     *
63     * @param (string) $node: node name
64     * @param (bool) $isAttribute: whether or not the name is an attribute of the content
65     * @param (string) $id: passed ID if rendering multiple nodes at the same time
66     * @return void
67     * @author Matthieu Lalonde
68     */
69    public  function onUnset($node, $isAttribute, $myRow);
70   
71    /**
72     * Allows for populating data from an array
73     *
74     * @param string $entries
75     * @param string $myFields
76     * @return void
77     * @author Matthieu Lalonde
78     */
79    public  function setEntries($entries, $myFields, $numRows);
80}
81
82class defaultRenderer implements tableRenderer
83{
84    public  function _init($myThis) {}
85    public  function isInited($mode = -1) {return true;}
86    public  function Output($asText = false) {}
87    public  function onSet($node, $value, $myFields, $myRow) {}
88    public  function onUnset($node, $isAttribute, $myRow) {}
89    public  function setEntries($entries, $myFields, $numRows) {}
90}
91
92/**
93* Table to JSON renderer
94*/
95class jsonRenderer implements tableRenderer
96{
97    private $myThis = null;
98   
99    function _init($myThis)
100    {
101        if (!function_exists('json_encode')) return false;
102       
103        $this->myThis =& $myThis;
104    }
105   
106   
107    public  function isInited($mode = -1) {return true;}
108    public  function onSet($node, $value, $myFields, $myRow) {}
109    public  function onUnset($node, $isAttribute, $myRow) {}
110    public  function setEntries($entries, $myFields, $numRows) {}
111   
112    public  function Output($asText = false)
113    {
114        if (!function_exists('json_encode')) return false;
115       
116        if (!isset($this->myThis) && !is_object($this->myThis)) return false;
117       
118        return json_encode($this->myThis->getEntries());
119    }
120}
121
122
123
124class xmlRenderer implements tableRenderer
125{   
126    public          $isInited   = false;
127    private         $myDoc      = null;
128    private         $myRow      = null;
129    private         $myRowNum   = 0;
130    private         $myRoot     = 'docRoot';
131    private         $myRowName  = 'entryRow';
132   
133    /**
134     * Initializes a SimpleXMLElement object
135     *
136     * @return void
137     * @author Matthieu Lalonde
138     **/
139    public  function __construct() {}
140   
141    public  function isInited($mode = -1)
142    {
143        if ($mode == false) {
144            $this->isInited = false;
145        }
146       
147        return $this->isInited;
148    }
149   
150    /**
151     * undocumented function
152     *
153     * @return void
154     * @author Matthieu Lalonde
155     **/
156    public  function _init($myThis)
157    {
158        $this->myRow    = null;
159        $this->myRowNum = null;
160       
161        if (!isset($myThis->rootXPath))
162        {
163            $this->myRowName    = $myThis->tableName;
164            $this->myRoot       = 'appData';
165        } else
166        {
167            $xpathParts = explode('/', removeLeadingSlash($myThis->rootXPath));
168            $this->myRoot       = $xpathParts[0];
169            $this->myRowName    = $xpathParts[1];
170        }
171       
172        $myRoot     = $this->myRoot;
173        $myXML      = <<<XML
174<?xml version="1.0" encoding="UTF-8"?>
175<${myRoot}/>
176XML;
177        $this->myDoc        = new SimpleXMLElement($myXML);
178       
179        $this->isInited = true;
180       
181        return true;
182    }
183   
184    /**
185     * onSet whenever the table class sets an overloads
186     *
187     * @return void
188     * @author Matthieu Lalonde
189     * @param  Takes the name of the node as a string and a mixed value
190     **/
191    public  function onSet($node, $value, $myFields, $myRow)
192    {   
193        if (!$this->isInited()) {
194            return null;
195        }
196        if (!isset($this->myDoc->{$this->myRowName}[$myRow])) {
197            if (version_compare(phpversion(), '5.1.3') == -1) {
198                throw new appException('The XML Renderer requires PHP 5.1.3 or above to function properly', MSG_FATAL);
199            } else {
200                $myXMlEntry = $this->myDoc->addChild($this->myRowName);
201            }
202        } else {
203            $myXMlEntry     = $this->myDoc->{$this->myRowName}[$myRow];
204        }
205       
206        if ($value == 'NOW()') $value = dbAbstract::getNOW();
207       
208        $fieldName = self::cleanXMLTag($node);
209        if (!tableAbstract::isAttribute($node, $myFields)) {
210            $myXMlEntry->$fieldName = $value;
211        } else {
212            $myXMlEntry[$fieldName] = $value;
213        }
214       
215        $this->myRowNum = $myRow;
216       
217        return true;
218    }
219   
220    /**
221     * onUnset whenever the table class unsets an overloads
222     *
223     * @return void
224     * @author Matthieu Lalonde
225     * @param  Takes the name of the node as a string
226     **/
227    public  function onUnset($node, $isAttribute, $myRow)
228    {
229        if (!$this->isInited) return null;
230       
231        if (!isset($this->myDoc->{$myRowName[$myRow]})) false;
232       
233        if (!$isAttribute)
234        {
235            unset($this->myDoc->{$myRowName[$myRow]});
236        } elseif ($isAttribute && isset($this->myDoc->{$myRowName[$myRow]}[$node]))
237        {
238            unset($this->myDoc->{$myRowName[$myRow]}[$node]);
239        }
240       
241        return false;
242    }
243   
244    /**
245     * Takes an array of entries and populates the XML with it
246     *
247     * @param array $entries
248     * @param array $myFields: list of data fields
249     * @return void
250     * @author Matthieu Lalonde
251     */
252    public  function setEntries($entries, $myFields, $numRows)
253    {
254        for ($i = 0;$i < $numRows;$i++)
255        {
256            if (!isset($entries[$i])) {
257                foreach ($myFields as $myField => $fieldInfo)
258                {
259                    if (!array_key_exists($myField, $entries))
260                    {
261                        $this->onSet($myField, '', $myFields, 0);
262                    }
263                }
264           
265                foreach ($entries as $entryNode => $entryValue)
266                {
267                    $this->onSet($entryNode, $entryValue, $myFields, 0);
268                }
269            } else {
270                foreach ($myFields as $myField => $fieldInfo)
271                {
272                    if (!array_key_exists($myField, $entries[$i]))
273                    {
274                        $this->onSet($myField, '', $myFields, $i);
275                    }
276                }
277               
278                foreach ($entries[$i] as $entryNode => $entryValue)
279                {
280                    $this->onSet($entryNode, $entryValue, $myFields, $i);
281                }
282            }
283        }
284    }
285   
286    final   public  function Output($asText = false)
287    {
288        if (!$this->isInited()) return false;
289        return self::outputXML($this->myDoc, $asText);
290    }
291
292    final   private static  function outputXML($myXMLDoc, $asText = null)
293    {
294        if ($asText) {
295            $xmlDoc = new DOMDocument;
296            $xmlDoc->formatOutput       = true;
297            $xmlDoc->preserveWhiteSpace = false;
298            $xmlDoc->loadXML($myXMLDoc->asXML());
299            return $xmlDoc->saveXML();
300        }
301
302        return $myXMLDoc;
303    }
304   
305    final   private static  function cleanXMLTag(&$tag)
306    {
307        $tag = trim($tag);
308        if (preg_match('/^[0-9](.*)/', $tag)) {
309            $tag = '_' . $tag;
310        }
311       
312        return $tag;
313    }
314   
315    final   private static  function getBaseDoc()
316    {
317        return <<<XML
318<?xml version="1.0" encoding="UTF-8"?>
319<request/>
320XML;
321    }
322}
323
324/**
325* Models
326**/
327class activeRecordTest extends tableAbstract
328{
329    public $rootXPath = '/appData/pageData';
330   
331    function __construct(tableRenderer &$renderer = null)
332    {
333        $this->_init(__CLASS__, $renderer);
334    }
335   
336    protected   function onCreate()
337    {
338        $tableName  = $this->myName;
339        $tableSQL   = <<<SQL
340CREATE TABLE `${tableName}` (
341  `id` mediumint(8) unsigned NOT NULL auto_increment,
342  `login` varchar(128) character set latin1 NOT NULL,
343  `password` varchar(45) character set latin1 NOT NULL,
344  `email` varchar(255) character set latin1 NOT NULL,
345  `created` datetime NOT NULL,
346  PRIMARY KEY  (`id`)
347) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
348SQL;
349       
350        return $tableSQL;
351    }
352       
353    public  function loginExists($login)
354    {
355        if ($this->fetchRow(HP('limit', 1)) > 0) return true;
356        else return false;
357    }
358   
359    protected function validate_login($data)
360    {
361        return false; /*
362        if (rand(0, 1) == 0) return true;
363        else return false;*/
364    }
365   
366    protected function validate_password($data)
367    {
368            return false; /*
369        if (rand(0, 1) == 0) return true;
370        else return false;*/
371    }
372   
373    protected function validate_email($data)
374    {
375        return false; /*
376        if (rand(0, 1) == 0) return true;
377        else return false;*/
378    }
379} // END table model Pages
380
381/**
382 * tableAbstract is a simple active record abstraction layer
383 *
384 * @package SmurfCMS
385 * @author Matthieu Lalonde
386 */
387class tableAbstract
388{
389    /* Public */
390   
391    /* Protected */
392   
393    /**
394     * List of the table's fields name
395     *
396     * @var array
397     * @access protected
398     */
399    protected   $myFields           = array();
400    /**
401     * Table's fields definition
402     *
403     * @var array
404     * @access protected
405     */
406    protected   $myFieldsDef        = array();
407    /**
408     * List of private fields
409     *
410     * @var array
411     * @access protected
412     */
413    protected   $privateFields      = array('id', 'dateCreated', 'dateUpdated');
414    /**
415     * Table name from the model
416     *
417     * @var string
418     * @access protected
419     */
420    protected   $tableName          = '';   
421    /**
422     * Table's name in the database
423     *
424     * @var string
425     * @access protected
426     */
427    protected   $myName             = '';
428    /**
429     * ID of the row in myEntries
430     *
431     * @var int
432     * @access protected
433     */
434    protected   $myRowID            = null;
435    /**
436     * Table & Overload entries
437     *
438     * @var array
439     * @access protected
440     */
441    protected   $myEntries          = null;
442    /**
443     * Number of rows from the last query
444     *
445     * @var int
446     * @access protected
447     */
448    protected   $numRows            = 0;
449   
450   
451    /* Private */
452    /**
453     * Last table query conditions (WHERE...)
454     *
455     * @var string
456     * @access private
457     */
458    private     $lastDBConditions   = null;
459    /**
460     * Last query parameters
461     *
462     * @var array
463     * @access private
464     */
465    private     $queryParams        = null;
466    /**
467     * Table renderer object
468     *
469     * @var object
470     * @access private
471     */
472    private     $myRenderer     = null;
473    /**
474     * List of all the table's queries
475     * and whether or not they failed.
476     *
477     * @var array
478     * @access private
479     */
480    private     $queriesLog         = array();
481    /**
482     * Name of the default renderer
483     *
484     * @var string
485     * @access private
486     */
487    private     $defaultRenderer    = 'defaultRenderer';
488    /**
489     * List of default query conditions
490     *
491     * @var array
492     * @access private
493     */
494    private static  $queryConditions= array(
495                                                'conditions'    => '1',                 // WHERE $conditions
496                                                'limit'         => null,
497                                                'orderBy'       => 'id',
498                                                'order'         =>  'ASC',              // Either nothing or DESC
499                                                'types'         => false,               // Usually internal!
500                                                'mode'          => MDB2_FETCHMODE_ASSOC,// Shouldn't change for overload!!!
501                                            );
502   
503    /**
504     | Basic object methods |
505     **/
506   
507    /**
508     * __construct() initilizes the table.
509     * A note of warning: this can be overloaded by a table child!
510     *
511     * @param (string) $tableName: name of this table
512     * @param (string) $tableRenderer: default table renderer object (optional)
513     * @return (bool)  true on success
514     * @author Matthieu Lalonde
515     */
516    public  function __construct($tableName, tableRenderer &$renderer = null)
517    {
518        if(get_class($this) == __CLASS__)
519        {
520            throw new fatalException('You cannot instanciate this class, please use a model!');
521            return null;
522        } else {
523            return $this->_init($tableName, $renderer);
524        }
525    }
526   
527    /**
528     * __destruct() disconnects the database and resets the query
529     *
530     * @return void
531     * @author Matthieu Lalonde
532     */
533    final   public  function __destruct()
534    {
535        $dbAbstract = Singleton::getInstance('dbAbstract');
536        $dbAbstract->disconnectDB();
537        $this->resetQuery();
538    }
539   
540    /**
541     * _init() initialize the table by fetching the fields
542     * and manipulatin the table (creation as necessary)
543     *
544     * @param (string) $tableName: name of this table
545     * @param (string) $tableRenderer: default table renderer object (optional)
546     * @return (bool)  true on success
547     * @author Matthieu Lalonde
548     */
549    final   public  function _init($tableName, tableRenderer &$renderer = null)
550    {
551        $dbAbstract         = Singleton::getInstance('dbAbstract');
552        $this->tableName    = strtolower($tableName);
553        $this->myName       = addTrailingChar(dbAbstract::$tablePrefix . strtolower($tableName), 's');
554       
555        $this->setRenderer($renderer);
556       
557        $tempFields         = $dbAbstract->fetchTableFields($this->myName);
558        if ($tempFields === false)
559        {
560            // The table does not exist, let's try and create it first.
561            if ($this->createTable(/*true to drop first!*/)) {
562                $tempFields     = $dbAbstract->fetchTableFields($this->myName);
563            }
564           
565            if ($tempFields === false)
566            {
567                die('cannot init!');
568                //$msgController    = msgController();
569                //$msgController->appendMsg(new fatalError('Cannot initialize table `'.$this->myName.'`!', ERROR_CODE_DB));
570                return false;
571            }
572        }
573       
574        foreach ($tempFields as $field)
575        {
576            $myTmpField                     = $field['Field'];
577            $this->myFields[]               = $myTmpField;
578            $this->myFieldsDef[$myTmpField] = $field;
579        }
580       
581        return true;
582    } // END _init()
583   
584    /**
585    | Class Accessor Methods |
586     **/
587   
588    /**
589     * getLastQuery returns the last database query to be executed (failure or not)
590     *
591     * @return string
592     * @author Matthieu Lalonde
593     */
594    final   public  function getLastQuery()
595    {
596        $dbAbstract = Singleton::getInstance('dbAbstract');
597       
598        return $dbAbstract->mdb2->last_query;
599    }
600   
601    /**
602     * getEntries returns the current overload entries
603     *
604     * @return array
605     * @author Matthieu Lalonde
606     */
607    final   public  function getEntries()
608    {
609        return $this->myEntries;
610    }
611   
612    /**
613     * getMyfields returns the name of the table's fields
614     *
615     * @return array
616     * @author Matthieu Lalonde
617     */
618    final   public  function getMyfields()
619    {
620        return $this->myFields;
621    }
622   
623    /**
624     * getMyTableName returns the table's short name
625     *
626     * @return sting
627     * @author Matthieu Lalonde
628     */
629    final   public  function getMyTableName()
630    {
631        return $this->tableName;
632    }
633   
634    /**
635     * getMyName returns the table's full name
636     *
637     * @return string
638     * @author Matthieu Lalonde
639     */
640    final   public  function getMyName()
641    {
642        return $this->myName;
643    }
644   
645    /**
646     * getNumRow returns the current number of rows
647     *
648     * @return int
649     * @author Matthieu Lalonde
650     */
651    final   public  function getNumRow()
652    {
653        return $this->numRows;
654    }
655   
656    /**
657     * getQueryLog return an array containing a log of the queries
658     *
659     * @return (array) array(query, (bool)result)
660     * @author Matthieu Lalonde
661     */
662    final   public  function getQueryLog()
663    {
664        return $this->queriesLog;
665    }
666   
667    /**
668     * getRowID returns the current row number of myEntries
669     *
670     * @return void
671     * @author Matthieu Lalonde
672     */
673    final   public  function getRowID()
674    {
675        return $this->myRowID;
676    }
677   
678    /**
679     * setRowID affects a row number to myEntries
680     *
681     * @param (int) $id: row number
682     * @return (bool) true on success
683     * @author Matthieu Lalonde
684     */
685   
686    final   public  function setRowID($id)
687    {
688        if (!isset($id) || !is_numeric($id))
689        {
690            $this->myRowID = false;
691            return false;
692        }
693           
694        if ($id > $this->numRows) {return false;}
695       
696        $this->myRowID  = $id;
697       
698        return true;
699    }
700   
701    /**
702     * Resets the current table state
703     * Deletes all information about the current query!
704     * Allows for the same object to be used multiple times
705     *
706     * @return void
707     * @author Matthieu Lalonde
708     */
709    final   public  function resetQuery()
710    {
711        if ($this->myRenderer)
712        {
713            $this->myRenderer->_init($this);
714        }
715       
716        $this->myRowID          = null;
717        $this->myEntries        = null;
718        $this->queryParams      = null;
719        $this->numRows          = 0;
720        $this->lastDBConditions = '';
721       
722        //$this->queriesLog[]       = 'Reseted query!';
723    }
724   
725    /**
726     * getMyRenderer returns the current renderer of the default render is there is one
727     *
728     * @return void
729     * @author Matthieu Lalonde
730     */
731    final   public function getMyRenderer()
732    {
733        if (!interface_exists('tableRenderer', true)) return false;
734       
735        if (!$this->myRenderer) {
736            if (empty($this->defaultRenderer) ||
737                !class_exists($this->defaultRenderer))
738            {
739                return false;
740            }
741           
742            $this->myRenderer   = new $this->defaultRenderer;
743        }
744       
745        return $this->myRenderer;
746    }
747   
748    /**
749     * setRenderer allows the table class to receive a new renderer
750     *
751     * @param tableRenderer $renderer
752     * @return void
753     * @author Matthieu Lalonde
754     */
755    final   public  function setRenderer(tableRenderer &$renderer)
756    {
757        $this->myRenderer = $renderer;
758        $this->myRenderer->_init($this);
759    }
760   
761    /**
762    | Class Rendering Methods |
763    **/
764   
765    /**
766     * render renders all of the overloads with the external renderer
767     *
768     * @param (bool) $asText: force the renderer to output text if it can.
769     * @return (mix) Renderer output
770     * @author Matthieu Lalonde
771     */
772    final   public  function render($asText = false)
773    {
774        try {
775            return $this->getMyRenderer()->Output($asText);
776        } catch (Exception $e) {
777            throw new fatalException('Unable to load default render!');
778        }
779       
780        return null;
781    }
782   
783    /**
784    | Class Overload Methods |
785     **/
786   
787    /**
788     * __set() overload. Stores the passed information in the myEntries named array
789     *
790     * @param (string) $name: field name
791     * @param (string) $value: field to insert
792     * @return void
793     * @author Matthieu Lalonde
794     */
795    final   public  function __set($name, $value)
796    {
797        return $this->_handleMyEntries($name, $value, 'set');
798    }
799   
800    // TODO This needs to handle "RoR like" A.R. methods
801    final   public  function __call($name, $arguments) {}
802   
803    /**
804     * __get() overload queries the myEntries array for a field of $name
805     *
806     * @param (string) $name: name of the node. but be a valid fields or __get returns false
807     * @return false if anything fails or the entry data
808     * @author Matthieu Lalonde
809     */
810    final   public  function __get($name)
811    {
812        return $this->_handleMyEntries($name, null, 'get');
813    }
814   
815    /**
816     * __toString is delegated to the rendering functions
817     *
818     * @author Matthieu Lalonde
819     */
820    final   public  function __toString()
821    {
822        return $this->render(true);
823    }
824   
825    /**
826     * __isset() overload queries the myEntries array in
827     * search of a field and returns whether or not it exists
828     *
829     * @param  (string) $name: must be a valid field name
830     * @return true is $name isset in myEntries, false in any other case
831     * @author Matthieu Lalonde
832     */
833    final   public  function __isset($name)
834    {
835        return $this->_handleMyEntries($name, null, 'isset');
836    }
837   
838    /**
839     * __unset() overload unsets the specified entry from myEntries
840     *
841     * @param (string) $name: name of the field
842     * @return void
843     * @author Matthieu Lalonde
844     */
845    final   public  function __unset($name)
846    {
847        return $this->_handleMyEntries($name, null, 'unset');
848    }
849   
850    /**
851     * handles all of the queries to myEntries from the overloads.
852     *
853     * @param (string) $name: name of the field
854     * @param (string) $value: node value (optional)
855     * @param (string) $mode: overload mode
856     * @return Multiple possibilities, false as default and failure
857     * @author Matthieu Lalonde
858     */
859    final   private function _handleMyEntries($name, $value = '', $mode = 'set')
860    {
861        if (($mode == 'set' ||is_array($this->myEntries)) && in_array($name, $this->myFields))
862        {
863            $myRowID    = $this->myRowID;
864            if (!isset($myRowID)) {$myRowID = 0;}
865           
866            if ($mode == 'set' || isset($this->myEntries[$myRowID]))
867            {
868                if ($mode == 'set' || array_key_exists($name, $this->myEntries[$myRowID]))
869                {
870                    switch ($mode)
871                    {
872                        case 'set':
873                            if (!in_array($name, $this->privateFields))
874                            {
875                                // Call the renderer's onSet event
876                                if (isset($this->myRenderer))
877                                {
878                                    $this->myRenderer->onSet($name, $value, $this->myFieldsDef, $myRowID);
879                                }
880                               
881                                $this->myRowID                      = $myRowID;
882                                $this->myEntries[$myRowID][$name]   = $value;
883                                return true;
884                            }
885                            else
886                            {
887                                throw new Exception('Cannot access private field!');
888                                return false;
889                            }
890                        break;
891                        case 'get':
892                            if (isset($this->myEntries[$myRowID][$name]))
893                            {
894                                return $this->myEntries[$myRowID][$name];
895                            }
896                        break;
897                        case 'isset':
898                            return isset($this->myEntries[$myRowID][$name]);
899                        break;
900                        case 'unset':
901                            if (isset($this->myRenderer))
902                            {
903                                // Call the renderer's onUnset event
904                                $this->myRenderer->onUnset($name, self::isAttribute($name, $this->myFieldsDef), $myRowID);
905                            }
906                           
907                            unset($this->myEntries[$myRowID][$name]);
908                        break;
909                    }
910                }
911            }
912        }
913       
914        return false;
915    } // END _handleMyEntries()
916   
917    /**
918    | Class Table Manipulation Methods |
919    **/
920   
921    /**
922     * createTable attempts to create the table in the database if the table's child
923     * provides an sql query for it
924     *
925     * @param (bool) $dropTable: whether or not to drop the table first, mainly unsed internally atm
926     * @return (bool)
927     * @author Matthieu Lalonde
928     */
929    /**
930     * onCreate() is to be overloaded by the table's child.
931     * If it returns it's creation sql query, the table will be created as necessary.
932     */
933    protected       function onCreate() {return null;}
934    final   private function createTable($dropTable = false)
935    {   
936        $createTableQuery   = $this->onCreate();
937        if ($dropTable == true)
938        {
939            $createTableQuery = 'DROP TABLE `' . $this->tableName . '`; ' . "\n\n" . $createTableQuery;
940        }
941       
942        if (!empty($createTableQuery)) {
943            $dbAbstract = Singleton::getInstance('dbAbstract');
944            $dbAbstract->mdb2->exec($createTableQuery);
945           
946            $this->queriesLog[] = array($dbAbstract->mdb2->last_query, PEAR::isError($dbAbstract->mdb2));
947           
948            if (PEAR::isError($dbAbstract->mdb2))
949            {
950                throw new fatalException('Cannot create table `' . $his->tableName . '`');
951                return false;
952            }
953           
954            return true;
955        }
956       
957        return false;
958    } // END createTableQuery()
959   
960    /**
961     * deleteData deletes the content of myEntries in the database
962     * according to the selected row or for all entries with the last query.
963     * A word of warning, this will reset the query with resetQuery() and
964     * reinit the renderer if one is available!
965     *
966     * @return affected rows if all goes well, false if anything else
967     * @author Matthieu Lalonde
968     */
969    // onDelete() is to be overloaded by the table's child for data validation if necessary.
970    protected       function onDelete() {}
971    final   public  function deleteData()
972    {
973        $doNotDelete = $this->onDelete();
974       
975        if (!$doNotDelete && is_array($this->myEntries))
976        {
977            $dbAbstract = Singleton::getInstance('dbAbstract');
978           
979            if (isset($this->myRowID) && $this->numRows > 0)
980            {
981                $myQ = 'DELETE FROM `' . $this->myName . '` WHERE `id` = ' . $this->myEntries[$this->myRowID]['id'] . ';';
982            }
983            elseif (!empty($this->lastDBConditions) && $this->numRows > 0)
984            {
985                $myQ = 'DELETE FROM `' . $this->myName . '` WHERE ' . $this->lastDBConditions . ';';
986            }
987            else
988            {
989                return false;
990            }
991           
992            $dbAbstract->mdb2->setLimit(count($this->myEntries));
993            $affected =& $dbAbstract->mdb2->exec($myQ);
994           
995            $this->queriesLog[] = array($dbAbstract->mdb2->last_query, PEAR::isError($affected));
996
997            // Always check that result is not an error
998            if (PEAR::isError($affected)) {
999                throw new appException('Error deleting');
1000                return false;
1001            }
1002           
1003            $this->resetQuery();
1004            if ($this->myRenderer)
1005            {
1006                $this->myRenderer->isInited(false);
1007            }
1008           
1009            return $affected;
1010        } else
1011        {
1012            return $doNotDelete;
1013        }
1014       
1015        return null;
1016    } // END deleteData()
1017   
1018    /**
1019     * saveData saves (update or insert) the content of myEntries to the database
1020     * according to the selected row or for all entries with the last query.
1021     *
1022     * @return void
1023     * @author Matthieu Lalonde
1024     */
1025    // onSave() is to be overloaded by the table's child for data validation if necessary.
1026    protected       function onSave() {return null;}
1027    final   public  function saveData()
1028    {   
1029        $isNew      = false;
1030        $doNotSave  = $this->onSave();
1031       
1032        if (is_array($this->myEntries) && !$doNotSave)
1033        {
1034            for($i=(count($this->myEntries) - 1);$i>=0;$i--)
1035            {   
1036                if ($doNotSave) break;
1037               
1038                foreach($this->myFieldsDef as $myKey => $myFieldDef)
1039                {
1040                    if ($doNotSave) break;
1041                   
1042                    if (!is_numeric($myKey) && !in_array($myKey, $this->privateFields) &&
1043                        method_exists($this, ($meth = 'validate_' . $myKey)))
1044                    {
1045                        if (!isset($this->myEntries[$i][$myKey])) {
1046                            $myValBefore    = null;
1047                            $myVal          = null;
1048                        } else {
1049                            $myVal          = $this->myEntries[$i][$myKey];
1050                            $myValBefore    = $this->myEntries[$i][$myKey];
1051                        }
1052                       
1053                        $doNotSave = $this->$meth($myVal);
1054                       
1055                        if ($myVal != $myValBefore)
1056                        {
1057                            $this->myEntries[$i][$myKey] = $myVal;
1058                        }
1059                    }
1060                }
1061            }
1062           
1063            if (!$doNotSave)
1064            {
1065                $dbAbstract = Singleton::getInstance('dbAbstract');
1066               
1067                if (is_numeric($this->myRowID) && $this->numRows > 0 && isset($this->myEntries[$this->myRowID]['id']))
1068                {   
1069                    $myDate     = $this->setDefaultFields('update');
1070                    $myEntry    = array_merge($myDate, $this->myEntries[$this->myRowID]);
1071                   
1072                    $myQ[] = 'UPDATE `' . $this->myName . '` SET ' .
1073                            self::mysqlUpdateValues($myEntry, $this->myFieldsDef) .
1074                            ' WHERE `id` = ' . $this->myEntries[$this->myRowID]['id'] .';';
1075           
1076                } elseif (!empty($this->lastDBConditions) && $this->numRows > 0 && isset($this->myEntries[0]['id']))
1077                {
1078                    $myQ = array();
1079                    foreach ($this->myEntries as $myEntry)
1080                    {
1081                        $myDate     = $this->setDefaultFields('update');
1082                        $myEntry    = array_merge($myDate, $myEntry);
1083                       
1084                        //if (!isset($myEntry['id'])) continue;
1085                        $myQ[] = 'UPDATE `' . $this->myName . '` SET ' .
1086                                self::mysqlUpdateValues($myEntry, $this->myFieldsDef) .
1087                                ' WHERE `id` = ' . $myEntry['id'] . ';';
1088                    }
1089                } else {
1090                    $isNew  = true;
1091                    $myQ    = array();
1092                    foreach ($this->myEntries as $myEntry)
1093                    {
1094                        if (!array_key_exists('id', $myEntry))
1095                        {
1096                            $myEntry    = array_merge(array('id' => ''), $myEntry);
1097                        }
1098                       
1099                        $myDate     = $this->setDefaultFields('create');
1100                        $myEntry    = array_merge($myDate, $myEntry);
1101                        $myQData    = self::getInsertFields($myEntry, $this->myFieldsDef);
1102                        if (empty($myQData['queryKeys']) || empty($myQData['queryValues']))
1103                        {
1104                            continue;
1105                        }
1106                       
1107                        $myQ[]      .='INSERT INTO `' . $this->myName . '` (' . $myQData['queryKeys'] . ') ' .
1108                            'VALUES (' . $myQData['queryValues'] . ');';
1109                    }
1110                }
1111           
1112                if (!isset($myQ[0]))
1113                {
1114                    return false;
1115                }
1116           
1117                foreach ($myQ as $myQuery)
1118                {
1119                    if (!$isNew) $dbAbstract->mdb2->setLimit(1);
1120                    $affected   =& $dbAbstract->mdb2->exec($myQuery);
1121                    $lastQuery  = $dbAbstract->mdb2->last_query;
1122               
1123                    $this->queriesLog[] = array($lastQuery, PEAR::isError($affected));
1124               
1125                    if (PEAR::isError($affected)) break;
1126                }
1127           
1128           
1129                // Always check that result is not an error
1130                if (PEAR::isError($affected)) {
1131                    throw new appException('Cannot save to table ' . $affected->getMessage() . $lastQuery);
1132                    return false;
1133                }
1134           
1135                if ($isNew) $this->myEntries[0]['id'] = $dbAbstract->mdb2->lastInsertID();
1136                $this->numRows  = count($this->myEntries);
1137                return $affected;
1138            }
1139        }
1140       
1141        return false;
1142    } // END saveData();
1143   
1144    final   private function setDefaultFields($mode)
1145    {
1146        $myKey  = '';
1147        switch ($mode)
1148        {
1149            case 'create':
1150                $myKey = 'dateCreated';
1151            break;
1152            case 'update':
1153                $myKey = 'dateUpdated';
1154            break;
1155            default:
1156                return array();
1157            break;
1158        }
1159       
1160        if (!array_key_exists($myKey, $this->myFieldsDef))
1161        {
1162            return array();
1163        }
1164       
1165        return array($myKey => dbAbstract::getNOW());
1166    }
1167   
1168    /**
1169     * _query generates and fetches a selection query on the table
1170     *
1171     * @param array $fieldName: specify a list of fields to fetch (option, default to all)
1172     * @param array $myConditions: an array containing the search conditions of the query
1173     * @return mixed: number of fetched rows on success, false on failure
1174     * @author Matthieu Lalonde
1175     */
1176    final   private function _query($fieldName = '*', $myConditions = null)
1177    {
1178        $dbAbstract     = Singleton::getInstance('dbAbstract');
1179        $myConditions   = self::validateQueryConditions($myConditions);
1180       
1181        if (!$dbAbstract->isReady())
1182        {
1183            return null;
1184        }
1185       
1186        // Check for ordering
1187        $queryOrder = !empty($myConditions['orderBy']) ? ' ORDER BY `' . $myConditions['orderBy'] . '` ' . $myConditions['order'] : '';
1188       
1189        // Build the mySQL query
1190        $mySQL = 'SELECT '. self::fieldsToCSV($fieldName) .' FROM `' . $this->myName .'` WHERE '. $myConditions['conditions'] . $queryOrder .';';
1191       
1192        // Set limit
1193        if (isset($myConditions['limit']) && is_numeric($myConditions['limit']) && $myConditions['limit'] >= 0) {
1194            $dbAbstract->mdb2->setLimit($myConditions['limit']);
1195        }
1196       
1197        $res =& $dbAbstract->mdb2->query($mySQL);
1198       
1199        $this->lastDBConditions = $myConditions['conditions'];
1200        $this->queriesLog[]     = array($dbAbstract->mdb2->last_query, PEAR::isError($res));
1201       
1202        try {
1203            if (PEAR::isError($res)) {
1204                throw new Exception($res->getMessage() .'Unable to query from database table "'.$this->myName.'"', MSG_ERROR);
1205            }
1206           
1207            if (isset($myConditions['types']) && is_array($myConditions['types'])) {
1208                $res->setResultTypes($myConditions['limit']);
1209            }
1210           
1211            if (isset($myConditions['limit']) && $myConditions['limit'] == 1) {
1212                $return = $res->fetchRow($myConditions['mode']);
1213            } else {
1214                $return = $res->fetchAll($myConditions['mode']);
1215            }
1216           
1217            // Free the result!
1218            $this->numRows      = count($return);
1219            $res->free();
1220           
1221            if ($this->numRows > 0)
1222            {
1223                if (is_object($this->myRenderer))
1224                {   
1225                    $this->myRenderer->setEntries($return, $this->myFieldsDef, $this->numRows);
1226                }
1227               
1228                if ($this->numRows == 1)
1229                {
1230                    $this->myRowID  = 0;
1231                } else {
1232                    $this->myRowID  = null;
1233                }
1234               
1235                $this->myEntries    = $return;
1236               
1237                return $this->numRows;
1238            } else {
1239                throw new alertException('No result for: ' . $this->getLastQuery() . "\n", MSG_ALERT);//'call_back_error_string');
1240                return false;
1241            }
1242        } catch (Exception $e) { // This really shouldn't happen
1243            throw new appException($e->getMessage(), $e->getCode());
1244           
1245            return false;
1246        }
1247    } // End _query()
1248   
1249    /**
1250     * validateQueryConditions makes sure all the required
1251     * query conditions are listed in the query parameters
1252     *
1253     * @param array $myConditions: valid set of condition
1254     * @param string $array
1255     * @return array
1256     * @author Matthieu Lalonde
1257     */
1258    final   private static  function validateQueryConditions(array $myConditions = null)
1259    {
1260        $queryConditions    = self::$queryConditions;
1261       
1262        if (is_array($myConditions)) { 
1263            foreach ($queryConditions as $myKey => $myVal)
1264            {
1265                if (array_key_exists($myKey, $myConditions) && !empty($myConditions[$myKey])) {
1266                    $queryConditions[$myKey]    = $myConditions[$myKey];
1267                }
1268            }
1269        }
1270       
1271        // Override, otherwise... bad for overload!!!
1272        $queryConditions['mode']        = MDB2_FETCHMODE_ASSOC;
1273       
1274        return $queryConditions;
1275    } // END validateQueryConditions()
1276   
1277    /**
1278     * fetch, fetchRow(s) allow querying the table
1279     * and handle prepartion of the query parameters
1280     *
1281     * @param array $searchParams: contains a field/match named array, if 'QRelation' is set, the query will use OR instead of AND, if 'QReversed' is set, the search wil be reverse
1282     * @param array $queryParams: optional named array containing query parameters (see the validation function)
1283     * @return result of _query()
1284     * @author Matthieu Lalonde
1285     */
1286    final   public  function fetchRow($searchParams, $queryParams = null) {return $this->fetch($searchParams, $queryParams);}
1287    final   public  function fetchRows($searchParams, $queryParams = null) {return $this->fetch($searchParams, $queryParams);}
1288    final   public  function fetch($searchParams, $queryParams = null)
1289    {
1290        // First reset the query!
1291        $this->resetQuery();
1292       
1293        if (!is_array($searchParams) && !is_array($queryParams))
1294        {
1295            $res = $this->fetchAll();
1296        } else {   
1297            $singleArray        = false;
1298            if (!is_array($queryParams)) {
1299                $singleArray    = true;
1300                $queryParams    = $searchParams;
1301            }
1302           
1303            $queryFields = '*';
1304            if (isset($searchParams['fieldName'])) {
1305                if (in_array($searchParams['fieldName'], $this->myFields)) {
1306                    $queryFields = $searchParams['fieldName'];
1307                }   
1308                unset($searchParams['fieldName']);
1309                unset($queryParams['fieldName']);
1310            }
1311           
1312            $or = false;
1313            if (isset($searchParams['QRelation'])) {
1314                $or = true;
1315                unset($searchParams['QRelation']);
1316                unset($queryParams['QRelation']);
1317            }
1318           
1319            $not    = false;
1320            if (isset($searchParams['QReversed'])) {
1321                $not    = true;
1322                unset($searchParams['QReversed']);
1323                unset($queryParams['QReversed']);
1324            }
1325           
1326            if (!isset($queryParams['conditions']) && isset($searchParams)) {
1327                if ($singleArray)
1328                {
1329                    foreach (self::$queryConditions as $key => $value)
1330                    {
1331                        if (isset($searchParams[$key])) unset($searchParams[$key]);
1332                    }
1333                }
1334               
1335                $queryParams['conditions']  = $this->mysqlArrayToWHERE($searchParams, $or, $not);
1336            }
1337           
1338           
1339            $res                        = $this->_query($queryFields, $queryParams);
1340            if (!$res) {
1341                $this->queryParams      = null;
1342            } else {
1343                $this->queryParams      = $queryParams;
1344            }
1345        }
1346       
1347        return $res;
1348    } // END fetch()
1349   
1350    /**
1351     * fetchAll fetches all entries for the table
1352     *
1353     * @return _query() result
1354     * @author Matthieu Lalonde
1355     */
1356    final   public  function fetchAll()
1357    {
1358        // First reset the query!
1359        $this->resetQuery();
1360       
1361        return $this->_query();
1362    }
1363   
1364    /**
1365    | Class Utilities Methods |
1366    **/
1367   
1368    /**
1369     * mysqlArrayToWHERE creates an escaped string for mysql search queries
1370     *
1371     * @param array $mArray
1372     * @param bool $or: if true use OR instead of AND
1373     * @param bool $not: if true reverse the search
1374     * @return string
1375     * @author Matthieu Lalonde
1376     */
1377    final   private function mysqlArrayToWHERE($mArray, $or = false, $not = false)
1378    {
1379        $sFields    = '';
1380        $i          = 0;
1381       
1382        if (!$or) $relation = 'AND';
1383        else $relation      = 'OR';
1384       
1385        foreach($mArray as $key => $val)
1386        {
1387            if (is_numeric($key) || !in_array($key, $this->myFields)) unset($mArray[$key]);
1388        }
1389       
1390        if (empty($mArray)) return false;
1391       
1392        while (list($paramKey, $value) = each($mArray)) {
1393            if (dbAbstract::isLike($this->myFieldsDef[$paramKey]['Type'])) {
1394                $entryRelation  = ($not == true ? 'NOT ' : '') . 'LIKE';
1395            } else {
1396                $entryRelation  = ($not == true ? '!' : '') . '=';
1397            }
1398           
1399            $sFields.= '`' . $paramKey . '` '.$entryRelation.' \'' . $value . '\'';
1400            if(++$i < count($mArray))
1401            {
1402                $sFields.= ' ' . $relation . ' ';
1403            }
1404        }
1405       
1406        return $sFields;
1407    }
1408   
1409    /**
1410     * mysqlUpdateValues creates an escaped string for mysql update queries
1411     *
1412     * @param array $mArray: named array containing the update data
1413     * @param array $types: used for quoting
1414     * @return string
1415     * @author Matthieu Lalonde
1416     */
1417    final   private static  function mysqlUpdateValues($mArray, $types)
1418    {
1419        $sValues    = "";
1420        $i          = 0;
1421       
1422        unset($mArray['id']);
1423        $count      = count($mArray);
1424        while (list($key, $value) = each($mArray)) {
1425            $sValues .= "$key = '" . dbAbstract::escapeString($value) . "'";
1426            if(++$i < $count)
1427            {
1428                $sValues .= ", ";
1429            }
1430        }
1431       
1432        return $sValues;
1433    }
1434   
1435    /**
1436     * fieldsToCSV returns a CSV formated string
1437     *
1438     * @param (array) $mArray: contains a series of field names
1439     * @return string
1440     * @author Jérémie Lauzon, Matthieu Lalonde
1441     */
1442    final   private static  function fieldsToCSV($mArray)
1443    {
1444        $sFields    = "";
1445        $i          = 0;
1446       
1447        if (!is_array($mArray))
1448        {
1449            return $mArray;
1450        }
1451
1452        while (list($key, $value) = each($mArray)) {
1453            $value    = preg_replace('/![A-Za-z0-9_-]/', '', $value);
1454           
1455            $sFields .= '`' . $value . '`';
1456            if(++$i < count($mArray))
1457            {
1458                $sFields .= ", ";
1459            }
1460        }
1461
1462        return $sFields;
1463    }
1464   
1465    /**
1466     * valuesToCSV returns a CSV formated string of escaped values
1467     *
1468     * @param (array) $mArray: contains a series of field names
1469     * @param (array) $types: contains the type for each field for quoting purposes
1470     * @return string
1471     * @author Jérémie Lauzon, Matthieu Lalonde
1472     */
1473    final   private static  function valuesToCSV($mArray, $types)
1474    {
1475        $sValues    = "";
1476        $i          = 0;
1477        if (!array_key_exists('id', $mArray)) {
1478            $mArray     = array_merge(array('id' => ''), $mArray);
1479        }
1480       
1481        foreach ($mArray as $key => $value)
1482        {
1483            $quotes     = ($value == 'NOW()' ? '' : '\'');
1484            $sValues   .= $quotes . dbAbstract::escapeString($value) . $quotes;
1485            if(++$i < count($mArray))
1486            {
1487                $sValues .= ", ";
1488            }
1489        }
1490       
1491        return $sValues;
1492    }
1493   
1494    /**
1495     * getInsertFields returns an array of prepared data
1496     * to use for a insertion query
1497     *
1498     * @param array $recordValues
1499     * @param array or string $types
1500     * @return array array('queryKeys' => fieldsToCSV($recordKeys), 'queryValues' => valuesToCSV($recordValues, $types));
1501     * @author Matthieu Lalonde
1502     */
1503    final   private static  function getInsertFields(array $recordValues, $types)
1504    {
1505        $recordKeys = array();
1506        foreach ($recordValues as $recordKey => $recordValue) {
1507            $recordKeys[]   = $recordKey;
1508        }
1509       
1510        return array('queryKeys' => self::fieldsToCSV($recordKeys), 'queryValues' => self::valuesToCSV($recordValues, $types));
1511    }
1512   
1513    /**
1514     * isAttribute returns whether or not a field is an attribute
1515     *
1516     * @param string $fieldName
1517     * @return bool
1518     * @author Matthieu Lalonde
1519     */
1520    final   public  function isAttribute($fieldName, $fieldsDef)
1521    {
1522        if (in_array($fieldName, array('id', 'uid')) ||
1523            !empty($fieldsDef[$fieldName]['Key']) ||
1524            (isset($fieldsDef[$fieldName]) &&
1525            preg_match('/(enum\((.*)\))/', $fieldsDef[$fieldName]['Type'])))
1526        {
1527            return true;
1528        }
1529       
1530        return false;
1531    }
1532   
1533} // END class tableAbstract
1534
1535/**
1536* Load the database config
1537**/
1538global $dsn;
1539initDBConfig();
1540
1541function initDBConfig()
1542{
1543    global $dsn;
1544   
1545    include HELPERS . 'dbConf.inc.php';
1546    if (!isset($dsn) || empty($dsn)) {
1547        throw new fatalException('Could not find database configuration file!');
1548    }
1549}
1550
1551/**
1552 * dbAbstract: handles database connection
1553 *
1554 * @package SmurfCMS
1555 * @author Matthieu Lalonde
1556 **/
1557class dbAbstract
1558{   
1559    /**
1560     * Table prefix in the database, if you need one
1561     *
1562     * @var string
1563     * @access public
1564     */
1565    public static   $tablePrefix        = 'xcms_';
1566   
1567    /**
1568     * Database object
1569     *
1570     * @var MDB2 object
1571     * @access public
1572     */
1573    public          $mdb2;
1574    /**
1575     * MDB2 options
1576     *
1577     * @var array
1578     * @access private
1579     */
1580    private         $dsnOptions         = array('debug'       => 2);
1581    /**
1582     * Database connection options
1583     *
1584     * @var array
1585     * @access private
1586     */
1587    private         $dsn                = array();
1588    /**
1589     * state of the database connection
1590     *
1591     * @var string
1592     * @access private
1593     */
1594    private         $dbState            = false;
1595   
1596    /**
1597     * Don't allow the construct to be overwritten
1598     *
1599     * @return void
1600     * @author Matthieu Lalonde
1601     * @access public
1602     */
1603    final   public  function __construct() {}
1604   
1605    /**
1606     * __destruct makes sure the database is disconnected first
1607     *
1608     * @return void
1609     * @author Matthieu Lalonde
1610     * @access private
1611     */
1612    final   public  function __destruct()
1613    {
1614        $dbAbstract = Singleton::getInstance('dbAbstract');
1615        $dbAbstract->disconnectDB();
1616    } // End __destruct()
1617   
1618    /**
1619     * allows for easy access to the mdb2 methods
1620     *
1621     * @param string $name: method name
1622     * @param string $params: method params
1623     * @return void
1624     * @author Matthieu Lalonde
1625     * @access public
1626     */
1627    final   public  function __call($name, $params)
1628    {
1629        $dbAbstract = Singleton::getInstance('dbAbstract');
1630        if (method_exists($dbAbstract->mdb2, $name))
1631        {
1632            return call_user_func(array($dbAbstract->mdb2, $name), $params);
1633        }
1634       
1635        return false;
1636    }
1637   
1638    /**
1639     * disconnectDB closes the database connection and reset the state
1640     *
1641     * @return void
1642     * @author Matthieu Lalonde
1643     * @access public
1644     */
1645    final   public  function disconnectDB()
1646    {
1647        $dbAbstract = Singleton::getInstance('dbAbstract');
1648        if (is_object($dbAbstract->mdb2) && !(PEAR::isError($dbAbstract->mdb2))) {
1649            $dbAbstract->mdb2->disconnect();
1650        }
1651        $dbAbstract->mdb2       = null;
1652        $dbAbstract->dbState    = false;
1653    } // END disconnectDB
1654   
1655    /**
1656     * resetConnection attempts to reset the database's connection
1657     *
1658     * @return bool: true on success
1659     * @author Matthieu Lalonde
1660     * @access public
1661     */
1662    final   public  function resetConnection()
1663    {
1664        $dbAbstract = Singleton::getInstance('dbAbstract');
1665        $dbAbstract->disconnectDB();
1666        return $dbAbstract->_init();
1667    } // END resetConnection
1668   
1669    /**
1670     * _init starts up a connection to the database
1671     *
1672     * @return bool: true on success
1673     * @author Matthieu Lalonde
1674     * @access public
1675     */
1676    final   public  function _init()
1677    {   
1678        global $dsn;
1679        $dbAbstract         = Singleton::getInstance('dbAbstract');
1680        $dbAbstract->dsn    = $dsn;
1681       
1682        $dbAbstract->mdb2   =& MDB2::connect($dbAbstract->dsn, $dbAbstract->dsnOptions);
1683       
1684        if (PEAR::isError($dbAbstract->mdb2)) {
1685            throw new appException( 'Error connecting to the database with error: '.
1686                                        $dbAbstract->mdb2->getMessage() . ' on ' . $dbAbstract->mdb2->getUserinfo()
1687                                    );
1688            return false;
1689        } else {
1690            $dbAbstract->mdb2->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
1691            $dbAbstract->dbState    = true;
1692            return true;
1693        }
1694    } // End Init
1695   
1696    /**
1697    ** Acessors
1698    **/
1699   
1700    /**
1701     * isReady checks if the database is read or can be initialized.
1702     * in the later case, an attempt will be made to init the database
1703     *
1704     * @return bool: state of the database
1705     * @author Matthieu Lalonde
1706     * @access public
1707     */
1708    final   public  function isReady($noInit = false)
1709    {
1710        $dbAbstract     = Singleton::getInstance('dbAbstract');
1711        $msgController  = msgController();
1712        if (!$noInit && !$dbAbstract->dbState && $msgController->msgCount(false, false, ERROR_CODE_DB) == 0) {
1713            return $dbAbstract->_init();
1714        }
1715       
1716        return $dbAbstract->dbState;
1717    }
1718   
1719    /**
1720    | Utilities |
1721    **/
1722   
1723    /**
1724     * fetchTableFields fetch a litle of the table's fields
1725     *
1726     * @param string $tableName: valid table name
1727     * @return array, false on failure
1728     * @author Matthieu Lalonde
1729     * @access public
1730     */
1731    final   public  function fetchTableFields($tableName)
1732    {
1733        $dbAbstract = Singleton::getInstance('dbAbstract');
1734       
1735        $sql    = 'SHOW COLUMNS FROM `'. $tableName .'`;';
1736        $fields = Array();
1737       
1738        if (!$dbAbstract->isReady())
1739        {
1740            return false;
1741        }
1742       
1743        $res =& $dbAbstract->mdb2->query($sql);
1744       
1745        if (PEAR::isError($res)) {
1746            return false;
1747        }
1748       
1749        $return = $res->fetchAll(MDB2_FETCHMODE_ASSOC);
1750       
1751        // Free the result!
1752        $res->free();
1753       
1754        if (count($return) == 1) {
1755            return $return[0];
1756        } else if (count($return) > 1) {
1757            return $return;
1758        }
1759       
1760        return false;
1761    }
1762   
1763   
1764    /**
1765     * isLike returns true is a type uses a 'LIKE' syntax rather than '='
1766     *
1767     * @param string $type
1768     * @return bool
1769     * @author Matthieu Lalonde
1770     */
1771    final   static  function isLike($type)
1772    {
1773        if (preg_match('/(text)|(varchar)|(blob)/', $type))
1774        {
1775            return true;
1776        }
1777       
1778        return false;
1779    }
1780   
1781    /**
1782     * getNOW returns a mySQL NOW() DATETIME stamp
1783     *
1784     * @return void
1785     * @author Matthieu Lalonde
1786     */
1787    final   public  static  function getNOW() {
1788        return date("Y-m-d H:i:s");
1789    }
1790   
1791    /**
1792     * getNever returns a mySQL formatted _never_ DATETIME stamp
1793     *
1794     * @return void
1795     * @author Matthieu Lalonde
1796     */
1797    final   static  function getNever() {
1798        return '0000-00-00 00:00:00';
1799    }
1800   
1801    /**
1802     * escapeString escapes an array or a string of data.
1803     *
1804     * @param mixed $data
1805     * @param mixed $type
1806     * @param bool $trim
1807     * @return $data
1808     * @author Matthieu Lalonde
1809     */
1810    final   static  function escapeString(&$data, $trim = false)
1811    {
1812        $dbAbstract = Singleton::getInstance('dbAbstract');
1813       
1814        if (!is_array($data)) {
1815            if ($data == 'NOW()') return self::getNOW();
1816           
1817            $data = mysql_real_escape_string($data);
1818           
1819            if ($trim) {
1820                $data = trim($data);
1821            }
1822        } else {
1823            foreach($data as $key => $val) {
1824                if ($val == 'NOW()') {
1825                    $data[$key] = self::getNOW();
1826                } else {
1827                    if ($trim) {
1828                        $data[$key] = trim($val);
1829                    }
1830                   
1831                    $data[$key] = mysql_real_escape_string($val);
1832                }
1833            }
1834        }
1835       
1836        return $data;
1837    }
1838}
1839
1840?><pre>
1841<?php
1842function timer($start = 0)
1843{
1844    if ($start == 0) return microtime(true) - $start;
1845    else return '1/'. round(1 / (microtime(true) - $start));
1846}
1847$begin = timer();
1848
1849$myTest = new activeRecordTest(new xmlRenderer);
1850$myTest->fetchRows(H('fieldName', '*'));
1851
1852$myTest->fetchRow(HA(H('fieldName', 'email'), H('login', 'xsmurf')));
1853
1854// Use of a custom model method
1855echo 'User xsmurf <b>' . (($iExists = $myTest->loginExists('xsmurf')) ? 'exists' : 'doesn\'t exist') . "</b>\n";
1856
1857if ($myTest->fetchAll() == 0 || !$iExists)
1858{
1859    $myTest->email      = 'xsmurf@smurfturf.net';
1860    $myTest->password   = sha1('xsmurf');
1861    $myTest->login      = 'xsmurf';
1862   
1863    if ($myTest->saveData())
1864    {
1865        echo '<b>Saved data 1</b>' . "\n";
1866    } else {
1867        echo '<b style="color red">Failed to save 1</b>' . "\n";
1868    }
1869}
1870
1871if ($myTest->fetchRows(H('fieldName', 'id'), H('limit', 6)) <= 5) {
1872    $myTest->resetQuery(); // Force reset the query we want to create a new entry but had a valid query!
1873    $myTest->login      = 'fubar'.date('u');
1874    $myTest->email      = 'fubar@'.microtime().'.net';
1875    $myTest->password   = sha1('fubar');
1876   
1877    if ($myTest->saveData())
1878    {
1879        echo '<b>Saved data 2</b>' . "\n";
1880    } else {
1881        echo '<b style="color red">Failed to save 2</b>' . "\n";
1882    }
1883   
1884    /* Update the newly inserted row */
1885   
1886    $myTest->login      = 'fubar'.microtime();
1887    $myTest->email      = 'fubar@'.microtime().'.net';
1888    $myTest->password   = sha1('fubar');
1889   
1890    if ($myTest->saveData())
1891    {
1892        echo '<b>Updated data 2</b>' . "\n";
1893    } else {
1894        echo '<b style="color red">Updated to save 2</b>' . "\n";
1895    }
1896}
1897
1898if ($myTest->fetchRows(HP('fieldName', 'id', 'QReversed', 'true', 'login', 'xsmurf')) >= 5)
1899{
1900    if ($myTest->deleteData())
1901    {
1902        echo '<b>Deleted data 1</b>' . "\n";
1903    } else {
1904        echo '<b style="color red">Failed to delete 1</b>' . "\n";
1905    }
1906}
1907
1908if ($myTest->fetchAll() > 0)
1909{
1910    for ($i = 0;$i < $myTest->getNumRow();$i++)
1911    {
1912        $myTest->setRowID($i);
1913        echo $i . ': ' . $myTest->login . ' <b>&gt;</b> ' . $myTest->email . "\n";
1914    }
1915   
1916    if ($myTest->getNumRow() > 1 && $myTest->setRowID($myTest->getNumRow() - 1)) {
1917        $myTest->email = 'last@cool.net';
1918        $myTest->saveData();
1919    }
1920   
1921    if ($myTest->fetchRows(HP('fieldName', 'id', 'QReversed', 'true', 'login', 'xsmurf')) >= 3)
1922    {
1923        for ($i = 0;$i < $myTest->getNumRow();$i++)
1924        {
1925            $myTest->setRowID($i);
1926            $myTest->email = 'last@ofit.' . microtime();
1927        }
1928       
1929        $myTest->setRowID(false); // We want to update all of the entries so we have to unset our row pointer!
1930        $myTest->saveData();
1931    }
1932}
1933
1934$myTest->fetchRows(HP('fieldName', '*'));
1935
1936?></pre><?php
1937
1938echo '<h2>tableRenderer generated XML</h2><hr />';
1939
1940?><pre><?php
1941echo htmlentities($myTest->render(true));
1942
1943?></pre><?php
1944
1945echo '<h2>jsonRenderer output</h2><hr />';
1946
1947?><pre><?php
1948
1949$myTest->setRenderer(new jsonRenderer);
1950echo str_replace('},{', "},\n{", $myTest->render());
1951
1952$time = timer($begin);
1953
1954?></pre><?php
1955echo '<h2>Queries executed in ' . $time . ' sec.</h2>';
1956echo '<h2>Query log</h2><hr />';
1957?><pre><?php
1958
1959foreach ($myTest->getQueryLog() as $query)
1960{
1961    echo '<b style="color: '. (($query[1]) ? 'red' : ' green') .'">';
1962    echo ($query[1]) ? 'Failed' : ' Passed';
1963    echo ':</b> ';
1964    echo $query[0] . "\n";
1965}
1966
1967?></pre><?php //END
1968
1969echo '<h2>Query usage sources</h2><hr />';
1970$file = file_get_contents(__FILE__);
1971$res = substr($file, strpos($file, '<pre>'));
1972$res = substr($res, 0, strrpos($res, '</pre>'));
1973highlight_string($res);
1974echo '...';
1975
1976$exp    = explode("\n", $file);
1977$lines  = count($exp);
1978
1979echo <<<XHTML
1980<div>
1981    <h1>In $lines lines of code</h1>
1982</div>
1983<hr />
1984    <h1>Code coverage</h1>
1985XHTML;
1986
1987// Hash Pair
1988function HP()
1989{
1990    $args   = func_get_args();
1991    if (!is_array($args)) return $args;
1992   
1993    $myArgs = array();
1994    for ($i=0;$i<count($args);$i++)
1995    {
1996        if ($i%2 == 0) $myKey   = $args[$i];
1997        else
1998        {
1999            $myArgs[$myKey]     = $args[$i];
2000        }
2001    }
2002   
2003    return $myArgs;
2004} // END HP()
2005
2006// Hash from Array
2007function HA() {
2008    $args   = func_get_args();
2009    if (!is_array($args)) return $args;
2010   
2011    $myArgs = array();
2012    foreach ($args as $node)
2013    {
2014        foreach($node as $key => $val)
2015        {
2016            $myArgs[$key]   = $val;
2017        }
2018    }
2019   
2020    return $myArgs;
2021} // END HA()
2022
2023// Hash map entry
2024function H($node, $val) {return array($node => $val);}
2025?>