source: trunk/lib/DB.inc.php @ 550

Last change on this file since 550 was 550, checked in by anonymous, 8 years ago

Escaped quotes from email from names.
Changed logMsg string truncation method and added version to email log msg.
Better variable testing in carry queries.
Spelling errors.
Added runtime cache to Currency.
Added logging to form validation.
More robust form validation.
Added json serialization methond to Version.

File size: 15.6 KB
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
6 *
7 * This file is part of The Strangecode Codebase.
8 *
9 * The Strangecode Codebase is free software: you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your option)
12 * any later version.
13 *
14 * The Strangecode Codebase is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/**
24 * DB.inc.php
25 *
26 * Very lightweight DB semi-abstraction layer. Mainly to catch errors with mysql_query, with some goodies.
27 *
28 * @author  Quinn Comendant <quinn@strangecode.com>
29 * @version 2.1
30 */
31
32class DB
33{
34
35    // A place to keep an object instance for the singleton pattern.
36    protected static $instance = null;
37
38    // If $db->connect has successfully opened a db connection.
39    protected $_connected = false;
40
41    // Database handle.
42    public $dbh;
43
44    // Count how many queries run during the whole instance.
45    protected $_query_count = 0;
46
47    // Hash of DB parameters.
48    protected $_params = array();
49
50    // Default parameters.
51    protected $_param_defaults = array(
52
53        // DB passwords should be set as apache environment variables in httpd.conf, readable only by root.
54        'db_server' => 'localhost',
55        'db_name' => null,
56        'db_user' => null,
57        'db_pass' => null,
58
59        // Display all SQL queries. FALSE recommended for production sites.
60        'db_always_debug' => false,
61
62        // Display db errors. FALSE recommended for production sites.
63        'db_debug' => false,
64
65        // Script stops on db error. TRUE recommended for production sites.
66        'db_die_on_failure' => true,
67    );
68
69    // Translate between HTML and MySQL character set names.
70    public $mysql_character_sets = array(
71        'utf-8' => 'utf8',
72        'iso-8859-1' => 'latin1',
73    );
74
75    // Caches.
76    protected $existing_tables;
77    protected $table_columns;
78
79    /**
80     * This method enforces the singleton pattern for this class.
81     *
82     * @return  object  Reference to the global DB object.
83     * @access  public
84     * @static
85     */
86    public static function &getInstance()
87    {
88        if (self::$instance === null) {
89            self::$instance = new self();
90        }
91
92        return self::$instance;
93    }
94
95    /**
96     * Set (or overwrite existing) parameters by passing an array of new parameters.
97     *
98     * @access public
99     *
100     * @param  array    $params     Array of parameters (key => val pairs).
101     */
102    public function setParam($params)
103    {
104        $app =& App::getInstance();
105
106        if (isset($params) && is_array($params)) {
107            // Merge new parameters with old overriding only those passed.
108            $this->_params = array_merge($this->_params, $params);
109        } else {
110            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
111        }
112    }
113
114    /**
115     * Return the value of a parameter, if it exists.
116     *
117     * @access public
118     * @param string $param        Which parameter to return.
119     * @return mixed               Configured parameter value.
120     */
121    public function getParam($param)
122    {
123        $app =& App::getInstance();
124
125        if (array_key_exists($param, $this->_params)) {
126            return $this->_params[$param];
127        } else {
128            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
129            return null;
130        }
131    }
132
133    /**
134     * Connect to database with credentials in params.
135     *
136     * @access  public
137     * @author  Quinn Comendant <quinn@strangecode.com>
138     * @since   28 Aug 2005 14:02:49
139     */
140    public function connect()
141    {
142        $app =& App::getInstance();
143
144        if (!$this->getParam('db_name') || !$this->getParam('db_user') || !$this->getParam('db_pass')) {
145            $app->logMsg('Database credentials missing.', LOG_EMERG, __FILE__, __LINE__);
146            return false;
147        }
148
149        // Connect to database. Always create a new link to the server.
150        // Connection errors are surpressed so we can do our own error management below.
151        if ($this->dbh = @mysql_connect($this->getParam('db_server'), $this->getParam('db_user'), $this->getParam('db_pass'), true)) {
152            // Select database
153            mysql_select_db($this->getParam('db_name'), $this->dbh);
154        }
155
156        // Test for connection errors.
157        if (!$this->dbh || mysql_error($this->dbh)) {
158            $mysql_error_msg = $this->dbh ? 'Codebase MySQL error: (' . mysql_errno($this->dbh) . ') ' . mysql_error($this->dbh) : 'Codebase MySQL error: Could not connect to server.';
159            $app->logMsg($mysql_error_msg, LOG_EMERG, __FILE__, __LINE__);
160
161            // Print helpful or pretty error?
162            if ($this->getParam('db_debug')) {
163                echo $mysql_error_msg . "\n";
164            }
165
166            // Die if db_die_on_failure = true, or just continue without connection.
167            return $this->_fail();
168        }
169
170        // DB connection success!
171        $this->_connected = true;
172
173        // Tell MySQL what character set we're using. Available only on MySQL versions > 4.01.01.
174        if ('' != $app->getParam('character_set') && isset($this->mysql_character_sets[mb_strtolower($app->getParam('character_set'))])) {
175            $this->query("/*!40101 SET NAMES '" . $this->mysql_character_sets[mb_strtolower($app->getParam('character_set'))] . "' */");
176        } else {
177            $app->logMsg(sprintf('%s is not a known character_set.', $app->getParam('character_set')), LOG_ERR, __FILE__, __LINE__);
178        }
179
180        return true;
181    }
182
183    /**
184     * Close db connection.
185     *
186     * @access  public
187     * @author  Quinn Comendant <quinn@strangecode.com>
188     * @since   28 Aug 2005 14:32:01
189     */
190    public function close()
191    {
192        if (!$this->_connected) {
193            return false;
194        }
195        $this->_connected = false;
196        return mysql_close($this->dbh);
197    }
198
199    /*
200    *
201    *
202    * @access   public
203    * @param
204    * @return
205    * @author   Quinn Comendant <quinn@strangecode.com>
206    * @version  1.0
207    * @since    03 Jul 2013 14:50:23
208    */
209    public function reconnect()
210    {
211        $this->close();
212        $this->connect();
213    }
214
215    /*
216    * Die only if db_die_on_failure is true. This will be set to false for some cases
217    * when a database is not required for web app functionality.
218    *
219    * @access   public
220    * @param    string  $msg Print $msg when dying.
221    * @return   bool    false If we don't die.
222    * @author   Quinn Comendant <quinn@strangecode.com>
223    * @version  1.0
224    * @since    15 Jan 2007 15:59:00
225    */
226    protected function _fail()
227    {
228        $app =& App::getInstance();
229
230        if ($this->getParam('db_die_on_failure')) {
231            if (!$app->cli) {
232                // For http requests, send a Service Unavailable header.
233                header(' ', true, 503);
234                echo _("This page is temporarily unavailable. Please try again in a few minutes.");
235            }
236            die;
237        } else {
238            return false;
239        }
240    }
241
242    /**
243     * Return the current database handler.
244     *
245     * @access  public
246     * @return  resource Current value of $this->dbh.
247     * @author  Quinn Comendant <quinn@strangecode.com>
248     * @since   20 Aug 2005 13:50:36
249     */
250    public function getDBH()
251    {
252        if (!$this->_connected) {
253            return false;
254        }
255
256        return $this->dbh;
257    }
258
259    /**
260     * Returns connection status
261     *
262     * @access  public
263     * @author  Quinn Comendant <quinn@strangecode.com>
264     * @since   28 Aug 2005 14:58:09
265     */
266    public function isConnected()
267    {
268        return (true === $this->_connected);
269    }
270
271    /**
272     * Returns a properly escaped string using mysql_real_escape_string() with the current connection's charset.
273     *
274     * @access  public
275     * @param   string  $string     Input string to be sent as SQL query.
276     * @return  string              Escaped string from mysql_real_escape_string()
277     * @author  Quinn Comendant <quinn@strangecode.com>
278     * @since   06 Mar 2006 16:41:32
279     */
280    public function escapeString($string)
281    {
282        if (!$this->_connected) {
283            return false;
284        }
285
286        return mysql_real_escape_string($string, $this->dbh);
287    }
288
289    /**
290     * A wrapper for mysql_query. Allows us to set the database link_identifier,
291     * to trap errors and ease debugging.
292     *
293     * @param  string  $query   The SQL query to execute
294     * @param  bool    $debug   If true, prints debugging info
295     * @return resource         Query identifier
296     */
297    public function query($query, $debug=false)
298    {
299        $app =& App::getInstance();
300
301        if (!$this->_connected) {
302           return false;
303        }
304
305        $this->_query_count++;
306
307        $debugqry = preg_replace("/\n[\t ]+/", "\n", $query);
308        if ($this->getParam('db_always_debug') || $debug) {
309            echo "<!-- ----------------- Query $this->_query_count ---------------------\n$debugqry\n-->\n";
310        }
311
312        // Ensure we have an active connection.
313        // If we continue on a dead connection we might experience a "MySQL server has gone away" error.
314        // http://dev.mysql.com/doc/refman/5.0/en/gone-away.html
315        if (!mysql_ping($this->dbh)) {
316            $app->logMsg(sprintf('MySQL ping failed; reconnecting
 ("%s")', truncate(trim($debugqry), 150)), LOG_DEBUG, __FILE__, __LINE__);
317            $this->reconnect();
318        }
319
320        // Execute!
321        $qid = mysql_query($query, $this->dbh);
322
323        // Error checking.
324        if (!$qid || mysql_error($this->dbh)) {
325            $app->logMsg(sprintf('MySQL error %s: %s in query: %s', mysql_errno($this->dbh), mysql_error($this->dbh), $debugqry), LOG_EMERG, __FILE__, __LINE__);
326            if ($this->getParam('db_debug')) {
327                if (!$app->cli) {
328                    echo '<pre style="padding:2em; background:#ddd; font:9px monaco;">' . wordwrap(mysql_error($this->dbh)) . '<hr>' . htmlspecialchars($debugqry) . '</pre>';
329                }
330            }
331            // Die if db_die_on_failure = true, or just continue without connection
332            return $this->_fail();
333        }
334
335        return $qid;
336    }
337
338    /**
339     * Loads a list of tables in the current database into an array, and returns
340     * true if the requested table is found. Use this function to enable/disable
341     * functionality based upon the current available db tables or to dynamically
342     * create tables if missing.
343     *
344     * @param  string $table                The name of the table to search.
345     * @param  bool   $use_cached_results   Get fresh table info (in case DB changed).
346     * @return bool                         true if given $table exists.
347     */
348    public function tableExists($table, $use_cached_results=true)
349    {
350        $app =& App::getInstance();
351
352        if (!$this->_connected) {
353            return false;
354        }
355
356        if (!isset($this->existing_tables) || !$use_cached_results) {
357            $this->existing_tables = array();
358            $qid = $this->query("SHOW TABLES");
359            while (list($row) = mysql_fetch_row($qid)) {
360                $this->existing_tables[] = $row;
361            }
362        }
363        if (in_array($table, $this->existing_tables)) {
364            return true;
365        } else {
366            $app->logMsg(sprintf('Nonexistent DB table: %s.%s', $this->getParam('db_name'), $table), LOG_INFO, __FILE__, __LINE__);
367            return false;
368        }
369    }
370
371    /**
372     * Tests if the given array of columns exists in the specified table.
373     *
374     * @param  string $table                The name of the table to search.
375     * @param  array  $columns              An array of column names.
376     * @param  bool   $strict               Exact schema match. If TRUE, test if *only* the given columns exist. If FALSE, test if given columns exist (possibly amongst others).
377     * @param  bool   $use_cached_results   Get fresh table info (in case DB changed).
378     * @return bool                         true if column(s) exist.
379     */
380    public function columnExists($table, $columns, $strict=true, $use_cached_results=true)
381    {
382        $app =& App::getInstance();
383
384        if (!$this->_connected) {
385            $app->logMsg(sprintf('No DB connection to run %s', __METHOD__), LOG_NOTICE, __FILE__, __LINE__);
386            return false;
387        }
388
389        // Ensure the table exists.
390        if (!$this->tableExists($table, $use_cached_results)) {
391            $app->logMsg(sprintf('Table does not exist: %s', $table), LOG_DEBUG, __FILE__, __LINE__);
392            return false;
393        }
394
395        // For single-value columns.
396        if (!is_array($columns)) {
397            $columns = array($columns);
398        }
399
400        if (!isset($this->table_columns[$table]) || !$use_cached_results) {
401            // Populate and cache array of current columns for this table.
402            $this->table_columns[$table] = array();
403            $qid = $this->query("DESCRIBE $table");
404            while ($row = mysql_fetch_row($qid)) {
405                $this->table_columns[$table][] = $row[0];
406            }
407        }
408
409        if ($strict) {
410            // Do an exact comparison of table schemas.
411            sort($columns);
412            sort($this->table_columns[$table]);
413            return $this->table_columns[$table] == $columns;
414        } else {
415            // Only check that the specified columns are available in the table.
416            $match_columns = array_intersect($this->table_columns[$table], $columns);
417            sort($columns);
418            sort($match_columns);
419            return $match_columns == $columns;
420        }
421    }
422
423    /*
424    * Return the total number of queries executed thus far.
425    *
426    * @access   public
427    * @return   int Number of queries
428    * @author   Quinn Comendant <quinn@strangecode.com>
429    * @version  1.0
430    * @since    15 Jun 2006 11:46:05
431    */
432    public function numQueries()
433    {
434        return $this->_query_count;
435    }
436
437    /**
438     * Reset cached items.
439     *
440     * @access  public
441     * @author  Quinn Comendant <quinn@strangecode.com>
442     * @since   28 Aug 2005 22:10:50
443     */
444    public function resetCache()
445    {
446        $this->existing_tables = null;
447        $this->table_columns = null;
448    }
449
450    /**
451     * Returns the values of an ENUM or SET column, returning them as an array.
452     *
453     * @param  string $db_table   database table to lookup
454     * @param  string $db_col     database column to lookup
455     * @param  bool   $sort          Sort the output.
456     * @return array    Array of the set/enum values on success, false on failure.
457     */
458    public function getEnumValues($db_table, $db_col, $sort=false)
459    {
460        $app =& App::getInstance();
461
462        $qid = $this->query("SHOW COLUMNS FROM " . $this->escapeString($db_table) . " LIKE '" . $this->escapeString($db_col) . "'", false);
463
464        $row = mysql_fetch_row($qid);
465        if (preg_match('/^enum|^set/i', $row[1]) && preg_match_all("/'([^']*)'/", $row[1], $matches)) {
466            if ($sort) {
467                natsort($matches[1]);
468            }
469            return $matches[1];
470        } else {
471            $app->logMsg(sprintf('No set or enum fields found in %s.%s', $db_table, $db_col), LOG_ERR, __FILE__, __LINE__);
472            return false;
473        }
474    }
475
476
477} // End.
478
Note: See TracBrowser for help on using the repository browser.