Don't abuse isValidSqlLink() for checking results
[mailer.git] / inc / db / lib-mysqli.php
1 <?php
2 /************************************************************************
3  * Mailer v0.2.1-FINAL                                Start: 06/12/2013 *
4  * ===================                          Last change: 06/12/2013 *
5  *                                                                      *
6  * -------------------------------------------------------------------- *
7  * File              : lib-mysqli.php                                   *
8  * -------------------------------------------------------------------- *
9  * Short description : Database layer for MySQL 3/4/5 server (MySQLi)   *
10  * -------------------------------------------------------------------- *
11  * Kurzbeschreibung  : Datenbankschicht fuer MySQL 3/4/5 Server (MySQLi)*
12  * -------------------------------------------------------------------- *
13  * Copyright (c) 2003 - 2009 by Roland Haeder                           *
14  * Copyright (c) 2009 - 2016 by Mailer Developer Team                   *
15  * For more information visit: http://mxchange.org                      *
16  *                                                                      *
17  * This program is free software; you can redistribute it and/or modify *
18  * it under the terms of the GNU General Public License as published by *
19  * the Free Software Foundation; either version 2 of the License, or    *
20  * (at your option) any later version.                                  *
21  *                                                                      *
22  * This program is distributed in the hope that it will be useful,      *
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
25  * GNU General Public License for more details.                         *
26  *                                                                      *
27  * You should have received a copy of the GNU General Public License    *
28  * along with this program; if not, write to the Free Software          *
29  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,               *
30  * MA  02110-1301  USA                                                  *
31  ************************************************************************/
32
33 // Some security stuff...
34 if (!defined('__SECURITY')) {
35         die();
36 } // END - if
37
38 // SQL queries
39 function sqlQuery ($sqlString, $file, $line, $enableCodes = TRUE) {
40         // Is there cache?
41         if (!isset($GLOBALS[__FUNCTION__][$sqlString])) {
42                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Called: ' . $sqlString);
43
44                 // Trim SQL string
45                 $sqlStringModified = trim($sqlString);
46
47                 // Empty query string or link is not up?
48                 if (empty($sqlStringModified)) {
49                         // Empty SQL string!
50                         reportBug(__FUNCTION__, __LINE__, sprintf('SQL string is empty, please fix this: file=%s, line=%s',
51                                 basename($file),
52                                 $line
53                         ));
54                 } elseif (!isSqlLinkUp()) {
55                         // We should not quietly ignore this
56                         reportBug(__FUNCTION__, __LINE__, sprintf('Cannot query database: sqlString=%s,file=%s,line=%s',
57                                 $sqlStringModified,
58                                 basename($file),
59                                 $line
60                         ));
61                 }
62
63                 // Remove \t, \n and \r from queries they may confuse some MySQL versions
64                 $sqlStringModified = str_replace(array(chr(9), PHP_EOL, chr(13)), array(' ', ' ', ' '), $sqlStringModified);
65
66                 // Compile config entries out
67                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'sqlStringModified=' . $sqlStringModified . ',enableCodes=' . intval($enableCodes));
68                 $sqlStringModified = sqlPrepareQueryString($sqlStringModified, $enableCodes);
69                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'sqlStringModified=' . $sqlStringModified . ',enableCodes=' . intval($enableCodes));
70
71                 // Cache it and remember as last SQL query
72                 $GLOBALS[__FUNCTION__][$sqlString] = $sqlStringModified;
73                 $GLOBALS['last_sql']               = $sqlStringModified;
74                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Stored cache: ' . $sqlStringModified);
75         } elseif (!isSqlLinkUp()) {
76                 // Link went down while using cached SQL
77                 reportBug(__FUNCTION__, __LINE__, 'Link went down while using cached SQL: sqlString=' . $sqlString . ',file=' . basename($file) . ',line=' . $line . ',enableCodes=' . intval($enableCodes));
78         } else {
79                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Cache used: ' . $sqlString);
80
81                 // Use cache (to save a lot function calls
82                 $GLOBALS['last_sql'] = $GLOBALS[__FUNCTION__][$sqlString];
83
84                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Cache is: ' . $sqlString);
85         }
86
87         // Starting time
88         $querytimeBefore = microtime(TRUE);
89
90         // Run SQL command
91         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'file=' . basename($file) . ',line=' . $line . ',sql=' . $GLOBALS['last_sql']);
92         $result = mysqli_query(getSqlLink(), $GLOBALS['last_sql'])
93                 or logSqlError($file, $line, 'file='. basename($file) . ',line=' . $line . ':mysqli_error()=' . mysqli_error(getSqlLink()) . ',last_query=' . $GLOBALS['last_sql']);
94         //* DEBUG: */ logDebugMessage($file, $line, 'sql=' . $GLOBALS['last_sql'] . ',affected=' . sqlAffectedRows() . ',numRows='.(isValidSqlResult($result) ? sqlNumRows($result) : gettype($result)));
95
96         // Calculate query time
97         $queryTime = microtime(TRUE) - $querytimeBefore;
98
99         // Add this query to array including timing
100         addSqlToDebug($result, $GLOBALS['last_sql'], $queryTime, $file, $line);
101
102         // Save last successfull query
103         setConfigEntry('db_last_query', $GLOBALS['last_sql']);
104
105         // Count all query times
106         incrementConfigEntry('sql_time', $queryTime);
107
108         // Count this query
109         incrementConfigEntry('sql_count');
110
111         // Debug output
112         if (isSqlDebugEnabled()) {
113                 // Is this the first call?
114                 if (!isset($GLOBALS['sql_first_entry'])) {
115                         // Write first entry
116                         appendLineToFile(getCachePath() . 'mysql.log', 'Module=' . getModule());
117                         $GLOBALS['sql_first_entry'] = TRUE;
118                 } // END - if
119
120                 // Append debug line
121                 appendLineToFile(getCachePath() . 'mysql.log', basename($file) . '|LINE=' . $line . '|NUM=' . (isValidSqlResult($result) ? sqlNumRows($result) : 'false') . '|AFFECTED=' . sqlAffectedRows() . '|QUERYTIME:' . ($queryTime * 1000) . 'ms): ' . str_replace(array(chr(13), PHP_EOL), array('', ' '), $GLOBALS['last_sql']));
122         } // END - if
123
124         // Increment stats entry
125         incrementStatsEntry('db_hits');
126
127         // Return the result
128         return $result;
129 }
130
131 // SQL num rows
132 function sqlNumRows ($resource) {
133         // Valid link resource?
134         if (!isSqlLinkUp()) return FALSE;
135
136         // Get the count of rows from database
137         $lines = mysqli_num_rows($resource);
138
139         // Return lines
140         return $lines;
141 }
142
143 // SQL affected rows
144 function sqlAffectedRows () {
145         // Valid link resource?
146         if (!isSqlLinkUp()) return FALSE;
147
148         // Get affected rows
149         $lines = mysqli_affected_rows(getSqlLink());
150
151         // Return it
152         return $lines;
153 }
154
155 // SQL fetch row
156 function sqlFetchRow ($resource) {
157         // Is $resource valid?
158         if ((!isValidSqlResult($resource)) || (!isSqlLinkUp())) return FALSE;
159
160         // Fetch the data and return it
161         return mysqli_fetch_row($resource);
162 }
163
164 // SQL fetch array
165 function sqlFetchArray ($resource) {
166         // Is $resource valid?
167         if ((!isValidSqlResult($resource)) || (!isSqlLinkUp())) return FALSE;
168
169         // Load row as array from database
170         $row = mysqli_fetch_assoc($resource);
171
172         // Return only arrays here
173         if (is_array($row)) {
174                 // Return row
175                 return $row;
176         } else {
177                 // Return a false, else some loops would go endless...
178                 return FALSE;
179         }
180 }
181
182 // SQL result
183 function sqlResult ($resource, $row, $field = '0') {
184         // Is $resource valid?
185         if ((!isValidSqlResult($resource)) || (!isSqlLinkUp())) return FALSE;
186
187         // Run the result command
188         $result = mysqli_result($resource, $row, $field);
189
190         // ... and return the result
191         return $result;
192 }
193
194 // SQL connect
195 function sqlConnectToDatabase ($host, $login, $password, $file, $line) {
196         // Try to connect
197         $linkResource = mysqli_connect($host, $login, $password) or logSqlError($file, $line,  mysqli_error(getSqlLink()));
198
199         // Set the link resource
200         if ($linkResource instanceof mysqli) {
201                 /*
202                  * A non-resource (boolean) may happen on installation phase which
203                  * shall not be set here. Only valid link resources shall be set so
204                  * isSqlLinkUp() will only return 'true' if there is really a
205                  * working database link.
206                  */
207                 setSqlLink($file . ':' . __FUNCTION__, $line . ':' . __LINE__, $linkResource);
208
209                 // Init charsets (UTF-8 is default now)
210                 sqlQuery("SET
211         `character_set_results`='utf8',
212         `character_set_client`='utf8',
213         `character_set_connection`='utf8',
214         `character_set_database`='utf8',
215         `character_set_server`='utf8'", $file . ':' . __FUNCTION__, $line . ':' . __LINE__);
216
217                 // Disallow subtraction for unsigned columns
218                 sqlQuery("SET `sql_mode`='NO_UNSIGNED_SUBTRACTION'", $file . ':' . __FUNCTION__, $line . ':' . __LINE__);
219         } // END - if
220
221         // Any errors encountered?
222         if (mysqli_connect_error()) {
223                 // Something went horrible wrong
224                 reportBug($file . ':' . __FUNCTION__, $line . ':' . __LINE__, 'Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
225         } // END - if
226
227         // Return the resource
228         //* DEBUG: */ logDebugMessage($file . ':' . __FUNCTION__, $line . ':' . __LINE__, 'linkResource[]=' . gettype($linkResource));
229         return $linkResource;
230 }
231
232 // SQL select database
233 function sqlSelectDatabase ($dbName, $file, $line) {
234         // Is there still a valid link? If not, skip it.
235         if (!isSqlLinkUp()) return FALSE;
236
237         // Return the result
238         //* DEBUG: */ logDebugMessage($file . ':' . __FUNCTION__, $line . ':' . __LINE__, 'Selecting database ' . $dbName);
239         return mysqli_select_db(getSqlLink(), $dbName) or logSqlError($file, $line,  mysqli_error(getSqlLink()));
240 }
241
242 // SQL close link
243 function sqlCloseLink ($file, $line) {
244         // Is the link up?
245         if (!isSqlLinkUp()) {
246                 // Skip double close
247                 //* DEBUG: */ logDebugMessage($file . ':' . __FUNCTION__, $line . ':' . __LINE__, 'Called but no link is open.');
248                 return FALSE;
249         } // END - if
250
251         // Close database link and forget the link
252         $close = mysqli_close(getSqlLink()) or logSqlError($file . ':' . __FUNCTION__, $line . ':' . __LINE__, mysqli_error(getSqlLink()));
253
254         // Close link in this layer
255         unsetSqlLinkUp(__FUNCTION__, __LINE__);
256
257         // Return the result
258         //* DEBUG: */ logDebugMessage($file . ':' . __FUNCTION__, $line . ':' . __LINE__, 'close[' . gettype($close) . ']=' . intval($close));
259         return $close;
260 }
261
262 // SQL free result
263 function sqlFreeResult ($resource) {
264         if ((!isValidSqlResult($resource)) || (!isSqlLinkUp())) {
265                 // Abort here
266                 return FALSE;
267         } // END - if
268
269         // Free result
270         $res = mysqli_free_result($resource);
271
272         // And return that result of freeing it...
273         return $res;
274 }
275
276 // Get id from last INSERT command and secure id
277 function getSqlInsertId () {
278         if (!isSqlLinkUp()) return FALSE;
279         return bigintval(mysqli_insert_id(getSqlLink()));
280 }
281
282 // Escape a string for the database
283 function sqlEscapeString ($str, $secureString = TRUE, $strip = TRUE) {
284         // Is there cache?
285         if (!isset($GLOBALS['sql_escapes']['' . $str . ''])) {
286                 // Debug message
287                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ' - BEFORE!');
288
289                 // Prepare the string here
290                 $str = sqlPrepareQueryString($str);
291
292                 // Debug message
293                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ' - AFTER!');
294
295                 // Secure string first? (which is the default behaviour!)
296                 if ($secureString === TRUE) {
297                         // Debug message
298                         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ',strip=' . intval($strip) . ' - BEFORE!');
299
300                         // Then do it here
301                         $str = secureString($str, $strip);
302
303                         // Debug message
304                         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ',strip=' . intval($strip) . ' - AFTER!');
305                 } // END - if
306
307                 // Init (invalid) value
308                 $ret = '!INVALID!';
309
310                 if (!isSqlLinkUp()) {
311                         // Fall-back to escapeQuotes() when there is no link
312                         $ret = escapeQuotes($str);
313                 } elseif (function_exists('mysqli_real_escape_string')) {
314                         // Debug message
315                         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str);
316
317                         // The new and improved version
318                         $ret = mysqli_real_escape_string(getSqlLink(), $str);
319
320                         // Debug message
321                         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ',ret=' . $ret);
322                 } else {
323                         // If nothing else works, fall back to escapeQuotes() again
324                         $ret = escapeQuotes($str);
325                 }
326
327                 // Log message
328                 //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ',ret=' . $ret);
329
330                 // Cache result
331                 $GLOBALS['sql_escapes']['' . $str . ''] = $ret;
332         } // END - if
333
334         // Log message
335         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'str=' . $str . ',sql_escapes=' . $GLOBALS['sql_escapes']['' . $str . '']);
336
337         // Return it
338         return $GLOBALS['sql_escapes']['' . $str . ''];
339 }
340
341 // Set SQL error in global array
342 function setSqlError ($file, $line, $message) {
343         // Remember plain error in last_sql_error
344         $GLOBALS['last_sql_error'] = mysqli_error(getSqlLink());
345 }
346
347 // Checks whether given link is a valid SQL link
348 function isValidSqlLink ($linkInstance) {
349         // Is it an object?
350         $isValid = ($linkInstance instanceof mysqli);
351
352         // Debug message
353         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'linkInstance[]=' . gettype($linkInstance). ',isValid=' . intval($isValid));
354
355         // Return result
356         return $isValid;
357 }
358
359 // Checks whether given result is really a result
360 function isValidSqlResult ($resultInstance) {
361         // Is it a resource?
362         $isValid = ($resultInstance instanceof mysqli_result);
363
364         // Debug message
365         //* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'resultInstance[]=' . gettype($resultInstance). ',isValid=' . intval($isValid));
366
367         // Return result
368         return $isValid;
369 }
370
371 // [EOF]
372 ?>