Нет описания

class-db.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <?php
  2. /**
  3. * DB class.
  4. *
  5. * This handy class originated from Pippin's Easy Digital Downloads.
  6. * https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/class-edd-db.php
  7. *
  8. * Sub-classes should define $table_name, $version, and $primary_key in __construct() method.
  9. *
  10. * @since 1.1.6
  11. */
  12. abstract class WPForms_DB {
  13. /**
  14. * Database table name.
  15. *
  16. * @since 1.1.6
  17. *
  18. * @var string
  19. */
  20. public $table_name;
  21. /**
  22. * Database version.
  23. *
  24. * @since 1.1.6
  25. *
  26. * @var string
  27. */
  28. public $version;
  29. /**
  30. * Primary key (unique field) for the database table.
  31. *
  32. * @since 1.1.6
  33. *
  34. * @var string
  35. */
  36. public $primary_key;
  37. /**
  38. * Database type identifier.
  39. *
  40. * @since 1.5.1
  41. *
  42. * @var string
  43. */
  44. public $type;
  45. /**
  46. * Retrieve the list of columns for the database table.
  47. * Sub-classes should define an array of columns here.
  48. *
  49. * @since 1.1.6
  50. *
  51. * @return array List of columns.
  52. */
  53. public function get_columns() {
  54. return [];
  55. }
  56. /**
  57. * Retrieve column defaults.
  58. * Sub-classes can define default for any/all of columns defined in the get_columns() method.
  59. *
  60. * @since 1.1.6
  61. *
  62. * @return array All defined column defaults.
  63. */
  64. public function get_column_defaults() {
  65. return [];
  66. }
  67. /**
  68. * Retrieve a row from the database based on a given row ID.
  69. *
  70. * @since 1.1.6
  71. *
  72. * @param int $row_id Row ID.
  73. *
  74. * @return null|object
  75. */
  76. public function get( $row_id ) {
  77. global $wpdb;
  78. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
  79. return $wpdb->get_row(
  80. $wpdb->prepare(
  81. "SELECT * FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  82. (int) $row_id
  83. )
  84. );
  85. }
  86. /**
  87. * Retrieve a row based on column and row ID.
  88. *
  89. * @since 1.1.6
  90. *
  91. * @param string $column Column name.
  92. * @param int|string $value Column value.
  93. *
  94. * @return object|null Database query result, object or null on failure.
  95. */
  96. public function get_by( $column, $value ) {
  97. global $wpdb;
  98. if (
  99. empty( $value ) ||
  100. ! array_key_exists( $column, $this->get_columns() )
  101. ) {
  102. return null;
  103. }
  104. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
  105. return $wpdb->get_row(
  106. $wpdb->prepare(
  107. "SELECT * FROM $this->table_name WHERE $column = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  108. $value
  109. )
  110. );
  111. }
  112. /**
  113. * Retrieve a value based on column name and row ID.
  114. *
  115. * @since 1.1.6
  116. *
  117. * @param string $column Column name.
  118. * @param int|string $row_id Row ID.
  119. *
  120. * @return string|null Database query result (as string), or null on failure.
  121. */
  122. public function get_column( $column, $row_id ) {
  123. global $wpdb;
  124. if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) {
  125. return null;
  126. }
  127. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
  128. return $wpdb->get_var(
  129. $wpdb->prepare(
  130. "SELECT $column FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  131. (int) $row_id
  132. )
  133. );
  134. }
  135. /**
  136. * Retrieve one column value based on another given column and matching value.
  137. *
  138. * @since 1.1.6
  139. *
  140. * @param string $column Column name.
  141. * @param string $column_where Column to match against in the WHERE clause.
  142. * @param string $column_value Value to match to the column in the WHERE clause.
  143. *
  144. * @return string|null Database query result (as string), or null on failure.
  145. */
  146. public function get_column_by( $column, $column_where, $column_value ) {
  147. global $wpdb;
  148. if (
  149. empty( $column ) ||
  150. empty( $column_where ) ||
  151. empty( $column_value ) ||
  152. ! array_key_exists( $column_where, $this->get_columns() ) ||
  153. ! array_key_exists( $column, $this->get_columns() )
  154. ) {
  155. return null;
  156. }
  157. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
  158. return $wpdb->get_var(
  159. $wpdb->prepare(
  160. "SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  161. $column_value
  162. )
  163. );
  164. }
  165. /**
  166. * Insert a new record into the database.
  167. *
  168. * @since 1.1.6
  169. *
  170. * @param array $data Column data.
  171. * @param string $type Optional. Data type context.
  172. *
  173. * @return int ID for the newly inserted record. 0 otherwise.
  174. */
  175. public function add( $data, $type = '' ) {
  176. global $wpdb;
  177. // Set default values.
  178. $data = (array) wp_parse_args( $data, $this->get_column_defaults() );
  179. do_action( 'wpforms_pre_insert_' . $type, $data );
  180. // Initialise column format array.
  181. $column_formats = $this->get_columns();
  182. // Force fields to lower case.
  183. $data = array_change_key_case( $data );
  184. // White list columns.
  185. $data = array_intersect_key( $data, $column_formats );
  186. // Reorder $column_formats to match the order of columns given in $data.
  187. $data_keys = array_keys( $data );
  188. $column_formats = array_merge( array_flip( $data_keys ), $column_formats );
  189. $wpdb->insert( $this->table_name, $data, $column_formats );
  190. do_action( 'wpforms_post_insert_' . $type, $wpdb->insert_id, $data );
  191. return $wpdb->insert_id;
  192. }
  193. /**
  194. * Insert a new record into the database. This runs the add() method.
  195. *
  196. * @see add()
  197. *
  198. * @since 1.1.6
  199. *
  200. * @param array $data Column data.
  201. *
  202. * @return int ID for the newly inserted record.
  203. */
  204. public function insert( $data ) {
  205. return $this->add( $data );
  206. }
  207. /**
  208. * Update an existing record in the database.
  209. *
  210. * @since 1.1.6
  211. *
  212. * @param int|string $row_id Row ID for the record being updated.
  213. * @param array $data Optional. Array of columns and associated data to update. Default empty array.
  214. * @param string $where Optional. Column to match against in the WHERE clause. If empty, $primary_key
  215. * will be used. Default empty.
  216. * @param string $type Optional. Data type context, e.g. 'affiliate', 'creative', etc. Default empty.
  217. *
  218. * @return bool False if the record could not be updated, true otherwise.
  219. */
  220. public function update( $row_id, $data = [], $where = '', $type = '' ) {
  221. global $wpdb;
  222. // Row ID must be a positive integer.
  223. $row_id = absint( $row_id );
  224. if ( empty( $row_id ) ) {
  225. return false;
  226. }
  227. if ( empty( $where ) ) {
  228. $where = $this->primary_key;
  229. }
  230. do_action( 'wpforms_pre_update_' . $type, $data );
  231. // Initialise column format array.
  232. $column_formats = $this->get_columns();
  233. // Force fields to lower case.
  234. $data = array_change_key_case( $data );
  235. // White list columns.
  236. $data = array_intersect_key( $data, $column_formats );
  237. // Reorder $column_formats to match the order of columns given in $data.
  238. $data_keys = array_keys( $data );
  239. $column_formats = array_merge( array_flip( $data_keys ), $column_formats );
  240. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
  241. if ( $wpdb->update( $this->table_name, $data, [ $where => $row_id ], $column_formats ) === false ) {
  242. return false;
  243. }
  244. do_action( 'wpforms_post_update_' . $type, $data );
  245. return true;
  246. }
  247. /**
  248. * Delete a record from the database.
  249. *
  250. * @since 1.1.6
  251. *
  252. * @param int|string $row_id Row ID.
  253. *
  254. * @return bool False if the record could not be deleted, true otherwise.
  255. */
  256. public function delete( $row_id = 0 ) {
  257. global $wpdb;
  258. // Row ID must be positive integer.
  259. $row_id = absint( $row_id );
  260. if ( empty( $row_id ) ) {
  261. return false;
  262. }
  263. do_action( 'wpforms_pre_delete', $row_id );
  264. do_action( 'wpforms_pre_delete_' . $this->type, $row_id );
  265. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  266. if ( $wpdb->query( $wpdb->prepare( "DELETE FROM $this->table_name WHERE $this->primary_key = %d", $row_id ) ) === false ) {
  267. return false;
  268. }
  269. do_action( 'wpforms_post_delete', $row_id );
  270. do_action( 'wpforms_post_delete_' . $this->type, $row_id );
  271. return true;
  272. }
  273. /**
  274. * Delete a record from the database by column.
  275. *
  276. * @since 1.1.6
  277. *
  278. * @param string $column Column name.
  279. * @param int|string $column_value Column value.
  280. *
  281. * @return bool False if the record could not be deleted, true otherwise.
  282. */
  283. public function delete_by( $column, $column_value ) {
  284. global $wpdb;
  285. if (
  286. empty( $column ) ||
  287. empty( $column_value ) ||
  288. ! array_key_exists( $column, $this->get_columns() )
  289. ) {
  290. return false;
  291. }
  292. do_action( 'wpforms_pre_delete', $column_value );
  293. do_action( 'wpforms_pre_delete_' . $this->type, $column_value );
  294. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  295. if ( $wpdb->query( $wpdb->prepare( "DELETE FROM $this->table_name WHERE $column = %s", $column_value ) ) === false ) {
  296. return false;
  297. }
  298. do_action( 'wpforms_post_delete', $column_value );
  299. do_action( 'wpforms_post_delete_' . $this->type, $column_value );
  300. return true;
  301. }
  302. /**
  303. * Delete record(s) from the database using WHERE IN syntax.
  304. *
  305. * @since 1.6.4
  306. *
  307. * @param string $column Column name.
  308. * @param mixed $column_values Column values.
  309. *
  310. * @return int|bool Number of deleted records, false otherwise.
  311. */
  312. public function delete_where_in( $column, $column_values ) {
  313. global $wpdb;
  314. if ( empty( $column ) || empty( $column_values ) ) {
  315. return false;
  316. }
  317. if ( ! array_key_exists( $column, $this->get_columns() ) ) {
  318. return false;
  319. }
  320. $values = is_array( $column_values ) ? $column_values : [ $column_values ];
  321. foreach ( $values as $key => $value ) {
  322. // Check if a string contains an integer and sanitize accordingly.
  323. if ( (string) (int) $value === $value ) {
  324. $values[ $key ] = (int) $value;
  325. $placeholders[ $key ] = '%d';
  326. } else {
  327. $values[ $key ] = sanitize_text_field( $value );
  328. $placeholders[ $key ] = '%s';
  329. }
  330. }
  331. $placeholders = isset( $placeholders ) ? implode( ',', $placeholders ) : '';
  332. $sql = "DELETE FROM $this->table_name WHERE $column IN ( $placeholders )";
  333. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
  334. return $wpdb->query( $wpdb->prepare( $sql, $values ) );
  335. }
  336. /**
  337. * Check if the given table exists.
  338. *
  339. * @since 1.1.6
  340. * @since 1.5.9 Default value is now the current child class table name.
  341. *
  342. * @param string $table The table name. Defaults to the child class table name.
  343. *
  344. * @return bool If the table name exists.
  345. */
  346. public function table_exists( $table = '' ) {
  347. global $wpdb;
  348. if ( ! empty( $table ) ) {
  349. $table = sanitize_text_field( $table );
  350. } else {
  351. $table = $this->table_name;
  352. }
  353. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
  354. return $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table;
  355. }
  356. }