1 <?php
2
3 4 5 6 7 8 9
10 class rex_sql implements Iterator
11 {
12 use rex_factory_trait;
13
14 15 16
17 const FORMAT_DATETIME = 'Y-m-d H:i:s';
18
19 20 21 22 23
24 const OPT_BUFFERED = 'buffered';
25
26 protected $debug;
27 protected $values;
28 protected $rawValues;
29 protected $fieldnames;
30 protected $rawFieldnames;
31 protected $tablenames;
32 protected $lastRow;
33 protected $table;
34 protected $wherevar;
35 protected $whereParams;
36 protected $rows;
37 protected $counter;
38 protected $query;
39 protected $params;
40 protected $DBID;
41
42
43 protected $records;
44
45
46 protected $stmt;
47
48
49 protected static $pdo = [];
50
51 52 53 54 55
56 protected function __construct($DBID = 1)
57 {
58 $this->debug = false;
59 $this->flush();
60 $this->selectDB($DBID);
61 }
62
63 64 65 66 67 68 69
70 protected function selectDB($DBID)
71 {
72 $this->DBID = $DBID;
73
74 try {
75 if (!isset(self::$pdo[$DBID])) {
76 $dbconfig = rex::getProperty('db');
77 $conn = self::createConnection(
78 $dbconfig[$DBID]['host'],
79 $dbconfig[$DBID]['name'],
80 $dbconfig[$DBID]['login'],
81 $dbconfig[$DBID]['password'],
82 $dbconfig[$DBID]['persistent']
83 );
84 self::$pdo[$DBID] = $conn;
85
86
87 $this->setQuery('SET SESSION SQL_MODE="", NAMES utf8mb4');
88 }
89 } catch (PDOException $e) {
90 throw new rex_sql_exception('Could not connect to database', $e, $this);
91 }
92 }
93
94 95 96 97 98 99 100 101 102
103 protected static function createConnection($host, $database, $login, $password, $persistent = false)
104 {
105 if (!$database) {
106 throw new InvalidArgumentException('Database name can not be empty.');
107 }
108
109 $dsn = 'mysql:host=' . $host . ';dbname=' . $database;
110 $options = [
111 PDO::ATTR_PERSISTENT => (bool) $persistent,
112 PDO::ATTR_FETCH_TABLE_NAMES => true,
113
114
115 ];
116
117 $dbh = @new PDO($dsn, $login, $password, $options);
118 $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
119 return $dbh;
120 }
121
122 123 124 125 126 127 128 129
130 protected static function getQueryDBID($qry)
131 {
132 $qry = trim($qry);
133
134 if (preg_match('/\(DB([1-9]){1}\)/i', $qry, $matches)) {
135 return $matches[1];
136 }
137
138 return false;
139 }
140
141 142 143 144 145 146 147 148
149 protected static function stripQueryDBID(&$qry)
150 {
151 $qry = trim($qry);
152
153 if (($qryDBID = self::getQueryDBID($qry)) !== false) {
154 $qry = substr($qry, 6);
155 }
156
157 return $qryDBID;
158 }
159
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
179 public static function getQueryType($qry)
180 {
181 $qry = trim($qry);
182
183 self::stripQueryDBID($qry);
184
185 if (preg_match('/^(SELECT|SHOW|UPDATE|INSERT|DELETE|REPLACE|CREATE|CALL|OPTIMIZE)/i', $qry, $matches)) {
186 return strtoupper($matches[1]);
187 }
188
189 return false;
190 }
191
192 193 194 195 196 197 198 199
200 public static function datetime($timestamp = null)
201 {
202 return date(self::FORMAT_DATETIME, null === $timestamp ? time() : $timestamp);
203 }
204
205 206 207 208 209 210 211 212 213 214 215
216 public function setDBQuery($query, array $params = [], array $options = [])
217 {
218
219 $oldDBID = $this->DBID;
220
221
222 if (($qryDBID = self::stripQueryDBID($query)) !== false) {
223 $this->selectDB($qryDBID);
224 }
225
226 $this->setQuery($query, $params, $options);
227
228
229 $this->DBID = $oldDBID;
230
231 return $this;
232 }
233
234 235 236 237 238 239 240
241 public function setDebug($debug = true)
242 {
243 $this->debug = $debug;
244
245 return $this;
246 }
247
248 249 250 251 252 253 254 255 256
257 public function prepareQuery($qry)
258 {
259 $pdo = self::$pdo[$this->DBID];
260 try {
261 $this->query = $qry;
262 $this->stmt = $pdo->prepare($qry);
263 return $this->stmt;
264 } catch (PDOException $e) {
265 throw new rex_sql_exception('Error while preparing statement "' . $qry . '"! ' . $e->getMessage(), $e, $this);
266 }
267 }
268
269 270 271 272 273 274 275 276 277 278
279 public function execute(array $params = [], array $options = [])
280 {
281 if (!$this->stmt) {
282 throw new rex_sql_exception('you need to prepare a query before calling execute()', null, $this);
283 }
284
285 $buffered = null;
286 $pdo = self::$pdo[$this->DBID];
287 if (isset($options[self::OPT_BUFFERED])) {
288 $buffered = $pdo->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);
289 $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $options[self::OPT_BUFFERED]);
290 }
291
292 try {
293 $this->flush();
294 $this->params = $params;
295
296 $this->stmt->execute($params);
297 $this->rows = $this->stmt->rowCount();
298 } catch (PDOException $e) {
299 throw new rex_sql_exception('Error while executing statement "' . $this->query . '" using params ' . json_encode($params) . '! ' . $e->getMessage(), $e, $this);
300 } finally {
301 if (null !== $buffered) {
302 $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $buffered);
303 }
304
305 if ($this->debug) {
306 $this->printError($this->query, $params);
307 }
308 }
309
310 return $this;
311 }
312
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
331 public function setQuery($query, array $params = [], array $options = [])
332 {
333
334 $this->flush();
335 $this->query = $query;
336 $this->params = $params;
337 $this->stmt = null;
338
339 if (!empty($params)) {
340 $this->prepareQuery($query);
341 $this->execute($params, $options);
342
343 return $this;
344 }
345
346 $buffered = null;
347 $pdo = self::$pdo[$this->DBID];
348 if (isset($options[self::OPT_BUFFERED])) {
349 $buffered = $pdo->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);
350 $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $options[self::OPT_BUFFERED]);
351 }
352
353 try {
354 $this->stmt = rex_timer::measure(__METHOD__, function () use ($pdo, $query) {
355 return $pdo->query($query);
356 });
357
358 $this->rows = $this->stmt->rowCount();
359 } catch (PDOException $e) {
360 throw new rex_sql_exception('Error while executing statement "' . $query . '"! ' . $e->getMessage(), $e, $this);
361 } finally {
362 if (null !== $buffered) {
363 $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $buffered);
364 }
365
366 if ($this->debug) {
367 $this->printError($query, $params);
368 }
369 }
370
371 return $this;
372 }
373
374 375 376 377 378 379 380
381 public function setTable($table)
382 {
383 $this->table = $table;
384
385 return $this;
386 }
387
388 389 390 391 392 393 394 395
396 public function setRawValue($colName, $value)
397 {
398 $this->rawValues[$colName] = $value;
399 unset($this->values[$colName]);
400
401 return $this;
402 }
403
404 405 406 407 408 409 410 411
412 public function setValue($colName, $value)
413 {
414 $this->values[$colName] = $value;
415 unset($this->rawValues[$colName]);
416
417 return $this;
418 }
419
420 421 422 423 424 425 426 427
428 public function setArrayValue($colName, array $value)
429 {
430 return $this->setValue($colName, json_encode($value));
431 }
432
433 434 435 436 437 438 439 440
441 public function setDateTimeValue($colName, $timestamp)
442 {
443 return $this->setValue($colName, self::datetime($timestamp));
444 }
445
446 447 448 449 450 451 452
453 public function setValues(array $valueArray)
454 {
455 foreach ($valueArray as $name => $value) {
456 $this->setValue($name, $value);
457 }
458
459 return $this;
460 }
461
462 463 464 465 466
467 public function hasValues()
468 {
469 return !empty($this->values);
470 }
471
472 473 474 475 476 477 478 479 480 481
482 protected function isValueOf($feld, $prop)
483 {
484 if ($prop == '') {
485 return true;
486 }
487 return strpos($this->getValue($feld), $prop) !== false;
488 }
489
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
506 public function addRecord(callable $callback)
507 {
508 $record = self::factory($this->DBID);
509
510 $callback($record);
511
512 $this->records[] = $record;
513
514 return $this;
515 }
516
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
537 public function setWhere($where, $whereParams = null)
538 {
539 if (is_array($where)) {
540 $this->wherevar = 'WHERE ' . $this->buildWhereArg($where);
541 $this->whereParams = $where;
542 } elseif (is_string($where) && is_array($whereParams)) {
543 $this->wherevar = 'WHERE ' . $where;
544 $this->whereParams = $whereParams;
545 } elseif (is_string($where)) {
546
547
548
549
550 $this->wherevar = 'WHERE ' . $where;
551 $this->whereParams = [];
552 } else {
553 throw new rex_sql_exception('expecting $where to be an array, "' . gettype($where) . '" given!', null, $this);
554 }
555
556 return $this;
557 }
558
559 560 561 562 563 564 565 566 567
568 private function buildWhereArg(array $arrFields, $level = 0)
569 {
570 if ($level % 2 == 1) {
571 $op = ' OR ';
572 } else {
573 $op = ' AND ';
574 }
575
576 $qry = '';
577 foreach ($arrFields as $fld_name => $value) {
578 if (is_array($value)) {
579 $arg = '(' . $this->buildWhereArg($value, $level + 1) . ')';
580 } else {
581 $arg = $this->escapeIdentifier($fld_name) . ' = :' . $fld_name;
582 }
583
584 if ($qry != '') {
585 $qry .= $op;
586 }
587 $qry .= $arg;
588 }
589 return $qry;
590 }
591
592 593 594 595 596 597 598 599 600
601 public function getValue($colName)
602 {
603 if (empty($colName)) {
604 throw new rex_sql_exception('parameter fieldname must not be empty!', null, $this);
605 }
606
607
608 if (isset($this->values[$colName])) {
609 return $this->values[$colName];
610 }
611
612
613
614 if (strpos($colName, '.') === false) {
615 $tables = $this->getTablenames();
616 foreach ($tables as $table) {
617 if (in_array($table . '.' . $colName, $this->rawFieldnames)) {
618 return $this->fetchValue($table . '.' . $colName);
619 }
620 }
621 }
622
623 return $this->fetchValue($colName);
624 }
625
626 627 628 629 630 631 632 633 634
635 public function getArrayValue($colName)
636 {
637 return json_decode($this->getValue($colName), true);
638 }
639
640 641 642 643 644 645 646 647 648
649 public function getDateTimeValue($colName)
650 {
651 $value = $this->getValue($colName);
652 return $value ? strtotime($value) : null;
653 }
654
655 656 657 658 659
660 protected function fetchValue($feldname)
661 {
662 if (isset($this->values[$feldname])) {
663 return $this->values[$feldname];
664 }
665
666 if (empty($this->lastRow)) {
667
668 if ($this->stmt == null) {
669 return null;
670 }
671 $this->lastRow = $this->stmt->fetch(PDO::FETCH_ASSOC);
672 }
673
674
675 if (is_array($this->lastRow) && (isset($this->lastRow[$feldname]) || array_key_exists($feldname, $this->lastRow))) {
676 return $this->lastRow[$feldname];
677 }
678 trigger_error('Field "' . $feldname . '" does not exist in result!', E_USER_WARNING);
679 return null;
680 }
681
682 683 684 685 686 687 688 689 690
691 public function getRow($fetch_type = PDO::FETCH_ASSOC)
692 {
693 if (!$this->lastRow) {
694 $this->lastRow = $this->stmt->fetch($fetch_type);
695 }
696 return $this->lastRow;
697 }
698
699 700 701 702 703 704 705
706 public function hasValue($feldname)
707 {
708
709 if (isset($this->values[$feldname])) {
710 return true;
711 }
712
713 if (strpos($feldname, '.') !== false) {
714 $parts = explode('.', $feldname);
715 return in_array($parts[0], $this->getTablenames()) && in_array($parts[1], $this->getFieldnames());
716 }
717 return in_array($feldname, $this->getFieldnames());
718 }
719
720 721 722 723 724 725 726 727 728 729 730 731
732 public function isNull($feldname)
733 {
734 if ($this->hasValue($feldname)) {
735 return $this->getValue($feldname) === null;
736 }
737
738 return null;
739 }
740
741 742 743 744 745
746 public function getRows()
747 {
748 return $this->rows;
749 }
750
751 752 753 754 755
756 public function getFields()
757 {
758 return $this->stmt ? $this->stmt->columnCount() : 0;
759 }
760
761 762 763 764 765 766 767 768
769 protected function buildPreparedValues()
770 {
771 $qry = '';
772 if (is_array($this->values)) {
773 foreach ($this->values as $fld_name => $value) {
774 if ($qry != '') {
775 $qry .= ', ';
776 }
777
778 $qry .= $this->escapeIdentifier($fld_name) .' = :' . $fld_name;
779 }
780 }
781 if (is_array($this->rawValues)) {
782 foreach ($this->rawValues as $fld_name => $value) {
783 if ($qry != '') {
784 $qry .= ', ';
785 }
786
787 $qry .= $this->escapeIdentifier($fld_name) . ' = ' . $value;
788 }
789 }
790
791 if (trim($qry) == '') {
792
793 trigger_error('no values given to buildPreparedValues for update(), insert() or replace()', E_USER_WARNING);
794 }
795
796 return $qry;
797 }
798
799 800 801
802 public function getWhere()
803 {
804
805 if ($this->wherevar != '') {
806 return $this->wherevar;
807 }
808
809 return '';
810 }
811
812 813 814 815 816 817 818 819 820 821
822 public function select($fields = '*')
823 {
824 $this->setQuery(
825 'SELECT ' . $fields . ' FROM ' . $this->escapeIdentifier($this->table) . ' ' . $this->getWhere(),
826 $this->whereParams
827 );
828 return $this;
829 }
830
831 832 833 834 835 836 837 838
839 public function update()
840 {
841 $this->setQuery(
842 'UPDATE ' . $this->escapeIdentifier($this->table) . ' SET ' . $this->buildPreparedValues() . ' ' . $this->getWhere(),
843 array_merge($this->values, $this->whereParams)
844 );
845 return $this;
846 }
847
848 849 850 851 852 853 854 855
856 public function insert()
857 {
858 if ($this->records) {
859 return $this->setMultiRecordQuery('INSERT');
860 }
861
862
863 $tableName = $this->table;
864 $values = $this->values;
865
866 if ($this->values || $this->rawValues) {
867 $setValues = 'SET '.$this->buildPreparedValues();
868 } else {
869 $setValues = 'VALUES ()';
870 }
871
872 $this->setQuery(
873 'INSERT INTO ' . $this->escapeIdentifier($this->table) . ' ' . $setValues,
874 $this->values
875 );
876
877
878
879 if ($this->getRows() == 0) {
880 throw new rex_sql_exception('Error while inserting into table "' . $tableName . '" with values ' . print_r($values, true) . '! Check your null/not-null constraints!', null, $this);
881 }
882 return $this;
883 }
884
885 886 887 888 889
890 public function insertOrUpdate()
891 {
892 if ($this->records) {
893 return $this->setMultiRecordQuery('INSERT', true);
894 }
895
896
897 $tableName = $this->table;
898 $values = $this->values;
899
900 $onDuplicateKeyUpdate = $this->buildOnDuplicateKeyUpdate(array_keys(array_merge($this->values, $this->rawValues)));
901 $this->setQuery(
902 'INSERT INTO ' . $this->escapeIdentifier($this->table) . ' SET ' . $this->buildPreparedValues() . ' ' . $onDuplicateKeyUpdate,
903 $this->values
904 );
905
906
907
908 if ($this->getRows() == 0) {
909 throw new rex_sql_exception('Error while inserting into table "' . $tableName . '" with values ' . print_r($values, true) . '! Check your null/not-null constraints!', null, $this);
910 }
911 return $this;
912 }
913
914 915 916 917 918 919 920 921
922 public function replace()
923 {
924 if ($this->records) {
925 return $this->setMultiRecordQuery('REPLACE');
926 }
927
928 $this->setQuery(
929 'REPLACE INTO ' . $this->escapeIdentifier($this->table) . ' SET ' . $this->buildPreparedValues() . ' ' . $this->getWhere(),
930 array_merge($this->values, $this->whereParams)
931 );
932 return $this;
933 }
934
935 936 937 938 939 940 941 942
943 public function delete()
944 {
945 $this->setQuery(
946 'DELETE FROM ' . $this->escapeIdentifier($this->table) . ' ' . $this->getWhere(),
947 $this->whereParams
948 );
949 return $this;
950 }
951
952 953 954 955 956
957 private function flush()
958 {
959 $this->values = [];
960 $this->rawValues = [];
961 $this->records = [];
962 $this->whereParams = [];
963 $this->lastRow = [];
964 $this->fieldnames = null;
965 $this->rawFieldnames = null;
966 $this->tablenames = null;
967
968 $this->table = '';
969 $this->wherevar = '';
970 $this->counter = 0;
971 $this->rows = 0;
972
973 return $this;
974 }
975
976 977 978 979 980 981 982
983 public function flushValues()
984 {
985 $this->values = [];
986 $this->rawValues = [];
987
988 return $this;
989 }
990
991 992 993
994 public function hasNext()
995 {
996 return $this->counter < $this->rows;
997 }
998
999 1000 1001 1002 1003 1004 1005
1006 public function reset()
1007 {
1008
1009 if ($this->stmt && $this->counter != 0) {
1010 $this->execute($this->params);
1011 $this->counter = 0;
1012 }
1013
1014 return $this;
1015 }
1016
1017 1018 1019
1020 public function getLastId()
1021 {
1022 return self::$pdo[$this->DBID]->lastInsertId();
1023 }
1024
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
1037 public function getDBArray($query = null, array $params = [], $fetchType = PDO::FETCH_ASSOC)
1038 {
1039 if (!$query) {
1040 $query = $this->query;
1041 $params = $this->params;
1042 }
1043
1044 $pdo = self::$pdo[$this->DBID];
1045
1046 $pdo->setAttribute(PDO::ATTR_FETCH_TABLE_NAMES, false);
1047 $this->setDBQuery($query, $params);
1048 $pdo->setAttribute(PDO::ATTR_FETCH_TABLE_NAMES, true);
1049
1050 return $this->stmt->fetchAll($fetchType);
1051 }
1052
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
1064 public function getArray($query = null, array $params = [], $fetchType = PDO::FETCH_ASSOC)
1065 {
1066 if (!$query) {
1067 $query = $this->query;
1068 $params = $this->params;
1069 }
1070
1071 $pdo = self::$pdo[$this->DBID];
1072
1073 $pdo->setAttribute(PDO::ATTR_FETCH_TABLE_NAMES, false);
1074 $this->setQuery($query, $params);
1075 $pdo->setAttribute(PDO::ATTR_FETCH_TABLE_NAMES, true);
1076
1077 return $this->stmt->fetchAll($fetchType);
1078 }
1079
1080 1081 1082 1083 1084
1085 public function getErrno()
1086 {
1087 return $this->stmt ? $this->stmt->errorCode() : self::$pdo[$this->DBID]->errorCode();
1088 }
1089
1090 1091 1092
1093 public function getMysqlErrno()
1094 {
1095 $errorInfos = $this->stmt ? $this->stmt->errorInfo() : self::$pdo[$this->DBID]->errorInfo();
1096
1097 return (int) $errorInfos[1];
1098 }
1099
1100 1101 1102
1103 public function getError()
1104 {
1105 $errorInfos = $this->stmt ? $this->stmt->errorInfo() : self::$pdo[$this->DBID]->errorInfo();
1106
1107
1108
1109 return $errorInfos[2];
1110 }
1111
1112 1113 1114 1115 1116
1117 public function hasError()
1118 {
1119 return $this->getErrno() != 0;
1120 }
1121
1122 1123 1124 1125 1126 1127
1128 protected function printError($qry, $params)
1129 {
1130 $errors['query'] = $qry;
1131 if (!empty($params)) {
1132 $errors['params'] = $params;
1133
1134
1135 $i = 0;
1136 $errors['fullquery'] = preg_replace_callback(
1137 '/\?|((?<!:):[a-z0-9_]+)/i',
1138 static function ($matches) use ($params, &$i) {
1139 $key = substr($matches[0], 1);
1140 if (!array_key_exists($i, $params) && ($key === false || !array_key_exists($key, $params))) {
1141 return $matches[0];
1142 }
1143 $value = array_key_exists($i, $params) ? $params[$i] : $params[$key];
1144 $result = self::factory()->escape($value);
1145 ++$i;
1146 return $result;
1147 },
1148 $qry
1149 );
1150 }
1151 if ($this->getRows()) {
1152 $errors['count'] = $this->getRows();
1153 }
1154 if ($this->getError()) {
1155 $errors['error'] = $this->getError();
1156 $errors['ecode'] = $this->getErrno();
1157 }
1158 dump($errors);
1159 }
1160
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
1171 public function setNewId($field, $start_id = 0)
1172 {
1173
1174 $sql = self::factory();
1175 $sql->setQuery('SELECT ' . $this->escapeIdentifier($field) . ' FROM ' . $this->escapeIdentifier($this->table) . ' ORDER BY ' . $this->escapeIdentifier($field) . ' DESC LIMIT 1');
1176 if ($sql->getRows() == 0) {
1177 $id = $start_id;
1178 } else {
1179 $id = $sql->getValue($field);
1180 }
1181 ++$id;
1182 $this->setValue($field, $id);
1183
1184 return $id;
1185 }
1186
1187 1188 1189 1190 1191
1192 public function getFieldnames()
1193 {
1194 $this->fetchMeta();
1195 return $this->fieldnames;
1196 }
1197
1198 1199 1200
1201 public function getTablenames()
1202 {
1203 $this->fetchMeta();
1204 return $this->tablenames;
1205 }
1206
1207 private function fetchMeta()
1208 {
1209 if ($this->fieldnames === null) {
1210 $this->rawFieldnames = [];
1211 $this->fieldnames = [];
1212 $this->tablenames = [];
1213
1214 for ($i = 0; $i < $this->getFields(); ++$i) {
1215 $metadata = $this->stmt->getColumnMeta($i);
1216
1217
1218 $this->fieldnames[] = substr($metadata['name'], strlen($metadata['table'] . '.'));
1219 $this->rawFieldnames[] = $metadata['name'];
1220
1221 if (!in_array($metadata['table'], $this->tablenames)) {
1222 $this->tablenames[] = $metadata['table'];
1223 }
1224 }
1225 }
1226 }
1227
1228 1229 1230 1231 1232 1233 1234
1235 public function escape($value)
1236 {
1237 return self::$pdo[$this->DBID]->quote($value);
1238 }
1239
1240 1241 1242 1243 1244 1245 1246
1247 public function escapeIdentifier($name)
1248 {
1249 return '`' . str_replace('`', '``', $name) . '`';
1250 }
1251
1252 1253 1254 1255 1256
1257 public function addGlobalUpdateFields($user = null)
1258 {
1259 if (!$user) {
1260 if (rex::getUser()) {
1261 $user = rex::getUser()->getValue('login');
1262 } else {
1263 $user = rex::getEnvironment();
1264 }
1265 }
1266
1267 $this->setDateTimeValue('updatedate', time());
1268 $this->setValue('updateuser', $user);
1269
1270 return $this;
1271 }
1272
1273 1274 1275 1276 1277
1278 public function addGlobalCreateFields($user = null)
1279 {
1280 if (!$user) {
1281 if (rex::getUser()) {
1282 $user = rex::getUser()->getValue('login');
1283 } else {
1284 $user = rex::getEnvironment();
1285 }
1286 }
1287
1288 $this->setDateTimeValue('createdate', time());
1289 $this->setValue('createuser', $user);
1290
1291 return $this;
1292 }
1293
1294 1295 1296 1297 1298 1299 1300
1301 public function beginTransaction()
1302 {
1303 if (self::$pdo[$this->DBID]->inTransaction()) {
1304 throw new rex_sql_exception('Transaction already started', null, $this);
1305 }
1306 return self::$pdo[$this->DBID]->beginTransaction();
1307 }
1308
1309 1310 1311 1312 1313 1314 1315
1316 public function rollBack()
1317 {
1318 if (!self::$pdo[$this->DBID]->inTransaction()) {
1319 throw new rex_sql_exception('Unable to rollback, no transaction started before', null, $this);
1320 }
1321 return self::$pdo[$this->DBID]->rollBack();
1322 }
1323
1324 1325 1326 1327 1328 1329 1330
1331 public function commit()
1332 {
1333 if (!self::$pdo[$this->DBID]->inTransaction()) {
1334 throw new rex_sql_exception('Unable to commit, no transaction started before', null, $this);
1335 }
1336 return self::$pdo[$this->DBID]->commit();
1337 }
1338
1339 1340 1341
1342 public function inTransaction()
1343 {
1344 return self::$pdo[$this->DBID]->inTransaction();
1345 }
1346
1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358
1359 public function transactional(callable $callable)
1360 {
1361 $inTransaction = self::$pdo[$this->DBID]->inTransaction();
1362 if (!$inTransaction) {
1363 self::$pdo[$this->DBID]->beginTransaction();
1364 }
1365 try {
1366 $result = $callable();
1367 if (!$inTransaction) {
1368 self::$pdo[$this->DBID]->commit();
1369 }
1370 return $result;
1371 } catch (\Exception $e) {
1372 if (!$inTransaction) {
1373 self::$pdo[$this->DBID]->rollBack();
1374 }
1375 throw $e;
1376 } catch (\Throwable $e) {
1377 if (!$inTransaction) {
1378 self::$pdo[$this->DBID]->rollBack();
1379 }
1380 throw $e;
1381 }
1382 }
1383
1384
1385
1386 1387 1388 1389 1390
1391 public function rewind()
1392 {
1393 $this->reset();
1394 }
1395
1396 1397 1398 1399 1400
1401 public function current()
1402 {
1403 return $this;
1404 }
1405
1406 1407 1408
1409 public function key()
1410 {
1411 return $this->counter;
1412 }
1413
1414 1415 1416
1417 public function next()
1418 {
1419 ++$this->counter;
1420 $this->lastRow = null;
1421 }
1422
1423 1424 1425
1426 public function valid()
1427 {
1428 return $this->hasNext();
1429 }
1430
1431
1432
1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443
1444 public static function showCreateTable($table, $DBID = 1)
1445 {
1446 $sql = self::factory($DBID);
1447 $sql->setQuery('SHOW CREATE TABLE ' . $sql->escapeIdentifier($table));
1448
1449 if (!$sql->getRows()) {
1450 throw new rex_sql_exception(sprintf('Table "%s" does not exist.', $table));
1451 }
1452 if (!$sql->hasValue('Create Table')) {
1453 throw new rex_sql_exception(sprintf('Table "%s" does not exist, it is a view instead.', $table));
1454 }
1455
1456 return $sql->getValue('Create Table');
1457 }
1458
1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471
1472 public static function showTables($DBID = 1, $tablePrefix = null)
1473 {
1474 return self::factory($DBID)->getTablesAndViews($tablePrefix);
1475 }
1476
1477 1478 1479 1480 1481 1482 1483 1484 1485 1486
1487 public function getTablesAndViews($tablePrefix = null)
1488 {
1489 return $this->fetchTablesAndViews($tablePrefix);
1490 }
1491
1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
1502 public function getTables($tablePrefix = null)
1503 {
1504 return $this->fetchTablesAndViews($tablePrefix, 'Table_type = "BASE TABLE"');
1505 }
1506
1507 1508 1509 1510 1511 1512 1513 1514 1515 1516
1517 public function getViews($tablePrefix = null)
1518 {
1519 return $this->fetchTablesAndViews($tablePrefix, 'Table_type = "VIEW"');
1520 }
1521
1522 1523 1524 1525 1526 1527 1528 1529
1530 private function fetchTablesAndViews($tablePrefix = null, $where = null)
1531 {
1532 $qry = 'SHOW FULL TABLES';
1533
1534 $where = $where ? [$where] : [];
1535
1536 if ($tablePrefix != null) {
1537
1538 $tablePrefix = str_replace(['_', '%'], ['\_', '\%'], $tablePrefix);
1539 $column = $this->escapeIdentifier('Tables_in_'.rex::getProperty('db')[$this->DBID]['name']);
1540 $where[] = $column.' LIKE "' . $tablePrefix . '%"';
1541 }
1542
1543 if ($where) {
1544 $qry .= ' WHERE '.implode(' AND ', $where);
1545 }
1546
1547 $tables = $this->getArray($qry);
1548 $tables = array_map('reset', $tables);
1549
1550 return $tables;
1551 }
1552
1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583
1584 public static function showColumns($table, $DBID = 1)
1585 {
1586 $sql = self::factory($DBID);
1587 $sql->setQuery('SHOW COLUMNS FROM ' . $sql->escapeIdentifier($table));
1588
1589 $columns = [];
1590 foreach ($sql as $col) {
1591 $columns[] = [
1592 'name' => $col->getValue('Field'),
1593 'type' => $col->getValue('Type'),
1594 'null' => $col->getValue('Null'),
1595 'key' => $col->getValue('Key'),
1596 'default' => $col->getValue('Default'),
1597 'extra' => $col->getValue('Extra'),
1598 ];
1599 }
1600
1601 return $columns;
1602 }
1603
1604 1605 1606 1607 1608 1609 1610 1611 1612 1613
1614 public static function getServerVersion($DBID = 1)
1615 {
1616 if (!isset(self::$pdo[$DBID])) {
1617
1618 self::factory($DBID);
1619 }
1620 return self::$pdo[$DBID]->getAttribute(PDO::ATTR_SERVER_VERSION);
1621 }
1622
1623 1624 1625 1626 1627 1628 1629
1630 public static function factory($DBID = 1)
1631 {
1632 $class = static::getFactoryClass();
1633 return new $class($DBID);
1634 }
1635
1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647
1648 public static function checkDbConnection($host, $login, $pw, $dbname, $createDb = false)
1649 {
1650 if (!$dbname) {
1651 return rex_i18n::msg('sql_database_name_missing');
1652 }
1653
1654 $err_msg = true;
1655
1656 try {
1657 self::createConnection(
1658 $host,
1659 $dbname,
1660 $login,
1661 $pw
1662 );
1663
1664
1665 if ($createDb) {
1666
1667 $err_msg = rex_i18n::msg('sql_database_already_exists');
1668 }
1669 } catch (PDOException $e) {
1670
1671
1672
1673 if (strpos($e->getMessage(), 'SQLSTATE[HY000] [2002]') !== false) {
1674
1675 $err_msg = rex_i18n::msg('sql_unable_to_connect_database');
1676 }
1677
1678 elseif (strpos($e->getMessage(), 'SQLSTATE[HY000] [1049]') !== false ||
1679 strpos($e->getMessage(), 'SQLSTATE[42000]') !== false
1680 ) {
1681 if ($createDb) {
1682 try {
1683
1684 $conn = self::createConnection(
1685 $host,
1686 'mysql',
1687 $login,
1688 $pw
1689 );
1690 if ($conn->exec('CREATE DATABASE ' . $dbname . ' CHARACTER SET utf8 COLLATE utf8_general_ci') !== 1) {
1691
1692 $err_msg = rex_i18n::msg('sql_unable_to_create_database');
1693 }
1694 } catch (PDOException $e) {
1695
1696 $err_msg = rex_i18n::msg('sql_unable_to_open_database');
1697 }
1698 } else {
1699
1700 $err_msg = rex_i18n::msg('sql_unable_to_find_database');
1701 }
1702 }
1703
1704
1705 elseif (
1706 strpos($e->getMessage(), 'SQLSTATE[HY000] [1045]') !== false ||
1707 strpos($e->getMessage(), 'SQLSTATE[28000]') !== false ||
1708 strpos($e->getMessage(), 'SQLSTATE[HY000] [1044]') !== false ||
1709 strpos($e->getMessage(), 'SQLSTATE[42000]') !== false
1710 ) {
1711
1712 $err_msg = rex_i18n::msg('sql_unable_to_connect_database');
1713 }
1714
1715 elseif (
1716 strpos($e->getMessage(), 'SQLSTATE[HY000] [2005]') !== false
1717 ) {
1718
1719 $err_msg = rex_i18n::msg('sql_unable_to_connect_server');
1720 } else {
1721
1722 throw $e;
1723 }
1724 }
1725
1726
1727 $conn = null;
1728
1729 return $err_msg;
1730 }
1731
1732 1733 1734 1735 1736 1737 1738 1739
1740 private function setMultiRecordQuery($verb, $onDuplicateKeyUpdate = false)
1741 {
1742 $fields = [];
1743
1744 foreach ($this->records as $record) {
1745 foreach ($record->values as $field => $value) {
1746 $fields[$field] = true;
1747 }
1748 foreach ($record->rawValues as $field => $value) {
1749 $fields[$field] = true;
1750 }
1751 }
1752
1753 $fields = array_keys($fields);
1754
1755 $rows = [];
1756 $params = [];
1757
1758 foreach ($this->records as $record) {
1759 $row = [];
1760
1761 foreach ($fields as $field) {
1762 if (isset($record->rawValues[$field])) {
1763 $row[] = $record->rawValues[$field];
1764
1765 continue;
1766 }
1767
1768 if (!isset($record->values[$field]) && !array_key_exists($field, $this->values)) {
1769 $row[] = 'DEFAULT';
1770
1771 continue;
1772 }
1773
1774 $row[] = '?';
1775 $params[] = $record->values[$field];
1776 }
1777
1778 $rows[] = '('.implode(', ', $row).')';
1779 }
1780
1781 $query = $verb.' INTO '.$this->escapeIdentifier($this->table)."\n";
1782 $query .= '('.implode(', ', array_map([$this, 'escapeIdentifier'], $fields)).")\n";
1783 $query .= "VALUES\n";
1784 $query .= implode(",\n", $rows);
1785
1786 if ($onDuplicateKeyUpdate) {
1787 $query .= "\n".$this->buildOnDuplicateKeyUpdate($fields);
1788 }
1789
1790 return $this->setQuery($query, $params);
1791 }
1792
1793 1794 1795 1796 1797
1798 private function buildOnDuplicateKeyUpdate($fields)
1799 {
1800 $updates = [];
1801
1802 foreach ($fields as $field) {
1803 $field = $this->escapeIdentifier($field);
1804 $updates[] = "$field = VALUES($field)";
1805 }
1806
1807 return 'ON DUPLICATE KEY UPDATE '.implode(', ', $updates);
1808 }
1809 }
1810