PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.4.6
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.4.6
3.4.6 3.4.5 3.4.4 3.4.3 3.4.2 3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 All 199 releases
convertkit / includes / class-convertkit-form-entries.php

class-convertkit-form-entries.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 3.4.6, at includes/class-convertkit-form-entries.php

502 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Kit Admin Form Builder Entries class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Stores entries submitted via Form Builder blocks that have
11 * the 'Store Entries' option enabled.
12 *
13 * @package ConvertKit
14 * @author ConvertKit
15 */
16 class ConvertKit_Form_Entries {
17
18 /**
19 * Holds the DB table name
20 *
21 * @since 3.0.0
22 *
23 * @var string
24 */
25 private $table = 'kit_form_entries';
26
27 /**
28 * Holds the columns that entries can be ordered by.
29 *
30 * @since 3.4.6
31 *
32 * @var array
33 */
34 private $order_by_columns = array(
35 'id',
36 'post_id',
37 'first_name',
38 'email',
39 'form_id',
40 'tag_id',
41 'sequence_id',
42 'created_at',
43 'updated_at',
44 'api_result',
45 );
46
47 /**
48 * Create database table.
49 *
50 * @since 3.0.0
51 *
52 * @global $wpdb WordPress DB Object
53 */
54 public function create_database_table() {
55
56 global $wpdb;
57
58 // Create database table.
59 // The table name isn't prepared, as the %i placeholder requires WordPress 6.2+.
60 $query = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}{$this->table}` (
61 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
62 `post_id` int(11) NOT NULL,
63 `first_name` varchar(191) NOT NULL DEFAULT '',
64 `email` varchar(191) NOT NULL DEFAULT '',
65 `custom_fields` text,
66 `form_id` int(11) NOT NULL,
67 `tag_id` int(11) NOT NULL,
68 `sequence_id` int(11) NOT NULL,
69 `created_at` datetime NOT NULL,
70 `updated_at` datetime NOT NULL,
71 `api_result` varchar(191) NOT NULL DEFAULT 'success',
72 `api_error` text,
73 PRIMARY KEY (`id`),
74 KEY `post_id` (`post_id`),
75 KEY `first_name` (`first_name`),
76 KEY `email` (`email`),
77 KEY `form_id` (`form_id`),
78 KEY `tag_id` (`tag_id`),
79 KEY `sequence_id` (`sequence_id`),
80 KEY `api_result` (`api_result`)
81 )";
82 $query .= ' ' . $wpdb->get_charset_collate() . ' AUTO_INCREMENT=1';
83 $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
84
85 }
86
87 /**
88 * Adds an entry
89 *
90 * @since 3.0.0
91 *
92 * @param array $entry Entry.
93 * int $post_id Post ID.
94 * string $first_name First Name.
95 * string $email Email.
96 * array $custom_fields Custom Fields.
97 * int $form_id Form ID.
98 * int $tag_id Tag ID.
99 * int $sequence_id Sequence ID.
100 * string $api_result Result (success,error).
101 * string $api_error API Response (when $api_result is 'error').
102 * @return int|bool|WP_Error
103 */
104 public function add( $entry ) {
105
106 global $wpdb;
107
108 // If no email is provided, return an error.
109 if ( ! array_key_exists( 'email', $entry ) ) {
110 return new \WP_Error( 'convertkit_form_entries_no_email', __( 'No email address provided', 'convertkit' ) );
111 }
112
113 // If no post ID is provided, return an error.
114 if ( ! array_key_exists( 'post_id', $entry ) ) {
115 return new \WP_Error( 'convertkit_form_entries_no_post_id', __( 'No post ID provided', 'convertkit' ) );
116 }
117
118 // JSON encode custom fields, if supplied as an array.
119 if ( array_key_exists( 'custom_fields', $entry ) && is_array( $entry['custom_fields'] ) ) {
120 $entry['custom_fields'] = wp_json_encode( $entry['custom_fields'] );
121 }
122
123 // Add created_at and updated_at timestamps.
124 $entry['created_at'] = gmdate( 'Y-m-d H:i:s' );
125 $entry['updated_at'] = gmdate( 'Y-m-d H:i:s' );
126
127 $wpdb->insert(
128 $wpdb->prefix . $this->table,
129 $entry
130 );
131
132 // Return the entry ID.
133 return $wpdb->insert_id;
134
135 }
136
137 /**
138 * Updates an entry
139 *
140 * @since 3.0.0
141 *
142 * @param int $id Entry ID.
143 * @param array $entry Entry.
144 * int $post_id Post ID.
145 * string $first_name First Name.
146 * string $email Email.
147 * array $custom_fields Custom Fields.
148 * int $form_id Form ID.
149 * int $tag_id Tag ID.
150 * int $sequence_id Sequence ID.
151 * string $api_result Result (success,error).
152 * string $api_error API Response (when $api_result is 'error').
153 * @return int|bool|WP_Error
154 */
155 public function update( $id, $entry ) {
156
157 global $wpdb;
158
159 // If no email is provided, return an error.
160 if ( ! array_key_exists( 'email', $entry ) ) {
161 return new \WP_Error( 'convertkit_form_entries_no_email', __( 'No email address provided', 'convertkit' ) );
162 }
163
164 // JSON encode custom fields, if supplied as an array.
165 if ( array_key_exists( 'custom_fields', $entry ) && is_array( $entry['custom_fields'] ) ) {
166 $entry['custom_fields'] = wp_json_encode( $entry['custom_fields'] );
167 }
168
169 // Add updated_at timestamp.
170 $entry['updated_at'] = gmdate( 'Y-m-d H:i:s' );
171
172 $wpdb->update(
173 $wpdb->prefix . $this->table,
174 $entry,
175 array( 'id' => $id )
176 );
177
178 // Return the entry ID.
179 return (int) $id;
180
181 }
182
183 /**
184 * Upserts an entry
185 *
186 * @since 3.0.0
187 *
188 * @param array $entry Entry.
189 * int $post_id Post ID.
190 * string $first_name First Name.
191 * string $email Email.
192 * array $custom_fields Custom Fields.
193 * int $form_id Form ID.
194 * int $tag_id Tag ID.
195 * int $sequence_id Sequence ID.
196 * datetime $created_at Created At.
197 * datetime $api_request_sent Request Sent to API.
198 * string $api_result Result (success,test_mode,pending,error).
199 * string $api_response API Response.
200 * @return int|bool|WP_Error
201 */
202 public function upsert( $entry ) {
203
204 global $wpdb;
205
206 // If no email is provided, return an error.
207 if ( ! array_key_exists( 'email', $entry ) ) {
208 return new \WP_Error( 'convertkit_form_entries_no_email', __( 'No email address provided', 'convertkit' ) );
209 }
210
211 // If no post ID is provided, return an error.
212 if ( ! array_key_exists( 'post_id', $entry ) ) {
213 return new \WP_Error( 'convertkit_form_entries_no_post_id', __( 'No post ID provided', 'convertkit' ) );
214 }
215
216 // Check if an entry already exists for the given Post ID and Email.
217 $id = $wpdb->get_var(
218 $wpdb->prepare(
219 "SELECT id FROM `{$wpdb->prefix}{$this->table}` WHERE post_id = %d AND email = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
220 $entry['post_id'],
221 $entry['email']
222 )
223 );
224
225 // If an entry already exists, update it.
226 if ( $id ) {
227 return $this->update( $id, $entry );
228 }
229
230 // Insert new entry.
231 return $this->add( $entry );
232
233 }
234
235 /**
236 * Gets entries by IDs
237 *
238 * @since 3.0.0
239 *
240 * @param array $ids Entry IDs.
241 * @return array
242 */
243 public function get_by_ids( $ids ) {
244
245 global $wpdb;
246
247 // Map IDs as integers.
248 $ids = array_map( 'absint', $ids );
249
250 // Return empty array if no IDs are provided.
251 if ( empty( $ids ) ) {
252 return array();
253 }
254
255 // Create IN clause.
256 $in_clause = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
257
258 // Create SQL query.
259 $sql = "SELECT * FROM {$wpdb->prefix}{$this->table} WHERE id IN ($in_clause)";
260
261 return $wpdb->get_results(
262 $wpdb->prepare( $sql, $ids ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
263 ARRAY_A
264 );
265
266 }
267
268 /**
269 * Returns a CSV string for the given entries
270 *
271 * @since 3.0.0
272 *
273 * @param array $entries Entries.
274 * @return string
275 */
276 public function get_csv_string( $entries ) {
277
278 // Bail if no entries are provided.
279 if ( empty( $entries ) ) {
280 return '';
281 }
282
283 $csv = array(
284 $this->get_csv_row( array_keys( $entries[0] ) ),
285 );
286 foreach ( $entries as $entry ) {
287 $csv[] = $this->get_csv_row( $entry );
288 }
289
290 return implode( "\n", $csv );
291
292 }
293
294 /**
295 * Returns a CSV row for the given values, escaping double quotes and
296 * values that spreadsheet applications would run as a formula.
297 *
298 * @since 3.4.6
299 *
300 * @param array $values Values.
301 * @return string
302 */
303 private function get_csv_row( $values ) {
304
305 $row = array();
306 foreach ( $values as $value ) {
307 $value = (string) $value;
308
309 // Prefix values starting with a formula character, so they're treated as text.
310 if ( $value !== '' && in_array( $value[0], array( '=', '+', '-', '@', "\t", "\r" ), true ) ) {
311 $value = "'" . $value;
312 }
313
314 $row[] = '"' . str_replace( '"', '""', $value ) . '"';
315 }
316
317 return implode( ',', $row );
318
319 }
320
321 /**
322 * Searches entries by the given key/value pairs
323 *
324 * @since 3.0.0
325 *
326 * @param bool|string $search Search Query.
327 * @param bool|string $api_result API Result.
328 * @param string $order_by Order Results By.
329 * @param string $order Order (asc|desc).
330 * @param int $page Pagination Offset (default: 1).
331 * @param int $per_page Number of Results to Return (default: 25).
332 * @return array
333 */
334 public function search( $search = false, $api_result = false, $order_by = 'created_at', $order = 'desc', $page = 1, $per_page = 25 ) {
335
336 global $wpdb;
337
338 // Prepare query.
339 $query = "SELECT * FROM `{$wpdb->prefix}{$this->table}`";
340
341 // Build where clauses.
342 $where_clauses = $this->build_where_clauses( $search, $api_result );
343
344 // If where clauses are provided, add them to the query.
345 if ( count( $where_clauses ) ) {
346 $query .= ' WHERE ' . implode( ' AND ', $where_clauses );
347 }
348
349 // Fallback to ordering by created_at if the order by column is invalid.
350 if ( ! in_array( $order_by, $this->order_by_columns, true ) ) {
351 $order_by = 'created_at';
352 }
353
354 // Order.
355 $query .= " ORDER BY `{$order_by}` " . ( strtolower( $order ) === 'asc' ? 'ASC' : 'DESC' );
356
357 // Limit.
358 if ( $page > 0 && $per_page > 0 ) {
359 $query .= $wpdb->prepare( ' LIMIT %d, %d', ( ( $page - 1 ) * $per_page ), $per_page );
360 }
361
362 // Run and return query results.
363 return $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
364
365 }
366
367 /**
368 * Gets the number of entry records found for the given query parameters
369 *
370 * @since 3.0.0
371 *
372 * @param bool|string $search Search Query.
373 * @param bool|string $api_result API Result.
374 * @return int
375 */
376 public function total( $search = false, $api_result = false ) {
377
378 global $wpdb;
379
380 // Prepare query.
381 $query = "SELECT COUNT(id) FROM `{$wpdb->prefix}{$this->table}`";
382
383 // Build where clauses.
384 $where_clauses = $this->build_where_clauses( $search, $api_result );
385
386 // If where clauses are provided, add them to the query.
387 if ( count( $where_clauses ) ) {
388 $query .= ' WHERE ' . implode( ' AND ', $where_clauses );
389 }
390
391 // Run and return total records found.
392 return (int) $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
393
394 }
395
396 /**
397 * Builds the where clauses for the given query parameters
398 *
399 * @since 3.0.1
400 *
401 * @param bool|string $search Search Query.
402 * @param bool|string $api_result API Result.
403 * @return array
404 */
405 private function build_where_clauses( $search = false, $api_result = false ) {
406
407 global $wpdb;
408
409 $where_clauses = array();
410
411 // Add search clause.
412 if ( $search ) {
413 $where_clauses[] = $wpdb->prepare(
414 '(first_name LIKE %s OR email LIKE %s)',
415 '%' . $wpdb->esc_like( $search ) . '%',
416 '%' . $wpdb->esc_like( $search ) . '%'
417 );
418 }
419
420 // Add API result clause.
421 if ( $api_result ) {
422 $where_clauses[] = $wpdb->prepare(
423 'api_result = %s',
424 $api_result
425 );
426 }
427
428 return $where_clauses;
429
430 }
431
432 /**
433 * Deletes a single entry for the given ID
434 *
435 * @since 3.0.0
436 *
437 * @param array $id Entry ID.
438 * @return bool
439 */
440 public function delete_by_id( $id ) {
441
442 global $wpdb;
443
444 return $wpdb->delete(
445 $wpdb->prefix . $this->table,
446 array(
447 'id' => absint( $id ),
448 )
449 );
450
451 }
452
453 /**
454 * Deletes multiple entries for the given Entry IDs
455 *
456 * @since 3.0.0
457 *
458 * @param array $ids Entry IDs.
459 * @return int|bool Number of entries deleted, or false on error
460 */
461 public function delete_by_ids( $ids ) {
462
463 global $wpdb;
464
465 // Map IDs as integers.
466 $ids = array_values( array_filter( array_map( 'absint', $ids ) ) );
467
468 // Bail if no IDs are provided.
469 if ( empty( $ids ) ) {
470 return 0;
471 }
472
473 return $wpdb->query(
474 $wpdb->prepare(
475 sprintf(
476 'DELETE FROM %s WHERE id IN (%s)',
477 $wpdb->prefix . $this->table, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
478 implode( ',', array_fill( 0, count( $ids ), '%d' ) )
479 ),
480 $ids
481 )
482 );
483
484 }
485
486 /**
487 * Deletes all entries
488 *
489 * @since 3.0.0
490 *
491 * @return bool
492 */
493 public function delete_all() {
494
495 global $wpdb;
496
497 return $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}{$this->table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
498
499 }
500
501 }
502