PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/classes/Dbal/DbiMysqli.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 513 lines | 259 code | 60 blank | 194 comment | 38 complexity | 6c92ffcdd2b9a025cd29e0013998fcee MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. /**
  3. * Interface to the MySQL Improved extension (MySQLi)
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Dbal;
  7. use mysqli;
  8. use mysqli_result;
  9. use mysqli_stmt;
  10. use PhpMyAdmin\DatabaseInterface;
  11. use PhpMyAdmin\FieldMetadata;
  12. use PhpMyAdmin\Query\Utilities;
  13. use stdClass;
  14. use function __;
  15. use function defined;
  16. use function is_array;
  17. use function is_bool;
  18. use function mysqli_connect_errno;
  19. use function mysqli_connect_error;
  20. use function mysqli_get_client_info;
  21. use function mysqli_init;
  22. use function mysqli_report;
  23. use function stripos;
  24. use function trigger_error;
  25. use const E_USER_WARNING;
  26. use const MYSQLI_ASSOC;
  27. use const MYSQLI_BOTH;
  28. use const MYSQLI_CLIENT_COMPRESS;
  29. use const MYSQLI_CLIENT_SSL;
  30. use const MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
  31. use const MYSQLI_NUM;
  32. use const MYSQLI_OPT_LOCAL_INFILE;
  33. use const MYSQLI_OPT_SSL_VERIFY_SERVER_CERT;
  34. use const MYSQLI_REPORT_OFF;
  35. use const MYSQLI_STORE_RESULT;
  36. use const MYSQLI_USE_RESULT;
  37. /**
  38. * Interface to the MySQL Improved extension (MySQLi)
  39. */
  40. class DbiMysqli implements DbiExtension
  41. {
  42. /**
  43. * connects to the database server
  44. *
  45. * @param string $user mysql user name
  46. * @param string $password mysql user password
  47. * @param array $server host/port/socket/persistent
  48. *
  49. * @return mysqli|bool false on error or a mysqli object on success
  50. */
  51. public function connect($user, $password, array $server)
  52. {
  53. if ($server) {
  54. $server['host'] = empty($server['host'])
  55. ? 'localhost'
  56. : $server['host'];
  57. }
  58. mysqli_report(MYSQLI_REPORT_OFF);
  59. $mysqli = mysqli_init();
  60. if ($mysqli === false) {
  61. return false;
  62. }
  63. $client_flags = 0;
  64. /* Optionally compress connection */
  65. if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) {
  66. $client_flags |= MYSQLI_CLIENT_COMPRESS;
  67. }
  68. /* Optionally enable SSL */
  69. if ($server['ssl']) {
  70. $client_flags |= MYSQLI_CLIENT_SSL;
  71. if (
  72. ! empty($server['ssl_key']) ||
  73. ! empty($server['ssl_cert']) ||
  74. ! empty($server['ssl_ca']) ||
  75. ! empty($server['ssl_ca_path']) ||
  76. ! empty($server['ssl_ciphers'])
  77. ) {
  78. $mysqli->ssl_set(
  79. $server['ssl_key'] ?? '',
  80. $server['ssl_cert'] ?? '',
  81. $server['ssl_ca'] ?? '',
  82. $server['ssl_ca_path'] ?? '',
  83. $server['ssl_ciphers'] ?? ''
  84. );
  85. }
  86. /*
  87. * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later
  88. * @link https://bugs.php.net/bug.php?id=68344
  89. * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838
  90. */
  91. if (! $server['ssl_verify']) {
  92. $mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, (int) $server['ssl_verify']);
  93. $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
  94. }
  95. }
  96. if ($GLOBALS['cfg']['PersistentConnections']) {
  97. $host = 'p:' . $server['host'];
  98. } else {
  99. $host = $server['host'];
  100. }
  101. $return_value = $mysqli->real_connect(
  102. $host,
  103. $user,
  104. $password,
  105. '',
  106. $server['port'],
  107. (string) $server['socket'],
  108. $client_flags
  109. );
  110. if ($return_value === false) {
  111. /*
  112. * Switch to SSL if server asked us to do so, unfortunately
  113. * there are more ways MySQL server can tell this:
  114. *
  115. * - MySQL 8.0 and newer should return error 3159
  116. * - #2001 - SSL Connection is required. Please specify SSL options and retry.
  117. * - #9002 - SSL connection is required. Please specify SSL options and retry.
  118. */
  119. // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  120. $error_number = $mysqli->connect_errno;
  121. $error_message = $mysqli->connect_error;
  122. // phpcs:enable
  123. if (
  124. ! $server['ssl']
  125. && ($error_number == 3159
  126. || (($error_number == 2001 || $error_number == 9002)
  127. && stripos($error_message, 'SSL Connection is required') !== false))
  128. ) {
  129. trigger_error(
  130. __('SSL connection enforced by server, automatically enabling it.'),
  131. E_USER_WARNING
  132. );
  133. $server['ssl'] = true;
  134. return self::connect($user, $password, $server);
  135. }
  136. return false;
  137. }
  138. $mysqli->options(MYSQLI_OPT_LOCAL_INFILE, (int) defined('PMA_ENABLE_LDI'));
  139. return $mysqli;
  140. }
  141. /**
  142. * selects given database
  143. *
  144. * @param string|DatabaseName $databaseName database name to select
  145. * @param mysqli $link the mysqli object
  146. */
  147. public function selectDb($databaseName, $link): bool
  148. {
  149. return $link->select_db((string) $databaseName);
  150. }
  151. /**
  152. * runs a query and returns the result
  153. *
  154. * @param string $query query to execute
  155. * @param mysqli $link mysqli object
  156. * @param int $options query options
  157. *
  158. * @return mysqli_result|bool
  159. */
  160. public function realQuery($query, $link, $options)
  161. {
  162. if ($options == ($options | DatabaseInterface::QUERY_STORE)) {
  163. $method = MYSQLI_STORE_RESULT;
  164. } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) {
  165. $method = MYSQLI_USE_RESULT;
  166. } else {
  167. $method = 0;
  168. }
  169. return $link->query($query, $method);
  170. }
  171. /**
  172. * Run the multi query and output the results
  173. *
  174. * @param mysqli $link mysqli object
  175. * @param string $query multi query statement to execute
  176. */
  177. public function realMultiQuery($link, $query): bool
  178. {
  179. return $link->multi_query($query);
  180. }
  181. /**
  182. * returns array of rows with associative and numeric keys from $result
  183. *
  184. * @param mysqli_result $result result set identifier
  185. */
  186. public function fetchArray($result): ?array
  187. {
  188. if (! $result instanceof mysqli_result) {
  189. return null;
  190. }
  191. return $result->fetch_array(MYSQLI_BOTH);
  192. }
  193. /**
  194. * returns array of rows with associative keys from $result
  195. *
  196. * @param mysqli_result $result result set identifier
  197. */
  198. public function fetchAssoc($result): ?array
  199. {
  200. if (! $result instanceof mysqli_result) {
  201. return null;
  202. }
  203. return $result->fetch_array(MYSQLI_ASSOC);
  204. }
  205. /**
  206. * returns array of rows with numeric keys from $result
  207. *
  208. * @param mysqli_result $result result set identifier
  209. */
  210. public function fetchRow($result): ?array
  211. {
  212. if (! $result instanceof mysqli_result) {
  213. return null;
  214. }
  215. return $result->fetch_array(MYSQLI_NUM);
  216. }
  217. /**
  218. * Adjusts the result pointer to an arbitrary row in the result
  219. *
  220. * @param mysqli_result $result database result
  221. * @param int $offset offset to seek
  222. */
  223. public function dataSeek($result, $offset): bool
  224. {
  225. return $result->data_seek($offset);
  226. }
  227. /**
  228. * Frees memory associated with the result
  229. *
  230. * @param mysqli_result $result database result
  231. */
  232. public function freeResult($result): void
  233. {
  234. if (! ($result instanceof mysqli_result)) {
  235. return;
  236. }
  237. $result->close();
  238. }
  239. /**
  240. * Check if there are any more query results from a multi query
  241. *
  242. * @param mysqli $link the mysqli object
  243. */
  244. public function moreResults($link): bool
  245. {
  246. return $link->more_results();
  247. }
  248. /**
  249. * Prepare next result from multi_query
  250. *
  251. * @param mysqli $link the mysqli object
  252. */
  253. public function nextResult($link): bool
  254. {
  255. return $link->next_result();
  256. }
  257. /**
  258. * Store the result returned from multi query
  259. *
  260. * @param mysqli $link the mysqli object
  261. *
  262. * @return mysqli_result|bool false when empty results / result set when not empty
  263. */
  264. public function storeResult($link)
  265. {
  266. return $link->store_result();
  267. }
  268. /**
  269. * Returns a string representing the type of connection used
  270. *
  271. * @param mysqli $link mysql link
  272. *
  273. * @return string type of connection used
  274. */
  275. public function getHostInfo($link)
  276. {
  277. // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  278. return $link->host_info;
  279. }
  280. /**
  281. * Returns the version of the MySQL protocol used
  282. *
  283. * @param mysqli $link mysql link
  284. *
  285. * @return string version of the MySQL protocol used
  286. */
  287. public function getProtoInfo($link)
  288. {
  289. // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  290. return $link->protocol_version;
  291. }
  292. /**
  293. * returns a string that represents the client library version
  294. *
  295. * @return string MySQL client library version
  296. */
  297. public function getClientInfo()
  298. {
  299. return mysqli_get_client_info();
  300. }
  301. /**
  302. * returns last error message or false if no errors occurred
  303. *
  304. * @param mysqli|false|null $link mysql link
  305. *
  306. * @return string|bool error or false
  307. */
  308. public function getError($link)
  309. {
  310. $GLOBALS['errno'] = 0;
  311. if ($link !== null && $link !== false) {
  312. $error_number = $link->errno;
  313. $error_message = $link->error;
  314. } else {
  315. $error_number = mysqli_connect_errno();
  316. $error_message = (string) mysqli_connect_error();
  317. }
  318. if ($error_number === 0 || $error_message === '') {
  319. return false;
  320. }
  321. // keep the error number for further check after
  322. // the call to getError()
  323. $GLOBALS['errno'] = $error_number;
  324. return Utilities::formatError($error_number, $error_message);
  325. }
  326. /**
  327. * returns the number of rows returned by last query
  328. *
  329. * @param mysqli_result|bool $result result set identifier
  330. *
  331. * @return string|int
  332. * @psalm-return int|numeric-string
  333. */
  334. public function numRows($result)
  335. {
  336. // see the note for tryQuery();
  337. if (is_bool($result)) {
  338. return 0;
  339. }
  340. // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  341. return $result->num_rows;
  342. }
  343. /**
  344. * returns the number of rows affected by last query
  345. *
  346. * @param mysqli $link the mysqli object
  347. *
  348. * @return int|string
  349. * @psalm-return int|numeric-string
  350. */
  351. public function affectedRows($link)
  352. {
  353. // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  354. return $link->affected_rows;
  355. }
  356. /**
  357. * returns meta info for fields in $result
  358. *
  359. * @param mysqli_result $result result set identifier
  360. *
  361. * @return FieldMetadata[]|null meta info for fields in $result
  362. */
  363. public function getFieldsMeta($result): ?array
  364. {
  365. if (! $result instanceof mysqli_result) {
  366. return null;
  367. }
  368. $fields = $result->fetch_fields();
  369. if (! is_array($fields)) {
  370. return null;
  371. }
  372. foreach ($fields as $k => $field) {
  373. $fields[$k] = new FieldMetadata($field->type, $field->flags, $field);
  374. }
  375. return $fields;
  376. }
  377. /**
  378. * return number of fields in given $result
  379. *
  380. * @param mysqli_result $result result set identifier
  381. *
  382. * @return int field count
  383. */
  384. public function numFields($result)
  385. {
  386. // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  387. return $result->field_count;
  388. }
  389. /**
  390. * returns the length of the given field $i in $result
  391. *
  392. * @param mysqli_result $result result set identifier
  393. * @param int $i field
  394. *
  395. * @return int|bool length of field
  396. */
  397. public function fieldLen($result, $i)
  398. {
  399. if ($i >= $this->numFields($result)) {
  400. return false;
  401. }
  402. /** @var stdClass|false $fieldDefinition */
  403. $fieldDefinition = $result->fetch_field_direct($i);
  404. if ($fieldDefinition !== false) {
  405. return $fieldDefinition->length;
  406. }
  407. return false;
  408. }
  409. /**
  410. * returns name of $i. field in $result
  411. *
  412. * @param mysqli_result $result result set identifier
  413. * @param int $i field
  414. *
  415. * @return string name of $i. field in $result
  416. */
  417. public function fieldName($result, $i)
  418. {
  419. if ($i >= $this->numFields($result)) {
  420. return '';
  421. }
  422. /** @var stdClass|false $fieldDefinition */
  423. $fieldDefinition = $result->fetch_field_direct($i);
  424. if ($fieldDefinition !== false) {
  425. return $fieldDefinition->name;
  426. }
  427. return '';
  428. }
  429. /**
  430. * returns properly escaped string for use in MySQL queries
  431. *
  432. * @param mysqli $link database link
  433. * @param string $string string to be escaped
  434. *
  435. * @return string a MySQL escaped string
  436. */
  437. public function escapeString($link, $string)
  438. {
  439. return $link->real_escape_string($string);
  440. }
  441. /**
  442. * Prepare an SQL statement for execution.
  443. *
  444. * @param mysqli $link database link
  445. * @param string $query The query, as a string.
  446. *
  447. * @return mysqli_stmt|false A statement object or false.
  448. */
  449. public function prepare($link, string $query)
  450. {
  451. return $link->prepare($query);
  452. }
  453. }