BxMDb.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup DolphinMigration Dolphin Migration
  7. * @ingroup UnaModules
  8. *
  9. * @{
  10. */
  11. /**
  12. * BxMDb Class allows to create connect with Dolphin's database
  13. */
  14. class BxMDb
  15. {
  16. static $_rLink;
  17. static $_aDbCacheData;
  18. static $_sErrorKey = 'bx_db_error';
  19. static $_aErrors = array(
  20. BX_DB_ERR_CONNECT_FAILD => 'Database connect failed',
  21. BX_DB_ERR_QUERY_ERROR => 'Database query error',
  22. BX_DB_ERR_ESCAPE => 'Escape string error'
  23. );
  24. var $_bPdoPersistent;
  25. var $_iPdoFetchType;
  26. var $_iPdoErrorMode;
  27. var $_bErrorChecking;
  28. var $_aError;
  29. var $_sHost, $_sPort, $_sSocket, $_sDbname, $_sUser, $_sPassword, $_sCharset, $_sStorageEngine;
  30. var $_oStatement = null;
  31. var $_oDbCacheObject = null;
  32. public function __construct($aDbConf = false)
  33. {
  34. $this->_iPdoFetchType = PDO::FETCH_ASSOC;
  35. $this->_iPdoErrorMode = BX_DB_MODE_EXCEPTION;
  36. $this->_bErrorChecking = true;
  37. $this->_aError = array();
  38. $this->_sStorageEngine = 'MYISAM';
  39. $this->_sHost = $aDbConf['host'];
  40. $this->_sPort = $aDbConf['port'];
  41. $this->_sSocket = $aDbConf['sock'];
  42. $this->_sDbname = $aDbConf['name'];
  43. $this->_sUser = $aDbConf['user'];
  44. $this->_sPassword = $aDbConf['pwd'];
  45. @set_exception_handler(array($this, 'pdoExceptionHandler'));
  46. }
  47. /**
  48. * database query exception handler for exceptions appeared out of the try/catch block
  49. */
  50. public function pdoExceptionHandler($oException)
  51. {
  52. if(!($oException instanceof PDOException)) {
  53. throw $oException;
  54. return;
  55. }
  56. if(!isset($oException->errorInfo[self::$_sErrorKey]))
  57. $oException->errorInfo[self::$_sErrorKey] = array(
  58. 'code' => BX_DB_ERR_QUERY_ERROR,
  59. 'message' => !empty($oException->errorInfo[2]) ? $oException->errorInfo[2] : $oException->getMessage(),
  60. 'trace' => $oException->getTrace()
  61. );
  62. $this->error($oException->errorInfo[self::$_sErrorKey]);
  63. }
  64. public function error($aError)
  65. {
  66. $sErrorType = self::$_aErrors[$aError['code']];
  67. $bVerbose = isset($aError['verbose']) ? (bool)$aError['verbose'] : $this->_bErrorChecking;
  68. if(!$bVerbose) {
  69. $this->log($sErrorType . ': ' . $aError['message']);
  70. if (!defined('BX_DOL_INSTALL')) // this is needed to display error during installation
  71. return;
  72. }
  73. if((defined('BX_DB_FULL_VISUAL_PROCESSING') && BX_DB_FULL_VISUAL_PROCESSING) || defined('BX_DOL_INSTALL')) {
  74. $sOutput = '<div style="border:2px solid red;padding:4px;width:600px;margin:0px auto;">';
  75. $sOutput .= '<div style="text-align:center;background-color:red;color:white;font-weight:bold;">Error</div>';
  76. $sOutput .= '<div style="text-align:center;">' . $sErrorType . '</div>';
  77. if((defined('BX_DB_FULL_DEBUG_MODE') && BX_DB_FULL_DEBUG_MODE) || defined('BX_DOL_INSTALL'))
  78. $sOutput .= $this->errorOutput($aError);
  79. $sOutput .= '</div>';
  80. }
  81. if(defined('BX_DB_DO_EMAIL_ERROR_REPORT') && BX_DB_DO_EMAIL_ERROR_REPORT) {
  82. $sSiteTitle = $this->getParam('site_title');
  83. $sMailBody = "Database error in " . $sSiteTitle . "<br /><br /> \n";
  84. $sMailBody .= $this->errorOutput($aError);
  85. $sMailBody .= "<hr />Auto-report system";
  86. sendMail($this->getParam('site_email'), "Database error in " . $sSiteTitle, $sMailBody, 0, array(), BX_EMAIL_SYSTEM, 'html', true);
  87. }
  88. bx_log('sys_db', "$sErrorType\n" .
  89. (empty($aError['message']) ? '' : " Error: {$aError['message']}\n") .
  90. (empty($aError['query']) ? '' : " Query: {$aError['query']}\n") .
  91. (!function_exists('getLoggedId') || !getLoggedId() ? '' : " Account ID: " . getLoggedId() . "\n"),
  92. BX_LOG_ERR
  93. );
  94. bx_show_service_unavailable_error_and_exit($sOutput);
  95. }
  96. protected function errorOutput($aError)
  97. {
  98. $aErrorLocation = array();
  99. if(!empty($aError['query']) && !empty($aError['trace']))
  100. foreach($aError['trace'] as $aCall )
  101. if(isset($aCall['args']) && is_array($aCall['args']))
  102. foreach($aCall['args'] as $argNum => $argVal)
  103. if((is_string($argVal) && strcmp($argVal, $aError['query']) == 0) || ($argVal instanceof PDOStatement && strcmp($argVal->queryString, $aError['query']) == 0)) {
  104. $aErrorLocation['file'] = isset($aCall['file']) ? $aCall['file'] : (isset($aCall['class']) ? 'class: ' . $aCall['class'] : 'undefined');
  105. $aErrorLocation['line'] = isset($aCall['line']) ? $aCall['line'] : 'undefined';
  106. $aErrorLocation['function'] = $aCall['function'];
  107. $aErrorLocation['arg'] = $argNum;
  108. }
  109. $sOutput = '';
  110. if(!empty($aError['query']))
  111. $sOutput .= '<p><b>Query:</b><br />' . bx_process_output($aError['query']) . '</p>';
  112. if(!empty($aError['message']))
  113. $sOutput .= '<p><b>Mysql error:</b><br />' . $aError['message'] . '</p>';
  114. if(!empty($aErrorLocation))
  115. $sOutput .= '<p><b>Location:</b><br />The error was found in <b>' . $aErrorLocation['function'] . '</b> function in the file <b>' . $aErrorLocation['file'] . '</b> at line <b>' . $aErrorLocation['line'] . '</b>.</p>';
  116. $sOutput .= '<p><b>collation_connection:</b><br />' . $this->getOne("SELECT @@collation_connection") . '</p>';
  117. if(!empty($aError['trace'])) {
  118. $sBackTrace = print_r($aError['trace'], true);
  119. if (defined ('BX_DATABASE_USER') && !is_array(BX_DATABASE_USER))
  120. $sBackTrace = str_replace('[_sUser:protected] => ' . BX_DATABASE_USER, '[_sUser:protected] => *****', $sBackTrace);
  121. if (defined ('BX_DATABASE_PASS') && !is_array(BX_DATABASE_PASS))
  122. $sBackTrace = str_replace('[_sPassword:protected] => ' . BX_DATABASE_PASS, '[_sPassword:protected] => *****', $sBackTrace);
  123. $sOutput .= '<div><b>Debug backtrace:</b></div><div style="overflow:scroll;height:300px;border:1px solid gray;"><pre>' . htmlspecialchars_adv($sBackTrace) . '</pre></div>';
  124. }
  125. if(!empty(self::$_aParams)) {
  126. $sSettings = var_export(self::$_aParams, true);
  127. $sOutput .= '<div><b>Settings:</b></div><div style="overflow:scroll;height:300px;border:1px solid gray;"><pre>' . htmlspecialchars_adv($sSettings) . '</pre></div>';
  128. }
  129. $sOutput .= '<p><b>Called script:</b><br />' . htmlspecialchars_adv($_SERVER['PHP_SELF']) . '</p>';
  130. if(!empty($_REQUEST)) {
  131. $sRequest = var_export($_REQUEST, true);
  132. $sOutput .= '<p><b>Request parameters:</b><br /><pre>' . htmlspecialchars_adv($sRequest) . '</pre></p>';
  133. }
  134. return $sOutput;
  135. }
  136. /**
  137. * get mysql version
  138. */
  139. public function getVersion()
  140. {
  141. $s = $this->getOne("SELECT VERSION()");
  142. return preg_match("/([0-9\.]+)/", $s, $m) ? $m[1] : false;
  143. }
  144. public function connect()
  145. {
  146. if(self::$_rLink)
  147. return true;
  148. try {
  149. $sDsn = "mysql:host=" . $this->_sHost . ";";
  150. $sDsn .= $this->_sPort ? "port=" . $this->_sPort . ";" : "";
  151. $sDsn .= $this->_sSocket ? "unix_socket=" . $this->_sSocket . ";" : "";
  152. $sDsn .= "dbname=" . $this->_sDbname . ";charset=UTF8";
  153. self::$_rLink = new PDO($sDsn, $this->_sUser, $this->_sPassword, array(
  154. PDO::ATTR_ERRMODE => $this->_iPdoErrorMode,
  155. PDO::ATTR_DEFAULT_FETCH_MODE => $this->_iPdoFetchType,
  156. PDO::ATTR_PERSISTENT => $this->_bPdoPersistent
  157. ));
  158. $this->pdoExec("SET NAMES 'utf8'");
  159. $this->pdoExec("SET sql_mode = ''");
  160. $sVer = $this->getVersion();
  161. $sStorageEngine = !$sVer || version_compare($sVer, '5.7.5', '>=') ? 'default_storage_engine' : 'storage_engine';
  162. $this->pdoExec("SET $sStorageEngine=" . $this->_sStorageEngine);
  163. self::$_aDbCacheData = array();
  164. }
  165. catch (PDOException $oException) {
  166. $oException->errorInfo[self::$_sErrorKey] = array(
  167. 'code' => BX_DB_ERR_CONNECT_FAILD,
  168. 'message' => $oException->getMessage(),
  169. 'trace' => $oException->getTrace()
  170. );
  171. throw $oException;
  172. }
  173. return true;
  174. }
  175. public function res($oStatement, $aBindings = array(), $bVerbose = null)
  176. {
  177. if(!$oStatement || !($oStatement instanceof PDOStatement))
  178. return false;
  179. if($oStatement->errorCode() == BX_PDO_STATE_SUCCESS)
  180. return true;
  181. if(isset($GLOBALS['bx_profiler']))
  182. $GLOBALS['bx_profiler']->beginQuery($oStatement->queryString);
  183. $bResult = $this->executeStatement($oStatement, $aBindings, $bVerbose);
  184. //if mysql connection is lost - reconnect and try again
  185. if(!$bResult && !$this->ping()) {
  186. $this->disconnect();
  187. $this->connect();
  188. $bResult = $this->executeStatement($oStatement, $aBindings, $bVerbose);
  189. }
  190. if(isset($GLOBALS['bx_profiler']))
  191. $GLOBALS['bx_profiler']->endQuery($bResult);
  192. //is needed for SILENT mode
  193. if(!$bResult && !empty($this->_aError))
  194. $this->error($this->_aError);
  195. return $bResult;
  196. }
  197. public function ping()
  198. {
  199. try {
  200. $this->pdoQuery("SELECT 1");
  201. }
  202. catch (PDOException $e) {
  203. return false;
  204. }
  205. return true;
  206. }
  207. public function pdoQuery($sQuery)
  208. {
  209. return self::$_rLink->query($sQuery);
  210. }
  211. public function pdoExec($sQuery)
  212. {
  213. return self::$_rLink->exec($sQuery);
  214. }
  215. public function getOne($oStatement, $aBindings = array(), $iIndex = 0)
  216. {
  217. if(!$oStatement)
  218. return false;
  219. else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
  220. $oStatement = $this->prepare($oStatement);
  221. $aResult = array();
  222. if($this->res($oStatement, $aBindings))
  223. $aResult = $oStatement->fetch(PDO::FETCH_NUM);
  224. return is_array($aResult) && count($aResult) ? $aResult[$iIndex] : false;
  225. }
  226. public function prepare($sQuery)
  227. {
  228. if(!self::$_rLink)
  229. return false;
  230. $aArgs = func_get_args();
  231. $sQuery = array_shift($aArgs);
  232. $oStatement = self::$_rLink->prepare($sQuery);
  233. $iIndex = 1;
  234. foreach($aArgs as $mixedArg) {
  235. if(is_null($mixedArg))
  236. $iValueType = PDO::PARAM_NULL;
  237. else if(is_numeric($mixedArg))
  238. $iValueType = PDO::PARAM_INT;
  239. else
  240. $iValueType = PDO::PARAM_STR;
  241. $oStatement->bindValue($iIndex++, $mixedArg, $iValueType);
  242. }
  243. return $oStatement;
  244. }
  245. protected function executeStatement($oStatement, $aBindings = array(), $bVerbose = null)
  246. {
  247. $bResult = false;
  248. switch (self::$_rLink->getAttribute(PDO::ATTR_ERRMODE)) {
  249. case PDO::ERRMODE_SILENT:
  250. $bResult = $this->executeStatementSilent($oStatement, $aBindings, $bVerbose);
  251. break;
  252. case PDO::ERRMODE_EXCEPTION:
  253. $bResult = $this->executeStatementException($oStatement, $aBindings, $bVerbose);
  254. break;
  255. }
  256. return $bResult;
  257. }
  258. protected function executeStatementException($oStatement, $aBindings = array(), $bVerbose = null)
  259. {
  260. $bResult = false;
  261. try {
  262. $bResult = $oStatement->execute(!empty($aBindings) && is_array($aBindings) ? $aBindings : null);
  263. }
  264. catch (PDOException $oException) {
  265. $aError = $oStatement->errorInfo();
  266. $oException->errorInfo[self::$_sErrorKey] = array(
  267. 'code' => BX_DB_ERR_QUERY_ERROR,
  268. 'message' => !empty($aError[2]) ? $aError[2] : $oException->getMessage(),
  269. 'query' => $oStatement->queryString,
  270. 'trace' => $oException->getTrace(),
  271. 'verbose' => $bVerbose
  272. );
  273. throw $oException;
  274. }
  275. return $bResult;
  276. }
  277. protected function executeStatementSilent($oStatement, $aBindings = array(), $bVerbose = null)
  278. {
  279. $bResult = $oStatement->execute(!empty($aBindings) && is_array($aBindings) ? $aBindings : null);
  280. if($bResult)
  281. return true;
  282. $aError = $oStatement->errorInfo();
  283. $aTrace = debug_backtrace();
  284. unset($aTrace[0]);
  285. $this->_aError = array(
  286. 'code' => BX_DB_ERR_QUERY_ERROR,
  287. 'message' => !empty($aError[2]) ? $aError[2] : '',
  288. 'query' => $oStatement->queryString,
  289. 'trace' => $aTrace,
  290. 'verbose' => $bVerbose
  291. );
  292. return false;
  293. }
  294. function getRow($oStatement, $aBindings = array(), $iFetchType = PDO::FETCH_ASSOC)
  295. {
  296. $aResult = array();
  297. if(!$oStatement)
  298. return $aResult;
  299. else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
  300. $oStatement = $this->prepare($oStatement);
  301. if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
  302. $iFetchType = $this->_iPdoFetchType;
  303. if(!$this->res($oStatement, $aBindings))
  304. return array();
  305. $aResult = $oStatement->fetch($iFetchType);
  306. if($aResult === false)
  307. return array();
  308. return $aResult;
  309. }
  310. public function query($oStatement, $aBindings = array(), $bVerbose = null)
  311. {
  312. if(!$oStatement)
  313. return false;
  314. else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
  315. $oStatement = $this->prepare($oStatement);
  316. if($this->res($oStatement, $aBindings, $bVerbose))
  317. return $oStatement->rowCount();
  318. return false;
  319. }
  320. public function getAll($oStatement, $aBindings = array(), $iFetchType = PDO::FETCH_ASSOC)
  321. {
  322. $aResult = array();
  323. if(!$oStatement)
  324. return $aResult;
  325. else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
  326. $oStatement = $this->prepare($oStatement);
  327. if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
  328. $iFetchType = $this->_iPdoFetchType;
  329. if(!$this->res($oStatement, $aBindings))
  330. return array();
  331. $aResult = $oStatement->fetchAll($iFetchType);
  332. if($aResult === false)
  333. return array();
  334. return $aResult;
  335. }
  336. public function getPairs($oStatement, $sFieldKey, $sFieldValue, $aBindings = array())
  337. {
  338. $aResult = array();
  339. if(!$oStatement)
  340. return $aResult;
  341. else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
  342. $oStatement = $this->prepare($oStatement);
  343. $aRow = $this->getFirstRow($oStatement, $aBindings, PDO::FETCH_ASSOC);
  344. while(!empty($aRow)) {
  345. $aResult[$aRow[$sFieldKey]] = $aRow[$sFieldValue];
  346. $aRow = $this->getNextRow(PDO::FETCH_ASSOC);
  347. }
  348. return $aResult;
  349. }
  350. public function getFirstRow($oStatement, $aBindings = array(), $iFetchType = PDO::FETCH_ASSOC)
  351. {
  352. if(!$oStatement)
  353. return array();
  354. else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
  355. $oStatement = $this->prepare($oStatement);
  356. if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
  357. $iFetchType = $this->_iPdoFetchType;
  358. if(!$this->res($oStatement, $aBindings))
  359. return array();
  360. $aResult = $oStatement->fetch($iFetchType);
  361. if($aResult === false)
  362. return array();
  363. $this->_oStatement = $oStatement;
  364. return $aResult;
  365. }
  366. public function getNextRow($iFetchType = PDO::FETCH_ASSOC)
  367. {
  368. if(!$this->_oStatement)
  369. return array();
  370. if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
  371. $iFetchType = $this->_iPdoFetchType;
  372. $aResult = $this->_oStatement->fetch($iFetchType);
  373. if($aResult !== false)
  374. return $aResult;
  375. $this->_oStatement = null;
  376. return array();
  377. }
  378. public function isTableExists($sTable){
  379. $sQuery = $this -> prepare("SHOW TABLES LIKE ?", $sTable);
  380. $aResult = $this -> getRow($sQuery);
  381. return !empty($aResult);
  382. }
  383. public function getParam($sName){
  384. if (!$this -> isTableExists('sys_options')) return '';
  385. $sQuery = $this -> prepare("SELECT `value` FROM `sys_options` WHERE `name` = ?", $sName);
  386. return $this -> getOne($sQuery);
  387. }
  388. }
  389. /** @} */