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