├── solverlists.db
├── README.md
├── img
└── unch.jpg
├── libs
├── waf.php
├── conf.php
├── func.php
├── gen.php
└── MysqliDb.php
├── api.php
└── index.php
/solverlists.db:
--------------------------------------------------------------------------------
1 | viloid
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dump-The-Flag-Source
--------------------------------------------------------------------------------
/img/unch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vsec7/Dump-The-Flag-Source/master/img/unch.jpg
--------------------------------------------------------------------------------
/libs/waf.php:
--------------------------------------------------------------------------------
1 | "Thanks For Voting"));
17 | }else{
18 | header('Content-Type: application/json');
19 | echo json_encode(array("result" => mysqli_error($con)));
20 | }
21 | unset($_SESSION['cans']);
22 | }else{
23 | header('Content-Type: application/json');
24 | echo json_encode(array("result" => "Invalid cans_token"));
25 | }
26 | }else{
27 | header('Content-Type: application/json');
28 | echo json_encode(array("result" => "null"));
29 | }
30 |
--------------------------------------------------------------------------------
/libs/func.php:
--------------------------------------------------------------------------------
1 | rawQuery($sql)[0][table_name];
42 | }
43 |
44 |
45 | function column_name($n,$h,$u,$p,$d)
46 | {
47 | $con = new MysqliDb ($h,$u,$p,$d);
48 | $table = table_name($h,$u,$p,$d);
49 | $sql = "SELECT column_name FROM information_schema.columns WHERE table_name='$table' LIMIT $n,1";
50 | return $con->rawQuery($sql)[0][column_name];
51 | }
52 |
53 |
54 | function dump_data($tab,$col,$h,$u,$p,$d,$n=1)
55 | {
56 | $con = new MysqliDb ($h,$u,$p,$d);
57 | $sql = "SELECT $col FROM $tab WHERE id=$n";
58 | return $con->rawQuery($sql)[0][$col];
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/libs/gen.php:
--------------------------------------------------------------------------------
1 | rawQuery("DROP DATABASE IF EXISTS $dbname");
20 | // Buat Db baru
21 | $con->rawQuery("CREATE database $dbname CHARACTER SET `gbk`");
22 | // Buat table
23 | $con->rawQuery("CREATE TABLE IF NOT EXISTS $dbname.$rand_table
24 | (
25 | id INT(5) PRIMARY KEY NOT NULL AUTO_INCREMENT,
26 | vote VARCHAR(250) NOT NULL,
27 | $rand_column CHAR(32) NOT NULL
28 | )");
29 |
30 | $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
31 |
32 |
33 | // Buat key
34 | $key = "SHL{".rand_str(12, $chars)."}";
35 |
36 | $isi = array("1" => "Kebawahhhhh", "2" => "Kebawah Lagi", "3" => "Uhhhh bawahh", "4" => "Terussss",
37 | "5" => "Emmhhhh", "6" => "Teruss kebawah", "7" => "terussss", "8" => "Unchhh", "9" => "mwwaaahhh", "10" => "Capek Belum ?", "11" => "Kebawahhhhh", "12" => "Kebawah Lagi", "13" => "Uhhhh bawahh", "14" => "Terussss",
38 | "15" => "Emmhhhh", "16" => "Teruss kebawah", "17" => "terussss", "18" => "Unchhh", "19" => "mwwaaahhh", "20" => "Capek Belum ?" );
39 |
40 | for($i=1;$i<=20;$i++){
41 | $r = rand(0,1);
42 | $con->rawQuery("INSERT INTO $dbname.$rand_table VALUES ($i, '$r', '$isi[$i]')");
43 | }
44 | $con->rawQuery("INSERT INTO $dbname.$rand_table VALUES ('21', '1', '$key')");
45 |
46 | }
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | where("vote", 1);
12 | $db->get($tbl);
13 | $a = $db->count;
14 |
15 | // via
16 | $db->where("vote", 0);
17 | $db->get($tbl);
18 | $b = $db->count;
19 |
20 | $all = $a+$b;
21 |
22 | $nella = persen($a,$all);
23 | $via = persen($b,$all);
24 |
25 | $r = base64_encode("If you found valid Flag SHL{r4nd0m1z3str1n6} , Send Your Flag To https://t.me/viloid_bot");
26 |
27 | ?>
28 |
29 |
30 | DUMP THE FLAG 2
31 |
32 |
33 |
34 |
35 |
36 |
60 |
61 |
62 |
63 |
64 |
65 |
Penyanyi Dangdut Cans
66 |

67 |
74 |
75 |
76 |
STATISTIK
77 |
78 | | Total Valid Vote | : =$all;?> Jones |
79 | | Nella Kharisma | : =$a;?> ( =$nella;?> ) |
80 | | Via Vallen | : =$b;?> ( =$via;?> ) |
81 |
82 |
83 |
84 |
HALL OF FAME
85 |
86 | | NO | Solver Name |
87 | $n | @$solv | ";
92 | $n++;
93 | }
94 | ?>
95 |
96 |
97 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/libs/MysqliDb.php:
--------------------------------------------------------------------------------
1 |
8 | * @author Josh Campbell
9 | * @author Alexander V. Butenko
10 | * @copyright Copyright (c) 2010-2017
11 | * @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
12 | * @link http://github.com/joshcam/PHP-MySQLi-Database-Class
13 | * @version 2.9.2
14 | */
15 |
16 | class MysqliDb
17 | {
18 |
19 | /**
20 | * Static instance of self
21 | * @var MysqliDb
22 | */
23 | protected static $_instance;
24 |
25 | /**
26 | * Table prefix
27 | * @var string
28 | */
29 | public static $prefix = '';
30 |
31 | /**
32 | * MySQLi instances
33 | * @var mysqli[]
34 | */
35 | protected $_mysqli = array();
36 |
37 | /**
38 | * The SQL query to be prepared and executed
39 | * @var string
40 | */
41 | protected $_query;
42 |
43 | /**
44 | * The previously executed SQL query
45 | * @var string
46 | */
47 | protected $_lastQuery;
48 |
49 | /**
50 | * The SQL query options required after SELECT, INSERT, UPDATE or DELETE
51 | * @var array
52 | */
53 | protected $_queryOptions = array();
54 |
55 | /**
56 | * An array that holds where joins
57 | * @var array
58 | */
59 | protected $_join = array();
60 |
61 | /**
62 | * An array that holds where conditions
63 | * @var array
64 | */
65 | protected $_where = array();
66 |
67 | /**
68 | * An array that holds where join ands
69 | *
70 | * @var array
71 | */
72 | protected $_joinAnd = array();
73 |
74 | /**
75 | * An array that holds having conditions
76 | * @var array
77 | */
78 | protected $_having = array();
79 |
80 | /**
81 | * Dynamic type list for order by condition value
82 | * @var array
83 | */
84 | protected $_orderBy = array();
85 |
86 | /**
87 | * Dynamic type list for group by condition value
88 | * @var array
89 | */
90 | protected $_groupBy = array();
91 |
92 | /**
93 | * Dynamic type list for tempromary locking tables.
94 | * @var array
95 | */
96 | protected $_tableLocks = array();
97 |
98 | /**
99 | * Variable which holds the current table lock method.
100 | * @var string
101 | */
102 | protected $_tableLockMethod = "READ";
103 |
104 | /**
105 | * Dynamic array that holds a combination of where condition/table data value types and parameter references
106 | * @var array
107 | */
108 | protected $_bindParams = array(''); // Create the empty 0 index
109 |
110 | /**
111 | * Variable which holds an amount of returned rows during get/getOne/select queries
112 | * @var string
113 | */
114 | public $count = 0;
115 |
116 | /**
117 | * Variable which holds an amount of returned rows during get/getOne/select queries with withTotalCount()
118 | * @var string
119 | */
120 | public $totalCount = 0;
121 |
122 | /**
123 | * Variable which holds last statement error
124 | * @var string
125 | */
126 | protected $_stmtError;
127 |
128 | /**
129 | * Variable which holds last statement error code
130 | * @var int
131 | */
132 | protected $_stmtErrno;
133 |
134 | /**
135 | * Is Subquery object
136 | * @var bool
137 | */
138 | protected $isSubQuery = false;
139 |
140 | /**
141 | * Name of the auto increment column
142 | * @var int
143 | */
144 | protected $_lastInsertId = null;
145 |
146 | /**
147 | * Column names for update when using onDuplicate method
148 | * @var array
149 | */
150 | protected $_updateColumns = null;
151 |
152 | /**
153 | * Return type: 'array' to return results as array, 'object' as object
154 | * 'json' as json string
155 | * @var string
156 | */
157 | public $returnType = 'array';
158 |
159 | /**
160 | * Should join() results be nested by table
161 | * @var bool
162 | */
163 | protected $_nestJoin = false;
164 |
165 | /**
166 | * Table name (with prefix, if used)
167 | * @var string
168 | */
169 | private $_tableName = '';
170 |
171 | /**
172 | * FOR UPDATE flag
173 | * @var bool
174 | */
175 | protected $_forUpdate = false;
176 |
177 | /**
178 | * LOCK IN SHARE MODE flag
179 | * @var bool
180 | */
181 | protected $_lockInShareMode = false;
182 |
183 | /**
184 | * Key field for Map()'ed result array
185 | * @var string
186 | */
187 | protected $_mapKey = null;
188 |
189 | /**
190 | * Variables for query execution tracing
191 | */
192 | protected $traceStartQ;
193 | protected $traceEnabled;
194 | protected $traceStripPrefix;
195 | public $trace = array();
196 |
197 | /**
198 | * Per page limit for pagination
199 | *
200 | * @var int
201 | */
202 |
203 | public $pageLimit = 20;
204 | /**
205 | * Variable that holds total pages count of last paginate() query
206 | *
207 | * @var int
208 | */
209 | public $totalPages = 0;
210 |
211 | /**
212 | * @var array connections settings [profile_name=>[same_as_contruct_args]]
213 | */
214 | protected $connectionsSettings = array();
215 | /**
216 | * @var string the name of a default (main) mysqli connection
217 | */
218 | public $defConnectionName = 'default';
219 |
220 | public $autoReconnect = true;
221 | protected $autoReconnectCount = 0;
222 |
223 | /**
224 | * @var bool Operations in transaction indicator
225 | */
226 | protected $_transaction_in_progress = false;
227 |
228 | /**
229 | * @param string $host
230 | * @param string $username
231 | * @param string $password
232 | * @param string $db
233 | * @param int $port
234 | * @param string $charset
235 | * @param string $socket
236 | */
237 | public function __construct($host = null, $username = null, $password = null, $db = null, $port = null, $charset = 'utf8', $socket = null)
238 | {
239 | $isSubQuery = false;
240 |
241 | // if params were passed as array
242 | if (is_array($host)) {
243 | foreach ($host as $key => $val) {
244 | $$key = $val;
245 | }
246 | }
247 |
248 | $this->addConnection('default', array(
249 | 'host' => $host,
250 | 'username' => $username,
251 | 'password' => $password,
252 | 'db' => $db,
253 | 'port' => $port,
254 | 'socket' => $socket,
255 | 'charset' => $charset
256 | ));
257 |
258 | if ($isSubQuery) {
259 | $this->isSubQuery = true;
260 | return;
261 | }
262 |
263 | if (isset($prefix)) {
264 | $this->setPrefix($prefix);
265 | }
266 |
267 | self::$_instance = $this;
268 | }
269 |
270 | /**
271 | * A method to connect to the database
272 | *
273 | * @param null|string $connectionName
274 | * @throws Exception
275 | * @return void
276 | */
277 | public function connect($connectionName = 'default')
278 | {
279 | if(!isset($this->connectionsSettings[$connectionName]))
280 | throw new Exception('Connection profile not set');
281 |
282 | $pro = $this->connectionsSettings[$connectionName];
283 | $params = array_values($pro);
284 | $charset = array_pop($params);
285 |
286 | if ($this->isSubQuery) {
287 | return;
288 | }
289 |
290 | if (empty($pro['host']) && empty($pro['socket'])) {
291 | throw new Exception('MySQL host or socket is not set');
292 | }
293 |
294 | $mysqlic = new ReflectionClass('mysqli');
295 | $mysqli = $mysqlic->newInstanceArgs($params);
296 |
297 | if ($mysqli->connect_error) {
298 | throw new Exception('Connect Error ' . $mysqli->connect_errno . ': ' . $mysqli->connect_error, $mysqli->connect_errno);
299 | }
300 |
301 | if (!empty($charset)) {
302 | $mysqli->set_charset($charset);
303 | }
304 | $this->_mysqli[$connectionName] = $mysqli;
305 | }
306 |
307 | public function disconnectAll()
308 | {
309 | foreach (array_keys($this->_mysqli) as $k) {
310 | $this->disconnect($k);
311 | }
312 | }
313 |
314 | /**
315 | * Set the connection name to use in the next query
316 | * @param string $name
317 | * @return $this
318 | * @throws Exception
319 | */
320 | public function connection($name)
321 | {
322 | if (!isset($this->connectionsSettings[$name]))
323 | throw new Exception('Connection ' . $name . ' was not added.');
324 |
325 | $this->defConnectionName = $name;
326 | return $this;
327 | }
328 |
329 | /**
330 | * A method to disconnect from the database
331 | *
332 | * @params string $connection connection name to disconnect
333 | * @throws Exception
334 | * @return void
335 | */
336 | public function disconnect($connection = 'default')
337 | {
338 | if (!isset($this->_mysqli[$connection]))
339 | return;
340 |
341 | $this->_mysqli[$connection]->close();
342 | unset($this->_mysqli[$connection]);
343 | }
344 |
345 | /**
346 | * Create & store at _mysqli new mysqli instance
347 | * @param string $name
348 | * @param array $params
349 | * @return $this
350 | */
351 | public function addConnection($name, array $params)
352 | {
353 | $this->connectionsSettings[$name] = array();
354 | foreach (array('host', 'username', 'password', 'db', 'port', 'socket', 'charset') as $k) {
355 | $prm = isset($params[$k]) ? $params[$k] : null;
356 |
357 | if ($k == 'host') {
358 | if (is_object($prm))
359 | $this->_mysqli[$name] = $prm;
360 |
361 | if (!is_string($prm))
362 | $prm = null;
363 | }
364 | $this->connectionsSettings[$name][$k] = $prm;
365 | }
366 | return $this;
367 | }
368 |
369 | /**
370 | * A method to get mysqli object or create it in case needed
371 | *
372 | * @return mysqli
373 | */
374 | public function mysqli()
375 | {
376 | if (!isset($this->_mysqli[$this->defConnectionName])) {
377 | $this->connect($this->defConnectionName);
378 | }
379 | return $this->_mysqli[$this->defConnectionName];
380 | }
381 |
382 | /**
383 | * A method of returning the static instance to allow access to the
384 | * instantiated object from within another class.
385 | * Inheriting this class would require reloading connection info.
386 | *
387 | * @uses $db = MySqliDb::getInstance();
388 | *
389 | * @return MysqliDb Returns the current instance.
390 | */
391 | public static function getInstance()
392 | {
393 | return self::$_instance;
394 | }
395 |
396 | /**
397 | * Reset states after an execution
398 | *
399 | * @return MysqliDb Returns the current instance.
400 | */
401 | protected function reset()
402 | {
403 | if ($this->traceEnabled) {
404 | $this->trace[] = array($this->_lastQuery, (microtime(true) - $this->traceStartQ), $this->_traceGetCaller());
405 | }
406 |
407 | $this->_where = array();
408 | $this->_having = array();
409 | $this->_join = array();
410 | $this->_joinAnd = array();
411 | $this->_orderBy = array();
412 | $this->_groupBy = array();
413 | $this->_bindParams = array(''); // Create the empty 0 index
414 | $this->_query = null;
415 | $this->_queryOptions = array();
416 | $this->returnType = 'array';
417 | $this->_nestJoin = false;
418 | $this->_forUpdate = false;
419 | $this->_lockInShareMode = false;
420 | $this->_tableName = '';
421 | $this->_lastInsertId = null;
422 | $this->_updateColumns = null;
423 | $this->_mapKey = null;
424 | if(!$this->_transaction_in_progress ) {
425 | $this->defConnectionName = 'default';
426 | }
427 | $this->autoReconnectCount = 0;
428 | return $this;
429 | }
430 |
431 | /**
432 | * Helper function to create dbObject with JSON return type
433 | *
434 | * @return MysqliDb
435 | */
436 | public function jsonBuilder()
437 | {
438 | $this->returnType = 'json';
439 | return $this;
440 | }
441 |
442 | /**
443 | * Helper function to create dbObject with array return type
444 | * Added for consistency as thats default output type
445 | *
446 | * @return MysqliDb
447 | */
448 | public function arrayBuilder()
449 | {
450 | $this->returnType = 'array';
451 | return $this;
452 | }
453 |
454 | /**
455 | * Helper function to create dbObject with object return type.
456 | *
457 | * @return MysqliDb
458 | */
459 | public function objectBuilder()
460 | {
461 | $this->returnType = 'object';
462 | return $this;
463 | }
464 |
465 | /**
466 | * Method to set a prefix
467 | *
468 | * @param string $prefix Contains a tableprefix
469 | *
470 | * @return MysqliDb
471 | */
472 | public function setPrefix($prefix = '')
473 | {
474 | self::$prefix = $prefix;
475 | return $this;
476 | }
477 |
478 | /**
479 | * Pushes a unprepared statement to the mysqli stack.
480 | * WARNING: Use with caution.
481 | * This method does not escape strings by default so make sure you'll never use it in production.
482 | *
483 | * @author Jonas Barascu
484 | * @param [[Type]] $query [[Description]]
485 | */
486 | private function queryUnprepared($query)
487 | {
488 | // Execute query
489 | $stmt = $this->mysqli()->query($query);
490 |
491 | // Failed?
492 | if ($stmt !== false)
493 | return $stmt;
494 |
495 | if ($this->mysqli()->errno === 2006 && $this->autoReconnect === true && $this->autoReconnectCount === 0) {
496 | $this->connect($this->defConnectionName);
497 | $this->autoReconnectCount++;
498 | return $this->queryUnprepared($query);
499 | }
500 |
501 | throw new Exception(sprintf('Unprepared Query Failed, ERRNO: %u (%s)', $this->mysqli()->errno, $this->mysqli()->error), $this->mysqli()->errno);
502 | }
503 |
504 | /**
505 | * Execute raw SQL query.
506 | *
507 | * @param string $query User-provided query to execute.
508 | * @param array $bindParams Variables array to bind to the SQL statement.
509 | *
510 | * @return array Contains the returned rows from the query.
511 | */
512 | public function rawQuery($query, $bindParams = null)
513 | {
514 | $params = array(''); // Create the empty 0 index
515 | $this->_query = $query;
516 | $stmt = $this->_prepareQuery();
517 |
518 | if (is_array($bindParams) === true) {
519 | foreach ($bindParams as $prop => $val) {
520 | $params[0] .= $this->_determineType($val);
521 | array_push($params, $bindParams[$prop]);
522 | }
523 |
524 | call_user_func_array(array($stmt, 'bind_param'), $this->refValues($params));
525 | }
526 |
527 | $stmt->execute();
528 | $this->count = $stmt->affected_rows;
529 | $this->_stmtError = $stmt->error;
530 | $this->_stmtErrno = $stmt->errno;
531 | $this->_lastQuery = $this->replacePlaceHolders($this->_query, $params);
532 | $res = $this->_dynamicBindResults($stmt);
533 | $this->reset();
534 |
535 | return $res;
536 | }
537 |
538 | /**
539 | * Helper function to execute raw SQL query and return only 1 row of results.
540 | * Note that function do not add 'limit 1' to the query by itself
541 | * Same idea as getOne()
542 | *
543 | * @param string $query User-provided query to execute.
544 | * @param array $bindParams Variables array to bind to the SQL statement.
545 | *
546 | * @return array|null Contains the returned row from the query.
547 | */
548 | public function rawQueryOne($query, $bindParams = null)
549 | {
550 | $res = $this->rawQuery($query, $bindParams);
551 | if (is_array($res) && isset($res[0])) {
552 | return $res[0];
553 | }
554 |
555 | return null;
556 | }
557 |
558 | /**
559 | * Helper function to execute raw SQL query and return only 1 column of results.
560 | * If 'limit 1' will be found, then string will be returned instead of array
561 | * Same idea as getValue()
562 | *
563 | * @param string $query User-provided query to execute.
564 | * @param array $bindParams Variables array to bind to the SQL statement.
565 | *
566 | * @return mixed Contains the returned rows from the query.
567 | */
568 | public function rawQueryValue($query, $bindParams = null)
569 | {
570 | $res = $this->rawQuery($query, $bindParams);
571 | if (!$res) {
572 | return null;
573 | }
574 |
575 | $limit = preg_match('/limit\s+1;?$/i', $query);
576 | $key = key($res[0]);
577 | if (isset($res[0][$key]) && $limit == true) {
578 | return $res[0][$key];
579 | }
580 |
581 | $newRes = Array();
582 | for ($i = 0; $i < $this->count; $i++) {
583 | $newRes[] = $res[$i][$key];
584 | }
585 | return $newRes;
586 | }
587 |
588 | /**
589 | * A method to perform select query
590 | *
591 | * @param string $query Contains a user-provided select query.
592 | * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
593 | *
594 | * @return array Contains the returned rows from the query.
595 | */
596 | public function query($query, $numRows = null)
597 | {
598 | $this->_query = $query;
599 | $stmt = $this->_buildQuery($numRows);
600 | $stmt->execute();
601 | $this->_stmtError = $stmt->error;
602 | $this->_stmtErrno = $stmt->errno;
603 | $res = $this->_dynamicBindResults($stmt);
604 | $this->reset();
605 |
606 | return $res;
607 | }
608 |
609 | /**
610 | * This method allows you to specify multiple (method chaining optional) options for SQL queries.
611 | *
612 | * @uses $MySqliDb->setQueryOption('name');
613 | *
614 | * @param string|array $options The optons name of the query.
615 | *
616 | * @throws Exception
617 | * @return MysqliDb
618 | */
619 | public function setQueryOption($options)
620 | {
621 | $allowedOptions = Array('ALL', 'DISTINCT', 'DISTINCTROW', 'HIGH_PRIORITY', 'STRAIGHT_JOIN', 'SQL_SMALL_RESULT',
622 | 'SQL_BIG_RESULT', 'SQL_BUFFER_RESULT', 'SQL_CACHE', 'SQL_NO_CACHE', 'SQL_CALC_FOUND_ROWS',
623 | 'LOW_PRIORITY', 'IGNORE', 'QUICK', 'MYSQLI_NESTJOIN', 'FOR UPDATE', 'LOCK IN SHARE MODE');
624 |
625 | if (!is_array($options)) {
626 | $options = Array($options);
627 | }
628 |
629 | foreach ($options as $option) {
630 | $option = strtoupper($option);
631 | if (!in_array($option, $allowedOptions)) {
632 | throw new Exception('Wrong query option: ' . $option);
633 | }
634 |
635 | if ($option == 'MYSQLI_NESTJOIN') {
636 | $this->_nestJoin = true;
637 | } elseif ($option == 'FOR UPDATE') {
638 | $this->_forUpdate = true;
639 | } elseif ($option == 'LOCK IN SHARE MODE') {
640 | $this->_lockInShareMode = true;
641 | } else {
642 | $this->_queryOptions[] = $option;
643 | }
644 | }
645 |
646 | return $this;
647 | }
648 |
649 | /**
650 | * Function to enable SQL_CALC_FOUND_ROWS in the get queries
651 | *
652 | * @return MysqliDb
653 | */
654 | public function withTotalCount()
655 | {
656 | $this->setQueryOption('SQL_CALC_FOUND_ROWS');
657 | return $this;
658 | }
659 |
660 | /**
661 | * A convenient SELECT * function.
662 | *
663 | * @param string $tableName The name of the database table to work with.
664 | * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
665 | * or only $count
666 | * @param string $columns Desired columns
667 | *
668 | * @return array Contains the returned rows from the select query.
669 | */
670 | public function get($tableName, $numRows = null, $columns = '*')
671 | {
672 | if (empty($columns)) {
673 | $columns = '*';
674 | }
675 |
676 | $column = is_array($columns) ? implode(', ', $columns) : $columns;
677 |
678 | if (strpos($tableName, '.') === false) {
679 | $this->_tableName = self::$prefix . $tableName;
680 | } else {
681 | $this->_tableName = $tableName;
682 | }
683 |
684 | $this->_query = 'SELECT ' . implode(' ', $this->_queryOptions) . ' ' .
685 | $column . " FROM " . $this->_tableName;
686 | $stmt = $this->_buildQuery($numRows);
687 |
688 | if ($this->isSubQuery) {
689 | return $this;
690 | }
691 |
692 | $stmt->execute();
693 | $this->_stmtError = $stmt->error;
694 | $this->_stmtErrno = $stmt->errno;
695 | $res = $this->_dynamicBindResults($stmt);
696 | $this->reset();
697 |
698 | return $res;
699 | }
700 |
701 | /**
702 | * A convenient SELECT * function to get one record.
703 | *
704 | * @param string $tableName The name of the database table to work with.
705 | * @param string $columns Desired columns
706 | *
707 | * @return array Contains the returned rows from the select query.
708 | */
709 | public function getOne($tableName, $columns = '*')
710 | {
711 | $res = $this->get($tableName, 1, $columns);
712 |
713 | if ($res instanceof MysqliDb) {
714 | return $res;
715 | } elseif (is_array($res) && isset($res[0])) {
716 | return $res[0];
717 | } elseif ($res) {
718 | return $res;
719 | }
720 |
721 | return null;
722 | }
723 |
724 | /**
725 | * A convenient SELECT COLUMN function to get a single column value from one row
726 | *
727 | * @param string $tableName The name of the database table to work with.
728 | * @param string $column The desired column
729 | * @param int $limit Limit of rows to select. Use null for unlimited..1 by default
730 | *
731 | * @return mixed Contains the value of a returned column / array of values
732 | */
733 | public function getValue($tableName, $column, $limit = 1)
734 | {
735 | $res = $this->ArrayBuilder()->get($tableName, $limit, "{$column} AS retval");
736 |
737 | if (!$res) {
738 | return null;
739 | }
740 |
741 | if ($limit == 1) {
742 | if (isset($res[0]["retval"])) {
743 | return $res[0]["retval"];
744 | }
745 | return null;
746 | }
747 |
748 | $newRes = Array();
749 | for ($i = 0; $i < $this->count; $i++) {
750 | $newRes[] = $res[$i]['retval'];
751 | }
752 | return $newRes;
753 | }
754 |
755 | /**
756 | * Insert method to add new row
757 | *
758 | * @param string $tableName The name of the table.
759 | * @param array $insertData Data containing information for inserting into the DB.
760 | *
761 | * @return bool Boolean indicating whether the insert query was completed succesfully.
762 | */
763 | public function insert($tableName, $insertData)
764 | {
765 | return $this->_buildInsert($tableName, $insertData, 'INSERT');
766 | }
767 |
768 | /**
769 | * Insert method to add several rows at once
770 | *
771 | * @param string $tableName The name of the table.
772 | * @param array $multiInsertData Two-dimensinal Data-array containing information for inserting into the DB.
773 | * @param array $dataKeys Optinal Table Key names, if not set in insertDataSet.
774 | *
775 | * @return bool|array Boolean indicating the insertion failed (false), else return id-array ([int])
776 | */
777 | public function insertMulti($tableName, array $multiInsertData, array $dataKeys = null)
778 | {
779 | // only auto-commit our inserts, if no transaction is currently running
780 | $autoCommit = (isset($this->_transaction_in_progress) ? !$this->_transaction_in_progress : true);
781 | $ids = array();
782 |
783 | if($autoCommit) {
784 | $this->startTransaction();
785 | }
786 |
787 | foreach ($multiInsertData as $insertData) {
788 | if($dataKeys !== null) {
789 | // apply column-names if given, else assume they're already given in the data
790 | $insertData = array_combine($dataKeys, $insertData);
791 | }
792 |
793 | $id = $this->insert($tableName, $insertData);
794 | if(!$id) {
795 | if($autoCommit) {
796 | $this->rollback();
797 | }
798 | return false;
799 | }
800 | $ids[] = $id;
801 | }
802 |
803 | if($autoCommit) {
804 | $this->commit();
805 | }
806 |
807 | return $ids;
808 | }
809 |
810 | /**
811 | * Replace method to add new row
812 | *
813 | * @param string $tableName The name of the table.
814 | * @param array $insertData Data containing information for inserting into the DB.
815 | *
816 | * @return bool Boolean indicating whether the insert query was completed succesfully.
817 | */
818 | public function replace($tableName, $insertData)
819 | {
820 | return $this->_buildInsert($tableName, $insertData, 'REPLACE');
821 | }
822 |
823 | /**
824 | * A convenient function that returns TRUE if exists at least an element that
825 | * satisfy the where condition specified calling the "where" method before this one.
826 | *
827 | * @param string $tableName The name of the database table to work with.
828 | *
829 | * @return bool
830 | */
831 | public function has($tableName)
832 | {
833 | $this->getOne($tableName, '1');
834 | return $this->count >= 1;
835 | }
836 |
837 | /**
838 | * Update query. Be sure to first call the "where" method.
839 | *
840 | * @param string $tableName The name of the database table to work with.
841 | * @param array $tableData Array of data to update the desired row.
842 | * @param int $numRows Limit on the number of rows that can be updated.
843 | *
844 | * @return bool
845 | */
846 | public function update($tableName, $tableData, $numRows = null)
847 | {
848 | if ($this->isSubQuery) {
849 | return;
850 | }
851 |
852 | $this->_query = "UPDATE " . self::$prefix . $tableName;
853 |
854 | $stmt = $this->_buildQuery($numRows, $tableData);
855 | $status = $stmt->execute();
856 | $this->reset();
857 | $this->_stmtError = $stmt->error;
858 | $this->_stmtErrno = $stmt->errno;
859 | $this->count = $stmt->affected_rows;
860 |
861 | return $status;
862 | }
863 |
864 | /**
865 | * Delete query. Call the "where" method first.
866 | *
867 | * @param string $tableName The name of the database table to work with.
868 | * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
869 | * or only $count
870 | *
871 | * @return bool Indicates success. 0 or 1.
872 | */
873 | public function delete($tableName, $numRows = null)
874 | {
875 | if ($this->isSubQuery) {
876 | return;
877 | }
878 |
879 | $table = self::$prefix . $tableName;
880 |
881 | if (count($this->_join)) {
882 | $this->_query = "DELETE " . preg_replace('/.* (.*)/', '$1', $table) . " FROM " . $table;
883 | } else {
884 | $this->_query = "DELETE FROM " . $table;
885 | }
886 |
887 | $stmt = $this->_buildQuery($numRows);
888 | $stmt->execute();
889 | $this->_stmtError = $stmt->error;
890 | $this->_stmtErrno = $stmt->errno;
891 | $this->reset();
892 |
893 | return ($stmt->affected_rows > -1); // affected_rows returns 0 if nothing matched where statement, or required updating, -1 if error
894 | }
895 |
896 | /**
897 | * This method allows you to specify multiple (method chaining optional) AND WHERE statements for SQL queries.
898 | *
899 | * @uses $MySqliDb->where('id', 7)->where('title', 'MyTitle');
900 | *
901 | * @param string $whereProp The name of the database field.
902 | * @param mixed $whereValue The value of the database field.
903 | * @param string $operator Comparison operator. Default is =
904 | * @param string $cond Condition of where statement (OR, AND)
905 | *
906 | * @return MysqliDb
907 | */
908 | public function where($whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
909 | {
910 | // forkaround for an old operation api
911 | if (is_array($whereValue) && ($key = key($whereValue)) != "0") {
912 | $operator = $key;
913 | $whereValue = $whereValue[$key];
914 | }
915 |
916 | if (count($this->_where) == 0) {
917 | $cond = '';
918 | }
919 |
920 | $this->_where[] = array($cond, $whereProp, $operator, $whereValue);
921 | return $this;
922 | }
923 |
924 | /**
925 | * This function store update column's name and column name of the
926 | * autoincrement column
927 | *
928 | * @param array $updateColumns Variable with values
929 | * @param string $lastInsertId Variable value
930 | *
931 | * @return MysqliDb
932 | */
933 | public function onDuplicate($updateColumns, $lastInsertId = null)
934 | {
935 | $this->_lastInsertId = $lastInsertId;
936 | $this->_updateColumns = $updateColumns;
937 | return $this;
938 | }
939 |
940 | /**
941 | * This method allows you to specify multiple (method chaining optional) OR WHERE statements for SQL queries.
942 | *
943 | * @uses $MySqliDb->orWhere('id', 7)->orWhere('title', 'MyTitle');
944 | *
945 | * @param string $whereProp The name of the database field.
946 | * @param mixed $whereValue The value of the database field.
947 | * @param string $operator Comparison operator. Default is =
948 | *
949 | * @return MysqliDb
950 | */
951 | public function orWhere($whereProp, $whereValue = 'DBNULL', $operator = '=')
952 | {
953 | return $this->where($whereProp, $whereValue, $operator, 'OR');
954 | }
955 |
956 | /**
957 | * This method allows you to specify multiple (method chaining optional) AND HAVING statements for SQL queries.
958 | *
959 | * @uses $MySqliDb->having('SUM(tags) > 10')
960 | *
961 | * @param string $havingProp The name of the database field.
962 | * @param mixed $havingValue The value of the database field.
963 | * @param string $operator Comparison operator. Default is =
964 | *
965 | * @return MysqliDb
966 | */
967 |
968 | public function having($havingProp, $havingValue = 'DBNULL', $operator = '=', $cond = 'AND')
969 | {
970 | // forkaround for an old operation api
971 | if (is_array($havingValue) && ($key = key($havingValue)) != "0") {
972 | $operator = $key;
973 | $havingValue = $havingValue[$key];
974 | }
975 |
976 | if (count($this->_having) == 0) {
977 | $cond = '';
978 | }
979 |
980 | $this->_having[] = array($cond, $havingProp, $operator, $havingValue);
981 | return $this;
982 | }
983 |
984 | /**
985 | * This method allows you to specify multiple (method chaining optional) OR HAVING statements for SQL queries.
986 | *
987 | * @uses $MySqliDb->orHaving('SUM(tags) > 10')
988 | *
989 | * @param string $havingProp The name of the database field.
990 | * @param mixed $havingValue The value of the database field.
991 | * @param string $operator Comparison operator. Default is =
992 | *
993 | * @return MysqliDb
994 | */
995 | public function orHaving($havingProp, $havingValue = null, $operator = null)
996 | {
997 | return $this->having($havingProp, $havingValue, $operator, 'OR');
998 | }
999 |
1000 | /**
1001 | * This method allows you to concatenate joins for the final SQL statement.
1002 | *
1003 | * @uses $MySqliDb->join('table1', 'field1 <> field2', 'LEFT')
1004 | *
1005 | * @param string $joinTable The name of the table.
1006 | * @param string $joinCondition the condition.
1007 | * @param string $joinType 'LEFT', 'INNER' etc.
1008 | *
1009 | * @throws Exception
1010 | * @return MysqliDb
1011 | */
1012 | public function join($joinTable, $joinCondition, $joinType = '')
1013 | {
1014 | $allowedTypes = array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER', 'NATURAL');
1015 | $joinType = strtoupper(trim($joinType));
1016 |
1017 | if ($joinType && !in_array($joinType, $allowedTypes)) {
1018 | throw new Exception('Wrong JOIN type: ' . $joinType);
1019 | }
1020 |
1021 | if (!is_object($joinTable)) {
1022 | $joinTable = self::$prefix . $joinTable;
1023 | }
1024 |
1025 | $this->_join[] = Array($joinType, $joinTable, $joinCondition);
1026 |
1027 | return $this;
1028 | }
1029 |
1030 |
1031 | /**
1032 | * This is a basic method which allows you to import raw .CSV data into a table
1033 | * Please check out http://dev.mysql.com/doc/refman/5.7/en/load-data.html for a valid .csv file.
1034 |
1035 | * @author Jonas Barascu (Noneatme)
1036 | * @param string $importTable The database table where the data will be imported into.
1037 | * @param string $importFile The file to be imported. Please use double backslashes \\ and make sure you
1038 | * @param string $importSettings An Array defining the import settings as described in the README.md
1039 | * @return boolean
1040 | */
1041 | public function loadData($importTable, $importFile, $importSettings = null)
1042 | {
1043 | // We have to check if the file exists
1044 | if(!file_exists($importFile)) {
1045 | // Throw an exception
1046 | throw new Exception("importCSV -> importFile ".$importFile." does not exists!");
1047 | return;
1048 | }
1049 |
1050 | // Define the default values
1051 | // We will merge it later
1052 | $settings = Array("fieldChar" => ';', "lineChar" => PHP_EOL, "linesToIgnore" => 1);
1053 |
1054 | // Check the import settings
1055 | if(gettype($importSettings) == "array") {
1056 | // Merge the default array with the custom one
1057 | $settings = array_merge($settings, $importSettings);
1058 | }
1059 |
1060 | // Add the prefix to the import table
1061 | $table = self::$prefix . $importTable;
1062 |
1063 | // Add 1 more slash to every slash so maria will interpret it as a path
1064 | $importFile = str_replace("\\", "\\\\", $importFile);
1065 |
1066 | // Switch between LOAD DATA and LOAD DATA LOCAL
1067 | $loadDataLocal = isset($settings["loadDataLocal"]) ? 'LOCAL' : '';
1068 |
1069 | // Build SQL Syntax
1070 | $sqlSyntax = sprintf('LOAD DATA %s INFILE \'%s\' INTO TABLE %s',
1071 | $loadDataLocal, $importFile, $table);
1072 |
1073 | // FIELDS
1074 | $sqlSyntax .= sprintf(' FIELDS TERMINATED BY \'%s\'', $settings["fieldChar"]);
1075 | if(isset($settings["fieldEnclosure"])) {
1076 | $sqlSyntax .= sprintf(' ENCLOSED BY \'%s\'', $settings["fieldEnclosure"]);
1077 | }
1078 |
1079 | // LINES
1080 | $sqlSyntax .= sprintf(' LINES TERMINATED BY \'%s\'', $settings["lineChar"]);
1081 | if(isset($settings["lineStarting"])) {
1082 | $sqlSyntax .= sprintf(' STARTING BY \'%s\'', $settings["lineStarting"]);
1083 | }
1084 |
1085 | // IGNORE LINES
1086 | $sqlSyntax .= sprintf(' IGNORE %d LINES', $settings["linesToIgnore"]);
1087 |
1088 | // Exceute the query unprepared because LOAD DATA only works with unprepared statements.
1089 | $result = $this->queryUnprepared($sqlSyntax);
1090 |
1091 | // Are there rows modified?
1092 | // Let the user know if the import failed / succeeded
1093 | return (bool) $result;
1094 | }
1095 |
1096 | /**
1097 | * This method is usefull for importing XML files into a specific table.
1098 | * Check out the LOAD XML syntax for your MySQL server.
1099 | *
1100 | * @author Jonas Barascu
1101 | * @param string $importTable The table in which the data will be imported to.
1102 | * @param string $importFile The file which contains the .XML data.
1103 | * @param string $importSettings An Array defining the import settings as described in the README.md
1104 | *
1105 | * @return boolean Returns true if the import succeeded, false if it failed.
1106 | */
1107 | public function loadXml($importTable, $importFile, $importSettings = null)
1108 | {
1109 | // We have to check if the file exists
1110 | if(!file_exists($importFile)) {
1111 | // Does not exists
1112 | throw new Exception("loadXml: Import file does not exists");
1113 | return;
1114 | }
1115 |
1116 | // Create default values
1117 | $settings = Array("linesToIgnore" => 0);
1118 |
1119 | // Check the import settings
1120 | if(gettype($importSettings) == "array") {
1121 | $settings = array_merge($settings, $importSettings);
1122 | }
1123 |
1124 | // Add the prefix to the import table
1125 | $table = self::$prefix . $importTable;
1126 |
1127 | // Add 1 more slash to every slash so maria will interpret it as a path
1128 | $importFile = str_replace("\\", "\\\\", $importFile);
1129 |
1130 | // Build SQL Syntax
1131 | $sqlSyntax = sprintf('LOAD XML INFILE \'%s\' INTO TABLE %s',
1132 | $importFile, $table);
1133 |
1134 | // FIELDS
1135 | if(isset($settings["rowTag"])) {
1136 | $sqlSyntax .= sprintf(' ROWS IDENTIFIED BY \'%s\'', $settings["rowTag"]);
1137 | }
1138 |
1139 | // IGNORE LINES
1140 | $sqlSyntax .= sprintf(' IGNORE %d LINES', $settings["linesToIgnore"]);
1141 |
1142 | // Exceute the query unprepared because LOAD XML only works with unprepared statements.
1143 | $result = $this->queryUnprepared($sqlSyntax);
1144 |
1145 | // Are there rows modified?
1146 | // Let the user know if the import failed / succeeded
1147 | return (bool) $result;
1148 | }
1149 |
1150 | /**
1151 | * This method allows you to specify multiple (method chaining optional) ORDER BY statements for SQL queries.
1152 | *
1153 | * @uses $MySqliDb->orderBy('id', 'desc')->orderBy('name', 'desc', '^[a-z]')->orderBy('name', 'desc');
1154 | *
1155 | * @param string $orderByField The name of the database field.
1156 | * @param string $orderByDirection Order direction.
1157 | * @param mixed $customFieldsOrRegExp Array with fieldset for ORDER BY FIELD() ordering or string with regular expresion for ORDER BY REGEXP ordering
1158 | *
1159 | * @throws Exception
1160 | * @return MysqliDb
1161 | */
1162 | public function orderBy($orderByField, $orderbyDirection = "DESC", $customFieldsOrRegExp = null)
1163 | {
1164 | $allowedDirection = Array("ASC", "DESC");
1165 | $orderbyDirection = strtoupper(trim($orderbyDirection));
1166 | $orderByField = preg_replace("/[^ -a-z0-9\.\(\),_`\*\'\"]+/i", '', $orderByField);
1167 |
1168 | // Add table prefix to orderByField if needed.
1169 | //FIXME: We are adding prefix only if table is enclosed into `` to distinguish aliases
1170 | // from table names
1171 | $orderByField = preg_replace('/(\`)([`a-zA-Z0-9_]*\.)/', '\1' . self::$prefix . '\2', $orderByField);
1172 |
1173 |
1174 | if (empty($orderbyDirection) || !in_array($orderbyDirection, $allowedDirection)) {
1175 | throw new Exception('Wrong order direction: ' . $orderbyDirection);
1176 | }
1177 |
1178 | if (is_array($customFieldsOrRegExp)) {
1179 | foreach ($customFieldsOrRegExp as $key => $value) {
1180 | $customFieldsOrRegExp[$key] = preg_replace("/[^-a-z0-9\.\(\),_` ]+/i", '', $value);
1181 | }
1182 | $orderByField = 'FIELD (' . $orderByField . ', "' . implode('","', $customFieldsOrRegExp) . '")';
1183 | }elseif(is_string($customFieldsOrRegExp)){
1184 | $orderByField = $orderByField . " REGEXP '" . $customFieldsOrRegExp . "'";
1185 | }elseif($customFieldsOrRegExp !== null){
1186 | throw new Exception('Wrong custom field or Regular Expression: ' . $customFieldsOrRegExp);
1187 | }
1188 |
1189 | $this->_orderBy[$orderByField] = $orderbyDirection;
1190 | return $this;
1191 | }
1192 |
1193 | /**
1194 | * This method allows you to specify multiple (method chaining optional) GROUP BY statements for SQL queries.
1195 | *
1196 | * @uses $MySqliDb->groupBy('name');
1197 | *
1198 | * @param string $groupByField The name of the database field.
1199 | *
1200 | * @return MysqliDb
1201 | */
1202 | public function groupBy($groupByField)
1203 | {
1204 | $groupByField = preg_replace("/[^-a-z0-9\.\(\),_\* <>=!]+/i", '', $groupByField);
1205 |
1206 | $this->_groupBy[] = $groupByField;
1207 | return $this;
1208 | }
1209 |
1210 |
1211 | /**
1212 | * This method sets the current table lock method.
1213 | *
1214 | * @author Jonas Barascu
1215 | * @param string $method The table lock method. Can be READ or WRITE.
1216 | *
1217 | * @throws Exception
1218 | * @return MysqliDb
1219 | */
1220 | public function setLockMethod($method)
1221 | {
1222 | // Switch the uppercase string
1223 | switch(strtoupper($method)) {
1224 | // Is it READ or WRITE?
1225 | case "READ" || "WRITE":
1226 | // Succeed
1227 | $this->_tableLockMethod = $method;
1228 | break;
1229 | default:
1230 | // Else throw an exception
1231 | throw new Exception("Bad lock type: Can be either READ or WRITE");
1232 | break;
1233 | }
1234 | return $this;
1235 | }
1236 |
1237 | /**
1238 | * Locks a table for R/W action.
1239 | *
1240 | * @author Jonas Barascu
1241 | * @param string $table The table to be locked. Can be a table or a view.
1242 | *
1243 | * @throws Exception
1244 | * @return MysqliDb if succeeeded;
1245 | */
1246 | public function lock($table)
1247 | {
1248 | // Main Query
1249 | $this->_query = "LOCK TABLES";
1250 |
1251 | // Is the table an array?
1252 | if(gettype($table) == "array") {
1253 | // Loop trough it and attach it to the query
1254 | foreach($table as $key => $value) {
1255 | if(gettype($value) == "string") {
1256 | if($key > 0) {
1257 | $this->_query .= ",";
1258 | }
1259 | $this->_query .= " ".self::$prefix.$value." ".$this->_tableLockMethod;
1260 | }
1261 | }
1262 | }
1263 | else{
1264 | // Build the table prefix
1265 | $table = self::$prefix . $table;
1266 |
1267 | // Build the query
1268 | $this->_query = "LOCK TABLES ".$table." ".$this->_tableLockMethod;
1269 | }
1270 |
1271 | // Exceute the query unprepared because LOCK only works with unprepared statements.
1272 | $result = $this->queryUnprepared($this->_query);
1273 | $errno = $this->mysqli()->errno;
1274 |
1275 | // Reset the query
1276 | $this->reset();
1277 |
1278 | // Are there rows modified?
1279 | if($result) {
1280 | // Return true
1281 | // We can't return ourself because if one table gets locked, all other ones get unlocked!
1282 | return true;
1283 | }
1284 | // Something went wrong
1285 | else {
1286 | throw new Exception("Locking of table ".$table." failed", $errno);
1287 | }
1288 |
1289 | // Return the success value
1290 | return false;
1291 | }
1292 |
1293 | /**
1294 | * Unlocks all tables in a database.
1295 | * Also commits transactions.
1296 | *
1297 | * @author Jonas Barascu
1298 | * @return MysqliDb
1299 | */
1300 | public function unlock()
1301 | {
1302 | // Build the query
1303 | $this->_query = "UNLOCK TABLES";
1304 |
1305 | // Exceute the query unprepared because UNLOCK and LOCK only works with unprepared statements.
1306 | $result = $this->queryUnprepared($this->_query);
1307 | $errno = $this->mysqli()->errno;
1308 |
1309 | // Reset the query
1310 | $this->reset();
1311 |
1312 | // Are there rows modified?
1313 | if($result) {
1314 | // return self
1315 | return $this;
1316 | }
1317 | // Something went wrong
1318 | else {
1319 | throw new Exception("Unlocking of tables failed", $errno);
1320 | }
1321 |
1322 |
1323 | // Return self
1324 | return $this;
1325 | }
1326 |
1327 |
1328 | /**
1329 | * This methods returns the ID of the last inserted item
1330 | *
1331 | * @return int The last inserted item ID.
1332 | */
1333 | public function getInsertId()
1334 | {
1335 | return $this->mysqli()->insert_id;
1336 | }
1337 |
1338 | /**
1339 | * Escape harmful characters which might affect a query.
1340 | *
1341 | * @param string $str The string to escape.
1342 | *
1343 | * @return string The escaped string.
1344 | */
1345 | public function escape($str)
1346 | {
1347 | return $this->mysqli()->real_escape_string($str);
1348 | }
1349 |
1350 | /**
1351 | * Method to call mysqli->ping() to keep unused connections open on
1352 | * long-running scripts, or to reconnect timed out connections (if php.ini has
1353 | * global mysqli.reconnect set to true). Can't do this directly using object
1354 | * since _mysqli is protected.
1355 | *
1356 | * @return bool True if connection is up
1357 | */
1358 | public function ping()
1359 | {
1360 | return $this->mysqli()->ping();
1361 | }
1362 |
1363 | /**
1364 | * This method is needed for prepared statements. They require
1365 | * the data type of the field to be bound with "i" s", etc.
1366 | * This function takes the input, determines what type it is,
1367 | * and then updates the param_type.
1368 | *
1369 | * @param mixed $item Input to determine the type.
1370 | *
1371 | * @return string The joined parameter types.
1372 | */
1373 | protected function _determineType($item)
1374 | {
1375 | switch (gettype($item)) {
1376 | case 'NULL':
1377 | case 'string':
1378 | return 's';
1379 | break;
1380 |
1381 | case 'boolean':
1382 | case 'integer':
1383 | return 'i';
1384 | break;
1385 |
1386 | case 'blob':
1387 | return 'b';
1388 | break;
1389 |
1390 | case 'double':
1391 | return 'd';
1392 | break;
1393 | }
1394 | return '';
1395 | }
1396 |
1397 | /**
1398 | * Helper function to add variables into bind parameters array
1399 | *
1400 | * @param string Variable value
1401 | */
1402 | protected function _bindParam($value)
1403 | {
1404 | $this->_bindParams[0] .= $this->_determineType($value);
1405 | array_push($this->_bindParams, $value);
1406 | }
1407 |
1408 | /**
1409 | * Helper function to add variables into bind parameters array in bulk
1410 | *
1411 | * @param array $values Variable with values
1412 | */
1413 | protected function _bindParams($values)
1414 | {
1415 | foreach ($values as $value) {
1416 | $this->_bindParam($value);
1417 | }
1418 | }
1419 |
1420 | /**
1421 | * Helper function to add variables into bind parameters array and will return
1422 | * its SQL part of the query according to operator in ' $operator ?' or
1423 | * ' $operator ($subquery) ' formats
1424 | *
1425 | * @param string $operator
1426 | * @param mixed $value Variable with values
1427 | *
1428 | * @return string
1429 | */
1430 | protected function _buildPair($operator, $value)
1431 | {
1432 | if (!is_object($value)) {
1433 | $this->_bindParam($value);
1434 | return ' ' . $operator . ' ? ';
1435 | }
1436 |
1437 | $subQuery = $value->getSubQuery();
1438 | $this->_bindParams($subQuery['params']);
1439 |
1440 | return " " . $operator . " (" . $subQuery['query'] . ") " . $subQuery['alias'];
1441 | }
1442 |
1443 | /**
1444 | * Internal function to build and execute INSERT/REPLACE calls
1445 | *
1446 | * @param string $tableName The name of the table.
1447 | * @param array $insertData Data containing information for inserting into the DB.
1448 | * @param string $operation Type of operation (INSERT, REPLACE)
1449 | *
1450 | * @return bool Boolean indicating whether the insert query was completed succesfully.
1451 | */
1452 | private function _buildInsert($tableName, $insertData, $operation)
1453 | {
1454 | if ($this->isSubQuery) {
1455 | return;
1456 | }
1457 |
1458 | $this->_query = $operation . " " . implode(' ', $this->_queryOptions) . " INTO " . self::$prefix . $tableName;
1459 | $stmt = $this->_buildQuery(null, $insertData);
1460 | $status = $stmt->execute();
1461 | $this->_stmtError = $stmt->error;
1462 | $this->_stmtErrno = $stmt->errno;
1463 | $haveOnDuplicate = !empty ($this->_updateColumns);
1464 | $this->reset();
1465 | $this->count = $stmt->affected_rows;
1466 |
1467 | if ($stmt->affected_rows < 1) {
1468 | // in case of onDuplicate() usage, if no rows were inserted
1469 | if ($status && $haveOnDuplicate) {
1470 | return true;
1471 | }
1472 | return false;
1473 | }
1474 |
1475 | if ($stmt->insert_id > 0) {
1476 | return $stmt->insert_id;
1477 | }
1478 |
1479 | return true;
1480 | }
1481 |
1482 | /**
1483 | * Abstraction method that will compile the WHERE statement,
1484 | * any passed update data, and the desired rows.
1485 | * It then builds the SQL query.
1486 | *
1487 | * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
1488 | * or only $count
1489 | * @param array $tableData Should contain an array of data for updating the database.
1490 | *
1491 | * @return mysqli_stmt Returns the $stmt object.
1492 | */
1493 | protected function _buildQuery($numRows = null, $tableData = null)
1494 | {
1495 | // $this->_buildJoinOld();
1496 | $this->_buildJoin();
1497 | $this->_buildInsertQuery($tableData);
1498 | $this->_buildCondition('WHERE', $this->_where);
1499 | $this->_buildGroupBy();
1500 | $this->_buildCondition('HAVING', $this->_having);
1501 | $this->_buildOrderBy();
1502 | $this->_buildLimit($numRows);
1503 | $this->_buildOnDuplicate($tableData);
1504 |
1505 | if ($this->_forUpdate) {
1506 | $this->_query .= ' FOR UPDATE';
1507 | }
1508 | if ($this->_lockInShareMode) {
1509 | $this->_query .= ' LOCK IN SHARE MODE';
1510 | }
1511 |
1512 | $this->_lastQuery = $this->replacePlaceHolders($this->_query, $this->_bindParams);
1513 |
1514 | if ($this->isSubQuery) {
1515 | return;
1516 | }
1517 |
1518 | // Prepare query
1519 | $stmt = $this->_prepareQuery();
1520 |
1521 | // Bind parameters to statement if any
1522 | if (count($this->_bindParams) > 1) {
1523 | call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
1524 | }
1525 |
1526 | return $stmt;
1527 | }
1528 |
1529 | /**
1530 | * This helper method takes care of prepared statements' "bind_result method
1531 | * , when the number of variables to pass is unknown.
1532 | *
1533 | * @param mysqli_stmt $stmt Equal to the prepared statement object.
1534 | *
1535 | * @return array The results of the SQL fetch.
1536 | */
1537 | protected function _dynamicBindResults(mysqli_stmt $stmt)
1538 | {
1539 | $parameters = array();
1540 | $results = array();
1541 | /**
1542 | * @see http://php.net/manual/en/mysqli-result.fetch-fields.php
1543 | */
1544 | $mysqlLongType = 252;
1545 | $shouldStoreResult = false;
1546 |
1547 | $meta = $stmt->result_metadata();
1548 |
1549 | // if $meta is false yet sqlstate is true, there's no sql error but the query is
1550 | // most likely an update/insert/delete which doesn't produce any results
1551 | if (!$meta && $stmt->sqlstate)
1552 | return array();
1553 |
1554 | $row = array();
1555 | while ($field = $meta->fetch_field()) {
1556 | if ($field->type == $mysqlLongType) {
1557 | $shouldStoreResult = true;
1558 | }
1559 |
1560 | if ($this->_nestJoin && $field->table != $this->_tableName) {
1561 | $field->table = substr($field->table, strlen(self::$prefix));
1562 | $row[$field->table][$field->name] = null;
1563 | $parameters[] = & $row[$field->table][$field->name];
1564 | } else {
1565 | $row[$field->name] = null;
1566 | $parameters[] = & $row[$field->name];
1567 | }
1568 | }
1569 |
1570 | // avoid out of memory bug in php 5.2 and 5.3. Mysqli allocates lot of memory for long*
1571 | // and blob* types. So to avoid out of memory issues store_result is used
1572 | // https://github.com/joshcam/PHP-MySQLi-Database-Class/pull/119
1573 | if ($shouldStoreResult) {
1574 | $stmt->store_result();
1575 | }
1576 |
1577 | call_user_func_array(array($stmt, 'bind_result'), $parameters);
1578 |
1579 | $this->totalCount = 0;
1580 | $this->count = 0;
1581 |
1582 | while ($stmt->fetch()) {
1583 | if ($this->returnType == 'object') {
1584 | $result = new stdClass ();
1585 | foreach ($row as $key => $val) {
1586 | if (is_array($val)) {
1587 | $result->$key = new stdClass ();
1588 | foreach ($val as $k => $v) {
1589 | $result->$key->$k = $v;
1590 | }
1591 | } else {
1592 | $result->$key = $val;
1593 | }
1594 | }
1595 | } else {
1596 | $result = array();
1597 | foreach ($row as $key => $val) {
1598 | if (is_array($val)) {
1599 | foreach ($val as $k => $v) {
1600 | $result[$key][$k] = $v;
1601 | }
1602 | } else {
1603 | $result[$key] = $val;
1604 | }
1605 | }
1606 | }
1607 | $this->count++;
1608 | if ($this->_mapKey) {
1609 | $results[$row[$this->_mapKey]] = count($row) > 2 ? $result : end($result);
1610 | } else {
1611 | array_push($results, $result);
1612 | }
1613 | }
1614 |
1615 | if ($shouldStoreResult) {
1616 | $stmt->free_result();
1617 | }
1618 |
1619 | $stmt->close();
1620 |
1621 | // stored procedures sometimes can return more then 1 resultset
1622 | if ($this->mysqli()->more_results()) {
1623 | $this->mysqli()->next_result();
1624 | }
1625 |
1626 | if (in_array('SQL_CALC_FOUND_ROWS', $this->_queryOptions)) {
1627 | $stmt = $this->mysqli()->query('SELECT FOUND_ROWS()');
1628 | $totalCount = $stmt->fetch_row();
1629 | $this->totalCount = $totalCount[0];
1630 | }
1631 |
1632 | if ($this->returnType == 'json') {
1633 | return json_encode($results);
1634 | }
1635 |
1636 | return $results;
1637 | }
1638 |
1639 | /**
1640 | * Abstraction method that will build an JOIN part of the query
1641 | *
1642 | * @return void
1643 | */
1644 | protected function _buildJoinOld()
1645 | {
1646 | if (empty($this->_join)) {
1647 | return;
1648 | }
1649 |
1650 | foreach ($this->_join as $data) {
1651 | list ($joinType, $joinTable, $joinCondition) = $data;
1652 |
1653 | if (is_object($joinTable)) {
1654 | $joinStr = $this->_buildPair("", $joinTable);
1655 | } else {
1656 | $joinStr = $joinTable;
1657 | }
1658 |
1659 | $this->_query .= " " . $joinType . " JOIN " . $joinStr .
1660 | (false !== stripos($joinCondition, 'using') ? " " : " on ")
1661 | . $joinCondition;
1662 | }
1663 | }
1664 |
1665 | /**
1666 | * Insert/Update query helper
1667 | *
1668 | * @param array $tableData
1669 | * @param array $tableColumns
1670 | * @param bool $isInsert INSERT operation flag
1671 | *
1672 | * @throws Exception
1673 | */
1674 | public function _buildDataPairs($tableData, $tableColumns, $isInsert)
1675 | {
1676 | foreach ($tableColumns as $column) {
1677 | $value = $tableData[$column];
1678 |
1679 | if (!$isInsert) {
1680 | if(strpos($column,'.')===false) {
1681 | $this->_query .= "`" . $column . "` = ";
1682 | } else {
1683 | $this->_query .= str_replace('.','.`',$column) . "` = ";
1684 | }
1685 | }
1686 |
1687 | // Subquery value
1688 | if ($value instanceof MysqliDb) {
1689 | $this->_query .= $this->_buildPair("", $value) . ", ";
1690 | continue;
1691 | }
1692 |
1693 | // Simple value
1694 | if (!is_array($value)) {
1695 | $this->_bindParam($value);
1696 | $this->_query .= '?, ';
1697 | continue;
1698 | }
1699 |
1700 | // Function value
1701 | $key = key($value);
1702 | $val = $value[$key];
1703 | switch ($key) {
1704 | case '[I]':
1705 | $this->_query .= $column . $val . ", ";
1706 | break;
1707 | case '[F]':
1708 | $this->_query .= $val[0] . ", ";
1709 | if (!empty($val[1])) {
1710 | $this->_bindParams($val[1]);
1711 | }
1712 | break;
1713 | case '[N]':
1714 | if ($val == null) {
1715 | $this->_query .= "!" . $column . ", ";
1716 | } else {
1717 | $this->_query .= "!" . $val . ", ";
1718 | }
1719 | break;
1720 | default:
1721 | throw new Exception("Wrong operation");
1722 | }
1723 | }
1724 | $this->_query = rtrim($this->_query, ', ');
1725 | }
1726 |
1727 | /**
1728 | * Helper function to add variables into the query statement
1729 | *
1730 | * @param array $tableData Variable with values
1731 | */
1732 | protected function _buildOnDuplicate($tableData)
1733 | {
1734 | if (is_array($this->_updateColumns) && !empty($this->_updateColumns)) {
1735 | $this->_query .= " ON DUPLICATE KEY UPDATE ";
1736 | if ($this->_lastInsertId) {
1737 | $this->_query .= $this->_lastInsertId . "=LAST_INSERT_ID (" . $this->_lastInsertId . "), ";
1738 | }
1739 |
1740 | foreach ($this->_updateColumns as $key => $val) {
1741 | // skip all params without a value
1742 | if (is_numeric($key)) {
1743 | $this->_updateColumns[$val] = '';
1744 | unset($this->_updateColumns[$key]);
1745 | } else {
1746 | $tableData[$key] = $val;
1747 | }
1748 | }
1749 | $this->_buildDataPairs($tableData, array_keys($this->_updateColumns), false);
1750 | }
1751 | }
1752 |
1753 | /**
1754 | * Abstraction method that will build an INSERT or UPDATE part of the query
1755 | *
1756 | * @param array $tableData
1757 | */
1758 | protected function _buildInsertQuery($tableData)
1759 | {
1760 | if (!is_array($tableData)) {
1761 | return;
1762 | }
1763 |
1764 | $isInsert = preg_match('/^[INSERT|REPLACE]/', $this->_query);
1765 | $dataColumns = array_keys($tableData);
1766 | if ($isInsert) {
1767 | if (isset ($dataColumns[0]))
1768 | $this->_query .= ' (`' . implode($dataColumns, '`, `') . '`) ';
1769 | $this->_query .= ' VALUES (';
1770 | } else {
1771 | $this->_query .= " SET ";
1772 | }
1773 |
1774 | $this->_buildDataPairs($tableData, $dataColumns, $isInsert);
1775 |
1776 | if ($isInsert) {
1777 | $this->_query .= ')';
1778 | }
1779 | }
1780 |
1781 | /**
1782 | * Abstraction method that will build the part of the WHERE conditions
1783 | *
1784 | * @param string $operator
1785 | * @param array $conditions
1786 | */
1787 | protected function _buildCondition($operator, &$conditions)
1788 | {
1789 | if (empty($conditions)) {
1790 | return;
1791 | }
1792 |
1793 | //Prepare the where portion of the query
1794 | $this->_query .= ' ' . $operator;
1795 |
1796 | foreach ($conditions as $cond) {
1797 | list ($concat, $varName, $operator, $val) = $cond;
1798 | $this->_query .= " " . $concat . " " . $varName;
1799 |
1800 | switch (strtolower($operator)) {
1801 | case 'not in':
1802 | case 'in':
1803 | $comparison = ' ' . $operator . ' (';
1804 | if (is_object($val)) {
1805 | $comparison .= $this->_buildPair("", $val);
1806 | } else {
1807 | foreach ($val as $v) {
1808 | $comparison .= ' ?,';
1809 | $this->_bindParam($v);
1810 | }
1811 | }
1812 | $this->_query .= rtrim($comparison, ',') . ' ) ';
1813 | break;
1814 | case 'not between':
1815 | case 'between':
1816 | $this->_query .= " $operator ? AND ? ";
1817 | $this->_bindParams($val);
1818 | break;
1819 | case 'not exists':
1820 | case 'exists':
1821 | $this->_query.= $operator . $this->_buildPair("", $val);
1822 | break;
1823 | default:
1824 | if (is_array($val)) {
1825 | $this->_bindParams($val);
1826 | } elseif ($val === null) {
1827 | $this->_query .= ' ' . $operator . " NULL";
1828 | } elseif ($val != 'DBNULL' || $val == '0') {
1829 | $this->_query .= $this->_buildPair($operator, $val);
1830 | }
1831 | }
1832 | }
1833 | }
1834 |
1835 | /**
1836 | * Abstraction method that will build the GROUP BY part of the WHERE statement
1837 | *
1838 | * @return void
1839 | */
1840 | protected function _buildGroupBy()
1841 | {
1842 | if (empty($this->_groupBy)) {
1843 | return;
1844 | }
1845 |
1846 | $this->_query .= " GROUP BY ";
1847 |
1848 | foreach ($this->_groupBy as $key => $value) {
1849 | $this->_query .= $value . ", ";
1850 | }
1851 |
1852 | $this->_query = rtrim($this->_query, ', ') . " ";
1853 | }
1854 |
1855 | /**
1856 | * Abstraction method that will build the LIMIT part of the WHERE statement
1857 | *
1858 | * @return void
1859 | */
1860 | protected function _buildOrderBy()
1861 | {
1862 | if (empty($this->_orderBy)) {
1863 | return;
1864 | }
1865 |
1866 | $this->_query .= " ORDER BY ";
1867 | foreach ($this->_orderBy as $prop => $value) {
1868 | if (strtolower(str_replace(" ", "", $prop)) == 'rand()') {
1869 | $this->_query .= "rand(), ";
1870 | } else {
1871 | $this->_query .= $prop . " " . $value . ", ";
1872 | }
1873 | }
1874 |
1875 | $this->_query = rtrim($this->_query, ', ') . " ";
1876 | }
1877 |
1878 | /**
1879 | * Abstraction method that will build the LIMIT part of the WHERE statement
1880 | *
1881 | * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
1882 | * or only $count
1883 | *
1884 | * @return void
1885 | */
1886 | protected function _buildLimit($numRows)
1887 | {
1888 | if (!isset($numRows)) {
1889 | return;
1890 | }
1891 |
1892 | if (is_array($numRows)) {
1893 | $this->_query .= ' LIMIT ' . (int) $numRows[0] . ', ' . (int) $numRows[1];
1894 | } else {
1895 | $this->_query .= ' LIMIT ' . (int) $numRows;
1896 | }
1897 | }
1898 |
1899 | /**
1900 | * Method attempts to prepare the SQL query
1901 | * and throws an error if there was a problem.
1902 | *
1903 | * @return mysqli_stmt
1904 | * @throws Exception
1905 | */
1906 | protected function _prepareQuery()
1907 | {
1908 | $stmt = $this->mysqli()->prepare($this->_query);
1909 |
1910 | if ($stmt !== false) {
1911 | if ($this->traceEnabled)
1912 | $this->traceStartQ = microtime(true);
1913 | return $stmt;
1914 | }
1915 |
1916 | if ($this->mysqli()->errno === 2006 && $this->autoReconnect === true && $this->autoReconnectCount === 0) {
1917 | $this->connect($this->defConnectionName);
1918 | $this->autoReconnectCount++;
1919 | return $this->_prepareQuery();
1920 | }
1921 |
1922 | $error = $this->mysqli()->error;
1923 | $query = $this->_query;
1924 | $errno = $this->mysqli()->errno;
1925 | $this->reset();
1926 | throw new Exception(sprintf('%s query: %s', $error, $query), $errno);
1927 | }
1928 |
1929 | /**
1930 | * Referenced data array is required by mysqli since PHP 5.3+
1931 | *
1932 | * @param array $arr
1933 | *
1934 | * @return array
1935 | */
1936 | protected function refValues(array &$arr)
1937 | {
1938 | //Reference in the function arguments are required for HHVM to work
1939 | //https://github.com/facebook/hhvm/issues/5155
1940 | //Referenced data array is required by mysqli since PHP 5.3+
1941 | if (strnatcmp(phpversion(), '5.3') >= 0) {
1942 | $refs = array();
1943 | foreach ($arr as $key => $value) {
1944 | $refs[$key] = & $arr[$key];
1945 | }
1946 | return $refs;
1947 | }
1948 | return $arr;
1949 | }
1950 |
1951 | /**
1952 | * Function to replace ? with variables from bind variable
1953 | *
1954 | * @param string $str
1955 | * @param array $vals
1956 | *
1957 | * @return string
1958 | */
1959 | protected function replacePlaceHolders($str, $vals)
1960 | {
1961 | $i = 1;
1962 | $newStr = "";
1963 |
1964 | if (empty($vals)) {
1965 | return $str;
1966 | }
1967 |
1968 | while ($pos = strpos($str, "?")) {
1969 | $val = $vals[$i++];
1970 | if (is_object($val)) {
1971 | $val = '[object]';
1972 | }
1973 | if ($val === null) {
1974 | $val = 'NULL';
1975 | }
1976 | $newStr .= substr($str, 0, $pos) . "'" . $val . "'";
1977 | $str = substr($str, $pos + 1);
1978 | }
1979 | $newStr .= $str;
1980 | return $newStr;
1981 | }
1982 |
1983 | /**
1984 | * Method returns last executed query
1985 | *
1986 | * @return string
1987 | */
1988 | public function getLastQuery()
1989 | {
1990 | return $this->_lastQuery;
1991 | }
1992 |
1993 | /**
1994 | * Method returns mysql error
1995 | *
1996 | * @return string
1997 | */
1998 | public function getLastError()
1999 | {
2000 | if (!isset($this->_mysqli[$this->defConnectionName])) {
2001 | return "mysqli is null";
2002 | }
2003 | return trim($this->_stmtError . " " . $this->mysqli()->error);
2004 | }
2005 |
2006 | /**
2007 | * Method returns mysql error code
2008 | * @return int
2009 | */
2010 | public function getLastErrno () {
2011 | return $this->_stmtErrno;
2012 | }
2013 |
2014 | /**
2015 | * Mostly internal method to get query and its params out of subquery object
2016 | * after get() and getAll()
2017 | *
2018 | * @return array
2019 | */
2020 | public function getSubQuery()
2021 | {
2022 | if (!$this->isSubQuery) {
2023 | return null;
2024 | }
2025 |
2026 | array_shift($this->_bindParams);
2027 | $val = Array('query' => $this->_query,
2028 | 'params' => $this->_bindParams,
2029 | 'alias' => isset($this->connectionsSettings[$this->defConnectionName]) ? $this->connectionsSettings[$this->defConnectionName]['host'] : null
2030 | );
2031 | $this->reset();
2032 | return $val;
2033 | }
2034 |
2035 | /* Helper functions */
2036 |
2037 | /**
2038 | * Method returns generated interval function as a string
2039 | *
2040 | * @param string $diff interval in the formats:
2041 | * "1", "-1d" or "- 1 day" -- For interval - 1 day
2042 | * Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear
2043 | * Default null;
2044 | * @param string $func Initial date
2045 | *
2046 | * @return string
2047 | * @throws Exception
2048 | */
2049 | public function interval($diff, $func = "NOW()")
2050 | {
2051 | $types = Array("s" => "second", "m" => "minute", "h" => "hour", "d" => "day", "M" => "month", "Y" => "year");
2052 | $incr = '+';
2053 | $items = '';
2054 | $type = 'd';
2055 |
2056 | if ($diff && preg_match('/([+-]?) ?([0-9]+) ?([a-zA-Z]?)/', $diff, $matches)) {
2057 | if (!empty($matches[1])) {
2058 | $incr = $matches[1];
2059 | }
2060 |
2061 | if (!empty($matches[2])) {
2062 | $items = $matches[2];
2063 | }
2064 |
2065 | if (!empty($matches[3])) {
2066 | $type = $matches[3];
2067 | }
2068 |
2069 | if (!in_array($type, array_keys($types))) {
2070 | throw new Exception("invalid interval type in '{$diff}'");
2071 | }
2072 |
2073 | $func .= " " . $incr . " interval " . $items . " " . $types[$type] . " ";
2074 | }
2075 | return $func;
2076 | }
2077 |
2078 | /**
2079 | * Method returns generated interval function as an insert/update function
2080 | *
2081 | * @param string $diff interval in the formats:
2082 | * "1", "-1d" or "- 1 day" -- For interval - 1 day
2083 | * Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear
2084 | * Default null;
2085 | * @param string $func Initial date
2086 | *
2087 | * @return array
2088 | */
2089 | public function now($diff = null, $func = "NOW()")
2090 | {
2091 | return array("[F]" => Array($this->interval($diff, $func)));
2092 | }
2093 |
2094 | /**
2095 | * Method generates incremental function call
2096 | *
2097 | * @param int $num increment by int or float. 1 by default
2098 | *
2099 | * @throws Exception
2100 | * @return array
2101 | */
2102 | public function inc($num = 1)
2103 | {
2104 | if (!is_numeric($num)) {
2105 | throw new Exception('Argument supplied to inc must be a number');
2106 | }
2107 | return array("[I]" => "+" . $num);
2108 | }
2109 |
2110 | /**
2111 | * Method generates decrimental function call
2112 | *
2113 | * @param int $num increment by int or float. 1 by default
2114 | *
2115 | * @return array
2116 | * @throws Exception
2117 | */
2118 | public function dec($num = 1)
2119 | {
2120 | if (!is_numeric($num)) {
2121 | throw new Exception('Argument supplied to dec must be a number');
2122 | }
2123 | return array("[I]" => "-" . $num);
2124 | }
2125 |
2126 | /**
2127 | * Method generates change boolean function call
2128 | *
2129 | * @param string $col column name. null by default
2130 | *
2131 | * @return array
2132 | */
2133 | public function not($col = null)
2134 | {
2135 | return array("[N]" => (string) $col);
2136 | }
2137 |
2138 | /**
2139 | * Method generates user defined function call
2140 | *
2141 | * @param string $expr user function body
2142 | * @param array $bindParams
2143 | *
2144 | * @return array
2145 | */
2146 | public function func($expr, $bindParams = null)
2147 | {
2148 | return array("[F]" => array($expr, $bindParams));
2149 | }
2150 |
2151 | /**
2152 | * Method creates new mysqlidb object for a subquery generation
2153 | *
2154 | * @param string $subQueryAlias
2155 | *
2156 | * @return MysqliDb
2157 | */
2158 | public static function subQuery($subQueryAlias = "")
2159 | {
2160 | return new self(array('host' => $subQueryAlias, 'isSubQuery' => true));
2161 | }
2162 |
2163 | /**
2164 | * Method returns a copy of a mysqlidb subquery object
2165 | *
2166 | * @return MysqliDb new mysqlidb object
2167 | */
2168 | public function copy()
2169 | {
2170 | $copy = unserialize(serialize($this));
2171 | $copy->_mysqli = array();
2172 | return $copy;
2173 | }
2174 |
2175 | /**
2176 | * Begin a transaction
2177 | *
2178 | * @uses mysqli->autocommit(false)
2179 | * @uses register_shutdown_function(array($this, "_transaction_shutdown_check"))
2180 | */
2181 | public function startTransaction()
2182 | {
2183 | $this->mysqli()->autocommit(false);
2184 | $this->_transaction_in_progress = true;
2185 | register_shutdown_function(array($this, "_transaction_status_check"));
2186 | }
2187 |
2188 | /**
2189 | * Transaction commit
2190 | *
2191 | * @uses mysqli->commit();
2192 | * @uses mysqli->autocommit(true);
2193 | */
2194 | public function commit()
2195 | {
2196 | $result = $this->mysqli()->commit();
2197 | $this->_transaction_in_progress = false;
2198 | $this->mysqli()->autocommit(true);
2199 | return $result;
2200 | }
2201 |
2202 | /**
2203 | * Transaction rollback function
2204 | *
2205 | * @uses mysqli->rollback();
2206 | * @uses mysqli->autocommit(true);
2207 | */
2208 | public function rollback()
2209 | {
2210 | $result = $this->mysqli()->rollback();
2211 | $this->_transaction_in_progress = false;
2212 | $this->mysqli()->autocommit(true);
2213 | return $result;
2214 | }
2215 |
2216 | /**
2217 | * Shutdown handler to rollback uncommited operations in order to keep
2218 | * atomic operations sane.
2219 | *
2220 | * @uses mysqli->rollback();
2221 | */
2222 | public function _transaction_status_check()
2223 | {
2224 | if (!$this->_transaction_in_progress) {
2225 | return;
2226 | }
2227 | $this->rollback();
2228 | }
2229 |
2230 | /**
2231 | * Query exection time tracking switch
2232 | *
2233 | * @param bool $enabled Enable execution time tracking
2234 | * @param string $stripPrefix Prefix to strip from the path in exec log
2235 | *
2236 | * @return MysqliDb
2237 | */
2238 | public function setTrace($enabled, $stripPrefix = null)
2239 | {
2240 | $this->traceEnabled = $enabled;
2241 | $this->traceStripPrefix = $stripPrefix;
2242 | return $this;
2243 | }
2244 |
2245 | /**
2246 | * Get where and what function was called for query stored in MysqliDB->trace
2247 | *
2248 | * @return string with information
2249 | */
2250 | private function _traceGetCaller()
2251 | {
2252 | $dd = debug_backtrace();
2253 | $caller = next($dd);
2254 | while (isset($caller) && $caller["file"] == __FILE__) {
2255 | $caller = next($dd);
2256 | }
2257 |
2258 | return __CLASS__ . "->" . $caller["function"] . "() >> file \"" .
2259 | str_replace($this->traceStripPrefix, '', $caller["file"]) . "\" line #" . $caller["line"] . " ";
2260 | }
2261 |
2262 | /**
2263 | * Method to check if needed table is created
2264 | *
2265 | * @param array $tables Table name or an Array of table names to check
2266 | *
2267 | * @return bool True if table exists
2268 | */
2269 | public function tableExists($tables)
2270 | {
2271 | $tables = !is_array($tables) ? Array($tables) : $tables;
2272 | $count = count($tables);
2273 | if ($count == 0) {
2274 | return false;
2275 | }
2276 |
2277 | foreach ($tables as $i => $value)
2278 | $tables[$i] = self::$prefix . $value;
2279 | $db = isset($this->connectionsSettings[$this->defConnectionName]) ? $this->connectionsSettings[$this->defConnectionName]['db'] : null;
2280 | $this->where('table_schema', $db);
2281 | $this->where('table_name', $tables, 'in');
2282 | $this->get('information_schema.tables', $count);
2283 | return $this->count == $count;
2284 | }
2285 |
2286 | /**
2287 | * Return result as an associative array with $idField field value used as a record key
2288 | *
2289 | * Array Returns an array($k => $v) if get(.."param1, param2"), array ($k => array ($v, $v)) otherwise
2290 | *
2291 | * @param string $idField field name to use for a mapped element key
2292 | *
2293 | * @return MysqliDb
2294 | */
2295 | public function map($idField)
2296 | {
2297 | $this->_mapKey = $idField;
2298 | return $this;
2299 | }
2300 |
2301 | /**
2302 | * Pagination wraper to get()
2303 | *
2304 | * @access public
2305 | * @param string $table The name of the database table to work with
2306 | * @param int $page Page number
2307 | * @param array|string $fields Array or coma separated list of fields to fetch
2308 | * @return array
2309 | */
2310 | public function paginate ($table, $page, $fields = null) {
2311 | $offset = $this->pageLimit * ($page - 1);
2312 | $res = $this->withTotalCount()->get ($table, Array ($offset, $this->pageLimit), $fields);
2313 | $this->totalPages = ceil($this->totalCount / $this->pageLimit);
2314 | return $res;
2315 | }
2316 |
2317 | /**
2318 | * This method allows you to specify multiple (method chaining optional) AND WHERE statements for the join table on part of the SQL query.
2319 | *
2320 | * @uses $dbWrapper->joinWhere('user u', 'u.id', 7)->where('user u', 'u.title', 'MyTitle');
2321 | *
2322 | * @param string $whereJoin The name of the table followed by its prefix.
2323 | * @param string $whereProp The name of the database field.
2324 | * @param mixed $whereValue The value of the database field.
2325 | *
2326 | * @return $this
2327 | */
2328 | public function joinWhere($whereJoin, $whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
2329 | {
2330 | $this->_joinAnd[$whereJoin][] = Array ($cond, $whereProp, $operator, $whereValue);
2331 | return $this;
2332 | }
2333 |
2334 | /**
2335 | * This method allows you to specify multiple (method chaining optional) OR WHERE statements for the join table on part of the SQL query.
2336 | *
2337 | * @uses $dbWrapper->joinWhere('user u', 'u.id', 7)->where('user u', 'u.title', 'MyTitle');
2338 | *
2339 | * @param string $whereJoin The name of the table followed by its prefix.
2340 | * @param string $whereProp The name of the database field.
2341 | * @param mixed $whereValue The value of the database field.
2342 | *
2343 | * @return dbWrapper
2344 | */
2345 | public function joinOrWhere($whereJoin, $whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
2346 | {
2347 | return $this->joinWhere($whereJoin, $whereProp, $whereValue, $operator, 'OR');
2348 | }
2349 |
2350 | /**
2351 | * Abstraction method that will build an JOIN part of the query
2352 | */
2353 | protected function _buildJoin () {
2354 | if (empty ($this->_join))
2355 | return;
2356 |
2357 | foreach ($this->_join as $data) {
2358 | list ($joinType, $joinTable, $joinCondition) = $data;
2359 |
2360 | if (is_object ($joinTable))
2361 | $joinStr = $this->_buildPair ("", $joinTable);
2362 | else
2363 | $joinStr = $joinTable;
2364 |
2365 | $this->_query .= " " . $joinType. " JOIN " . $joinStr .
2366 | (false !== stripos($joinCondition, 'using') ? " " : " on ")
2367 | . $joinCondition;
2368 |
2369 | // Add join and query
2370 | if (!empty($this->_joinAnd) && isset($this->_joinAnd[$joinStr])) {
2371 | foreach($this->_joinAnd[$joinStr] as $join_and_cond) {
2372 | list ($concat, $varName, $operator, $val) = $join_and_cond;
2373 | $this->_query .= " " . $concat ." " . $varName;
2374 | $this->conditionToSql($operator, $val);
2375 | }
2376 | }
2377 | }
2378 | }
2379 |
2380 | /**
2381 | * Convert a condition and value into the sql string
2382 | * @param String $operator The where constraint operator
2383 | * @param String $val The where constraint value
2384 | */
2385 | private function conditionToSql($operator, $val) {
2386 | switch (strtolower ($operator)) {
2387 | case 'not in':
2388 | case 'in':
2389 | $comparison = ' ' . $operator. ' (';
2390 | if (is_object ($val)) {
2391 | $comparison .= $this->_buildPair ("", $val);
2392 | } else {
2393 | foreach ($val as $v) {
2394 | $comparison .= ' ?,';
2395 | $this->_bindParam ($v);
2396 | }
2397 | }
2398 | $this->_query .= rtrim($comparison, ',').' ) ';
2399 | break;
2400 | case 'not between':
2401 | case 'between':
2402 | $this->_query .= " $operator ? AND ? ";
2403 | $this->_bindParams ($val);
2404 | break;
2405 | case 'not exists':
2406 | case 'exists':
2407 | $this->_query.= $operator . $this->_buildPair ("", $val);
2408 | break;
2409 | default:
2410 | if (is_array ($val))
2411 | $this->_bindParams ($val);
2412 | else if ($val === null)
2413 | $this->_query .= $operator . " NULL";
2414 | else if ($val != 'DBNULL' || $val == '0')
2415 | $this->_query .= $this->_buildPair ($operator, $val);
2416 | }
2417 | }
2418 | }
2419 |
2420 | // END class
2421 |
--------------------------------------------------------------------------------