Make WordPress Core

Ticket #6305: wp-db.php

File wp-db.php, 17.4 KB (added by ryan, 18 years ago)
Line 
1<?php
2//  WordPress DB Class
3
4//  ORIGINAL CODE FROM:
5//  Justin Vincent (justin@visunet.ie)
6//      http://php.justinvincent.com
7
8define('EZSQL_VERSION', 'WP1.25');
9define('OBJECT', 'OBJECT', true);
10define('OBJECT_K', 'OBJECT_K', false);
11define('ARRAY_A', 'ARRAY_A', false);
12define('ARRAY_N', 'ARRAY_N', false);
13
14if (!defined('SAVEQUERIES'))
15        define('SAVEQUERIES', false);
16
17class wpdb {
18
19        var $show_errors = false;
20        var $suppress_errors = false;
21        var $last_error = '';
22        var $num_queries = 0;
23        var $last_query;
24        var $col_info;
25        var $queries;
26        var $prefix = '';
27        var $ready = false;
28
29        // Our tables
30        var $posts;
31        var $users;
32        var $categories;
33        var $post2cat;
34        var $comments;
35        var $links;
36        var $options;
37        var $postmeta;
38        var $usermeta;
39        var $terms;
40        var $term_taxonomy;
41        var $term_relationships;
42        var $tables = array('users', 'usermeta', 'posts', 'categories', 'post2cat', 'comments', 'links', 'link2cat', 'options',
43                        'postmeta', 'terms', 'term_taxonomy', 'term_relationships');
44        var $charset;
45        var $collate;
46        var $db_user;
47        var $db_password;
48        var $db_name;
49        var $db_host;
50
51        /**
52         * Connects to the database server and selects a database
53         * @param string $dbuser
54         * @param string $dbpassword
55         * @param string $dbname
56         * @param string $dbhost
57         */
58        function wpdb($dbuser, $dbpassword, $dbname, $dbhost) {
59                return $this->__construct($dbuser, $dbpassword, $dbname, $dbhost);
60        }
61
62        function __construct($dbuser, $dbpassword, $dbname, $dbhost) {
63                register_shutdown_function(array(&$this, "__destruct"));
64
65                if ( defined('WP_DEBUG') and WP_DEBUG == true )
66                        $this->show_errors();
67
68                if ( defined('DB_CHARSET') )
69                        $this->charset = DB_CHARSET;
70
71                if ( defined('DB_COLLATE') )
72                        $this->collate = DB_COLLATE;
73
74                $this->db_user = $dbuser;
75                $this->db_password = $dbpassword;
76                $this->db_name = $dbname;
77                $this->db_host = $dbhost;
78
79                $this->connect_to_db();
80        }
81
82        function __destruct() {
83                return true;
84        }
85
86        function connect_to_db() {
87                $this->dbh = @mysql_connect($this->db_host, $this->db_user, $this->db_password, true);
88                if ( !$this->dbh ) {
89                        $this->bail("
90<h1>Error establishing a database connection</h1>
91<p>This either means that the username and password information in your <code>wp-config.php</code> file is incorrect or we can't contact the database server at <code>$dbhost</code>. This could mean your host's database server is down.</p>
92<ul>
93        <li>Are you sure you have the correct username and password?</li>
94        <li>Are you sure that you have typed the correct hostname?</li>
95        <li>Are you sure that the database server is running?</li>
96</ul>
97<p>If you're unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href='http://wordpress.org/support/'>WordPress Support Forums</a>.</p>
98");
99                        return;
100                }
101
102                $this->ready = true;
103
104                if ( !empty($this->charset) && version_compare(mysql_get_server_info($this->dbh), '4.1.0', '>=') )
105                        $this->query("SET NAMES '$this->charset'");
106
107                $this->select($this->db_name);
108        }
109
110        function check_connection() {
111                $maxcount = 2;
112                $count = 0;
113
114                $ping = mysql_ping( $this->dbh ) ;
115
116                while ( !$ping && $count < $maxcount ) {
117                        @mysql_close($this->dbh);
118                        $this->connect_to_db();
119
120                        $ping = mysql_ping( $this->dbh ) ;
121
122                        if ( $ping )
123                                break;
124
125                        sleep(2);
126                        $count++;
127                }
128
129                if ( !$ping )
130                        $this->bail('Lost connection to server');
131        }
132 
133        function set_prefix($prefix) {
134
135                if ( preg_match('|[^a-z0-9_]|i', $prefix) )
136                        return new WP_Error('invalid_db_prefix', 'Invalid database prefix'); // No gettext here
137
138                $old_prefix = $this->prefix;
139                $this->prefix = $prefix;
140
141                foreach ( $this->tables as $table )
142                        $this->$table = $this->prefix . $table;
143
144                if ( defined('CUSTOM_USER_TABLE') )
145                        $this->users = CUSTOM_USER_TABLE;
146
147                if ( defined('CUSTOM_USER_META_TABLE') )
148                        $this->usermeta = CUSTOM_USER_META_TABLE;
149
150                return $old_prefix;
151        }
152
153        /**
154         * Selects a database using the current class's $this->dbh
155         * @param string $db name
156         */
157        function select($db) {
158                if (!@mysql_select_db($db, $this->dbh)) {
159                        $this->ready = false;
160                        $this->bail("
161<h1>Can&#8217;t select database</h1>
162<p>We were able to connect to the database server (which means your username and password is okay) but not able to select the <code>$db</code> database.</p>
163<ul>
164<li>Are you sure it exists?</li>
165<li>Does the user <code>".DB_USER."</code> have permission to use the <code>$db</code> database?</li>
166<li>On some systems the name of your database is prefixed with your username, so it would be like username_wordpress. Could that be the problem?</li>
167</ul>
168<p>If you don't know how to setup a database you should <strong>contact your host</strong>. If all else fails you may find help at the <a href='http://wordpress.org/support/'>WordPress Support Forums</a>.</p>");
169                        return;
170                }
171        }
172
173        /**
174         * Escapes content for insertion into the database, for security
175         *
176         * @param string $string
177         * @return string query safe string
178         */
179        function escape($string) {
180                return addslashes( $string );
181                // Disable rest for now, causing problems
182                /*
183                if( !$this->dbh || version_compare( phpversion(), '4.3.0' ) == '-1' )
184                        return mysql_escape_string( $string );
185                else
186                        return mysql_real_escape_string( $string, $this->dbh );
187                */
188        }
189
190        /**
191         * Escapes content by reference for insertion into the database, for security
192         * @param string $s
193         */
194        function escape_by_ref(&$s) {
195                $s = $this->escape($s);
196        }
197
198        /**
199         * Prepares a SQL query for safe use, using sprintf() syntax
200         */
201        function prepare($args=NULL) {
202                if ( NULL === $args )
203                        return;
204                $args = func_get_args();
205                $query = array_shift($args);
206                $query = str_replace("'%s'", '%s', $query); // in case someone mistakenly already singlequoted it
207                $query = str_replace('"%s"', '%s', $query); // doublequote unquoting
208                $query = str_replace('%s', "'%s'", $query); // quote the strings
209                array_walk($args, array(&$this, 'escape_by_ref'));
210                return @vsprintf($query, $args);
211        }
212
213        // ==================================================================
214        //      Print SQL/DB error.
215
216        function print_error($str = '') {
217                global $EZSQL_ERROR;
218
219                if (!$str) $str = mysql_error($this->dbh);
220                $EZSQL_ERROR[] =
221                array ('query' => $this->last_query, 'error_str' => $str);
222
223                if ( $this->suppress_errors )
224                        return false;
225
226                $error_str = "WordPress database error $str for query $this->last_query";
227                if ( $caller = $this->get_caller() )
228                        $error_str .= " made by $caller";
229
230                $log_error = true;
231                if ( ! function_exists('error_log') )
232                        $log_error = false;
233
234                $log_file = @ini_get('error_log');
235                if ( !empty($log_file) && ('syslog' != $log_file) && !is_writable($log_file) )
236                        $log_error = false;
237
238                if ( $log_error )
239                        @error_log($error_str, 0);
240
241                // Is error output turned on or not..
242                if ( !$this->show_errors )
243                        return false;
244
245                $str = htmlspecialchars($str, ENT_QUOTES);
246                $query = htmlspecialchars($this->last_query, ENT_QUOTES);
247
248                // If there is an error then take note of it
249                print "<div id='error'>
250                <p class='wpdberror'><strong>WordPress database error:</strong> [$str]<br />
251                <code>$query</code></p>
252                </div>";
253        }
254
255        // ==================================================================
256        //      Turn error handling on or off..
257
258        function show_errors( $show = true ) {
259                $errors = $this->show_errors;
260                $this->show_errors = $show;
261                return $errors;
262        }
263
264        function hide_errors() {
265                $show = $this->show_errors;
266                $this->show_errors = false;
267                return $show;
268        }
269
270        function suppress_errors( $suppress = true ) {
271                $errors = $this->suppress_errors;
272                $this->suppress_errors = $suppress;
273                return $errors;
274        }
275
276        // ==================================================================
277        //      Kill cached query results
278
279        function flush() {
280                $this->last_result = array();
281                $this->col_info = null;
282                $this->last_query = null;
283        }
284
285        // ==================================================================
286        //      Basic Query     - see docs for more detail
287
288        function query($query) {
289                if ( ! $this->ready )
290                        return false;
291
292                // filter the query, if filters are available
293                // NOTE: some queries are made before the plugins have been loaded, and thus cannot be filtered with this method
294                if ( function_exists('apply_filters') )
295                        $query = apply_filters('query', $query);
296
297                // initialise return
298                $return_val = 0;
299                $this->flush();
300
301                // Log how the function was called
302                $this->func_call = "\$db->query(\"$query\")";
303
304                // Keep track of the last query for debug..
305                $this->last_query = $query;
306
307                // Perform the query via std mysql_query function..
308                if (SAVEQUERIES)
309                        $this->timer_start();
310
311                $this->result = @mysql_query($query, $this->dbh);
312                ++$this->num_queries;
313
314                if (SAVEQUERIES)
315                        $this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() );
316
317                // If there is an error then take note of it..
318                if ( $this->last_error = mysql_error($this->dbh) ) {
319                        $errno = mysql_errno();
320                        if ( 2006 == $errno || 2013 == $errno ) {
321                                $this->check_connection();
322                                $this->result = @mysql_query($query, $this->dbh);
323                                if ( $this->last_error = mysql_error($this->dbh) ) {
324                                        $this->print_error();
325                                        return false;
326                                }
327                        } else {
328                                $this->print_error();
329                                return false;
330                        }
331                }
332
333                if ( preg_match("/^\\s*(insert|delete|update|replace) /i",$query) ) {
334                        $this->rows_affected = mysql_affected_rows($this->dbh);
335                        // Take note of the insert_id
336                        if ( preg_match("/^\\s*(insert|replace) /i",$query) ) {
337                                $this->insert_id = mysql_insert_id($this->dbh);
338                        }
339                        // Return number of rows affected
340                        $return_val = $this->rows_affected;
341                } else {
342                        $i = 0;
343                        while ($i < @mysql_num_fields($this->result)) {
344                                $this->col_info[$i] = @mysql_fetch_field($this->result);
345                                $i++;
346                        }
347                        $num_rows = 0;
348                        while ( $row = @mysql_fetch_object($this->result) ) {
349                                $this->last_result[$num_rows] = $row;
350                                $num_rows++;
351                        }
352
353                        @mysql_free_result($this->result);
354
355                        // Log number of rows the query returned
356                        $this->num_rows = $num_rows;
357
358                        // Return number of rows selected
359                        $return_val = $this->num_rows;
360                }
361
362                return $return_val;
363        }
364
365        /**
366         * Insert an array of data into a table
367         * @param string $table WARNING: not sanitized!
368         * @param array $data should not already be SQL-escaped
369         * @return mixed results of $this->query()
370         */
371        function insert($table, $data) {
372                $data = add_magic_quotes($data);
373                $fields = array_keys($data);
374                return $this->query("INSERT INTO $table (`" . implode('`,`',$fields) . "`) VALUES ('".implode("','",$data)."')");
375        }
376
377        /**
378         * Update a row in the table with an array of data
379         * @param string $table WARNING: not sanitized!
380         * @param array $data should not already be SQL-escaped
381         * @param array $where a named array of WHERE column => value relationships.  Multiple member pairs will be joined with ANDs.  WARNING: the column names are not currently sanitized!
382         * @return mixed results of $this->query()
383         */
384        function update($table, $data, $where){
385                $data = add_magic_quotes($data);
386                $bits = $wheres = array();
387                foreach ( array_keys($data) as $k )
388                        $bits[] = "`$k` = '$data[$k]'";
389
390                if ( is_array( $where ) )
391                        foreach ( $where as $c => $v )
392                                $wheres[] = "$c = '" . $this->escape( $v ) . "'";
393                else
394                        return false;
395                return $this->query( "UPDATE $table SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres ) . ' LIMIT 1' );
396        }
397
398        /**
399         * Get one variable from the database
400         * @param string $query (can be null as well, for caching, see codex)
401         * @param int $x = 0 row num to return
402         * @param int $y = 0 col num to return
403         * @return mixed results
404         */
405        function get_var($query=null, $x = 0, $y = 0) {
406                $this->func_call = "\$db->get_var(\"$query\",$x,$y)";
407                if ( $query )
408                        $this->query($query);
409
410                // Extract var out of cached results based x,y vals
411                if ( !empty( $this->last_result[$y] ) ) {
412                        $values = array_values(get_object_vars($this->last_result[$y]));
413                }
414
415                // If there is a value return it else return null
416                return (isset($values[$x]) && $values[$x]!=='') ? $values[$x] : null;
417        }
418
419        /**
420         * Get one row from the database
421         * @param string $query
422         * @param string $output ARRAY_A | ARRAY_N | OBJECT
423         * @param int $y row num to return
424         * @return mixed results
425         */
426        function get_row($query = null, $output = OBJECT, $y = 0) {
427                $this->func_call = "\$db->get_row(\"$query\",$output,$y)";
428                if ( $query )
429                        $this->query($query);
430                else
431                        return null;
432
433                if ( !isset($this->last_result[$y]) )
434                        return null;
435
436                if ( $output == OBJECT ) {
437                        return $this->last_result[$y] ? $this->last_result[$y] : null;
438                } elseif ( $output == ARRAY_A ) {
439                        return $this->last_result[$y] ? get_object_vars($this->last_result[$y]) : null;
440                } elseif ( $output == ARRAY_N ) {
441                        return $this->last_result[$y] ? array_values(get_object_vars($this->last_result[$y])) : null;
442                } else {
443                        $this->print_error(" \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N");
444                }
445        }
446
447        /**
448         * Gets one column from the database
449         * @param string $query (can be null as well, for caching, see codex)
450         * @param int $x col num to return
451         * @return array results
452         */
453        function get_col($query = null , $x = 0) {
454                if ( $query )
455                        $this->query($query);
456
457                $new_array = array();
458                // Extract the column values
459                for ( $i=0; $i < count($this->last_result); $i++ ) {
460                        $new_array[$i] = $this->get_var(null, $x, $i);
461                }
462                return $new_array;
463        }
464
465        /**
466         * Return an entire result set from the database
467         * @param string $query (can also be null to pull from the cache)
468         * @param string $output ARRAY_A | ARRAY_N | OBJECT_K | OBJECT
469         * @return mixed results
470         */
471        function get_results($query = null, $output = OBJECT) {
472                $this->func_call = "\$db->get_results(\"$query\", $output)";
473
474                if ( $query )
475                        $this->query($query);
476                else
477                        return null;
478
479                if ( $output == OBJECT ) {
480                        // Return an integer-keyed array of row objects
481                        return $this->last_result;
482                } elseif ( $output == OBJECT_K ) {
483                        // Return an array of row objects with keys from column 1
484                        // (Duplicates are discarded)
485                        foreach ( $this->last_result as $row ) {
486                                $key = array_shift( get_object_vars( $row ) );
487                                if ( !isset( $new_array[ $key ] ) )
488                                        $new_array[ $key ] = $row;
489                        }
490                        return $new_array;
491                } elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
492                        // Return an integer-keyed array of...
493                        if ( $this->last_result ) {
494                                $i = 0;
495                                foreach( $this->last_result as $row ) {
496                                        if ( $output == ARRAY_N ) {
497                                                // ...integer-keyed row arrays
498                                                $new_array[$i] = array_values( get_object_vars( $row ) );
499                                        } else {
500                                                // ...column name-keyed row arrays
501                                                $new_array[$i] = get_object_vars( $row );
502                                        }
503                                        ++$i;
504                                }
505                                return $new_array;
506                        }
507                }
508        }
509
510        /**
511         * Grabs column metadata from the last query
512         * @param string $info_type one of name, table, def, max_length, not_null, primary_key, multiple_key, unique_key, numeric, blob, type, unsigned, zerofill
513         * @param int $col_offset 0: col name. 1: which table the col's in. 2: col's max length. 3: if the col is numeric. 4: col's type
514         * @return mixed results
515         */
516        function get_col_info($info_type = 'name', $col_offset = -1) {
517                if ( $this->col_info ) {
518                        if ( $col_offset == -1 ) {
519                                $i = 0;
520                                foreach($this->col_info as $col ) {
521                                        $new_array[$i] = $col->{$info_type};
522                                        $i++;
523                                }
524                                return $new_array;
525                        } else {
526                                return $this->col_info[$col_offset]->{$info_type};
527                        }
528                }
529        }
530
531        /**
532         * Starts the timer, for debugging purposes
533         */
534        function timer_start() {
535                $mtime = microtime();
536                $mtime = explode(' ', $mtime);
537                $this->time_start = $mtime[1] + $mtime[0];
538                return true;
539        }
540
541        /**
542         * Stops the debugging timer
543         * @return int total time spent on the query, in milliseconds
544         */
545        function timer_stop() {
546                $mtime = microtime();
547                $mtime = explode(' ', $mtime);
548                $time_end = $mtime[1] + $mtime[0];
549                $time_total = $time_end - $this->time_start;
550                return $time_total;
551        }
552
553        /**
554         * Wraps fatal errors in a nice header and footer and dies.
555         * @param string $message
556         */
557        function bail($message) { // Just wraps errors in a nice header and footer
558                if ( !$this->show_errors ) {
559                        if ( class_exists('WP_Error') )
560                                $this->error = new WP_Error('500', $message);
561                        else
562                                $this->error = $message;
563                        return false;
564                }
565                wp_die($message);
566        }
567
568        /**
569         * Checks wether of not the database version is high enough to support the features WordPress uses
570         * @global $wp_version
571         */
572        function check_database_version()
573        {
574                global $wp_version;
575                // Make sure the server has MySQL 4.0
576                $mysql_version = preg_replace('|[^0-9\.]|', '', @mysql_get_server_info($this->dbh));
577                if ( version_compare($mysql_version, '4.0.0', '<') )
578                        return new WP_Error('database_version',sprintf(__('<strong>ERROR</strong>: WordPress %s requires MySQL 4.0.0 or higher'), $wp_version));
579        }
580
581        /**
582         * This function is called when WordPress is generating the table schema to determine wether or not the current database
583         * supports or needs the collation statements.
584         */
585        function supports_collation()
586        {
587                return ( version_compare(mysql_get_server_info($this->dbh), '4.1.0', '>=') );
588        }
589
590        /**
591         * Get the name of the function that called wpdb.
592         * @return string the name of the calling function
593         */
594        function get_caller() {
595                // requires PHP 4.3+
596                if ( !is_callable('debug_backtrace') )
597                        return '';
598
599                $bt = debug_backtrace();
600                $caller = '';
601
602                foreach ( $bt as $trace ) {
603                        if ( @$trace['class'] == __CLASS__ )
604                                continue;
605                        elseif ( strtolower(@$trace['function']) == 'call_user_func_array' )
606                                continue;
607                        elseif ( strtolower(@$trace['function']) == 'apply_filters' )
608                                continue;
609                        elseif ( strtolower(@$trace['function']) == 'do_action' )
610                                continue;
611
612                        $caller = $trace['function'];
613                        break;
614                }
615                return $caller;
616        }
617
618}
619
620if ( ! isset($wpdb) )
621        $wpdb = new wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
622?>