PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.4.3
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.4.3
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 2.2.9 2.3.0 2.3.1 All 196 releases
convertkit / admin / importers / class-convertkit-admin-importer.php

class-convertkit-admin-importer.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 3.4.3, at admin/importers/class-convertkit-admin-importer.php

590 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Admin Importer class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Import and migrate data from third party Form plugins to Kit.
11 *
12 * @package ConvertKit
13 * @author ConvertKit
14 */
15 abstract class ConvertKit_Admin_Importer {
16
17 /**
18 * Holds the importer name.
19 *
20 * @since 3.1.7
21 *
22 * @var string
23 */
24 public $name = '';
25
26 /**
27 * Holds the importer title.
28 *
29 * @since 3.1.7
30 *
31 * @var string
32 */
33 public $title = '';
34
35 /**
36 * Holds the importer description.
37 *
38 * @since 3.3.5
39 *
40 * @var string
41 */
42 public $description = '';
43
44 /**
45 * Holds the shortcode name for the third party Form plugin.
46 *
47 * @since 3.1.0
48 *
49 * @var string
50 */
51 public $shortcode_name = '';
52
53 /**
54 * Holds the shortcode ID attribute name(s) for the third party Form plugin.
55 *
56 * Accepts a string for a single attribute (e.g. 'form'), or an array of
57 * strings for plugins where the form ID may appear under multiple attribute
58 * names (e.g. ['form', 'id']).
59 *
60 * @since 3.1.0
61 *
62 * @var bool|string|array
63 */
64 public $shortcode_id_attribute = false;
65
66 /**
67 * Holds the block name for the third party Form plugin.
68 *
69 * @since 3.1.6
70 *
71 * @var string
72 */
73 public $block_name = '';
74
75 /**
76 * Holds the block ID attribute name(s) for the third party Form plugin.
77 *
78 * Accepts a string for a single attribute (e.g. 'form'), or an array of
79 * strings for plugins where the form ID may appear under multiple attribute
80 * names (e.g. ['form', 'id']).
81 *
82 * @since 3.1.6
83 *
84 * @var bool|string|array
85 */
86 public $block_id_attribute = false;
87
88 /**
89 * Returns an array of third party form IDs and titles.
90 *
91 * @since 3.1.0
92 *
93 * @return array
94 */
95 abstract public function get_forms();
96
97 /**
98 * Registers the importer if third party forms exist.
99 *
100 * @since 3.1.7
101 *
102 * @param array $importers Importers.
103 * @return array
104 */
105 public function register( $importers ) {
106
107 // Bail if no third party forms exist in posts.
108 if ( ! $this->has_forms_in_posts() ) {
109 return $importers;
110 }
111
112 // Bail if no third party forms exist for this importer.
113 if ( ! $this->has_forms() ) {
114 return $importers;
115 }
116
117 // Add this importer to the list of importers.
118 $importers[ $this->name ] = array(
119 'name' => $this->name,
120 'title' => $this->title,
121 'description' => $this->description,
122 'forms' => $this->get_forms(),
123 );
124
125 return $importers;
126
127 }
128
129 /**
130 * Replaces third party form shortcodes and blocks with Kit form shortcodes and blocks.
131 *
132 * @since 3.1.7
133 *
134 * @param array $mappings Mappings.
135 */
136 public function import( $mappings ) {
137
138 // Iterate through the mappings, replacing the third party form shortcodes and blocks with the Kit form shortcodes and blocks.
139 foreach ( $mappings as $third_party_form_id => $kit_form_id ) {
140 // Skip empty Kit Form IDs i.e. no mapping was provided for this third party form.
141 if ( empty( $kit_form_id ) ) {
142 continue;
143 }
144
145 if ( $this->block_name ) {
146 $this->replace_blocks_in_posts( $third_party_form_id, (int) $kit_form_id );
147 }
148 if ( $this->shortcode_name ) {
149 $this->replace_shortcodes_in_posts( $third_party_form_id, (int) $kit_form_id );
150 }
151 }
152
153 }
154
155 /**
156 * Returns an array of post IDs that contain the third party form block or shortcode.
157 *
158 * @since 3.1.5
159 *
160 * @return array
161 */
162 public function get_forms_in_posts() {
163
164 global $wpdb;
165
166 // Build WHERE clauses and values.
167 $post_content_clauses = array();
168 $post_content_values = array();
169
170 if ( $this->shortcode_name ) {
171 $post_content_clauses[] = 'post_content LIKE %s';
172 $post_content_values[] = '%[' . $this->shortcode_name . '%';
173 }
174 if ( $this->block_name ) {
175 $post_content_clauses[] = 'post_content LIKE %s';
176 $post_content_values[] = '%<!-- wp:' . $this->block_name . '%';
177 }
178
179 // Bail early if nothing to search for.
180 if ( empty( $post_content_clauses ) ) {
181 return array();
182 }
183
184 // Prepare SQL using wpdb->prepare.
185 // call_user_func_array() is used so variable length arrays can be passed to prepare().
186 $query = call_user_func_array(
187 array( $wpdb, 'prepare' ),
188 array_merge(
189 array(
190 "
191 SELECT ID
192 FROM {$wpdb->posts}
193 WHERE post_status = %s
194 AND (
195 " . implode( ' OR ', $post_content_clauses ) . '
196 )
197 ',
198 ),
199 array_merge(
200 array( 'publish' ),
201 $post_content_values
202 )
203 )
204 );
205
206 // Run query.
207 $results = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
208
209 return $results ? $results : array();
210
211 }
212
213 /**
214 * Returns whether any third party forms exist.
215 *
216 * @since 3.1.0
217 *
218 * @return bool
219 */
220 public function has_forms() {
221
222 return count( $this->get_forms() ) > 0;
223
224 }
225
226 /**
227 * Returns whether any third party forms exist in posts.
228 *
229 * @since 3.1.0
230 *
231 * @return bool
232 */
233 public function has_forms_in_posts() {
234
235 return count( $this->get_forms_in_posts() ) > 0;
236
237 }
238
239 /**
240 * Replaces the third party form shortcode with the Kit form shortcode.
241 *
242 * @since 3.1.0
243 *
244 * @param string|int $third_party_form_id Third Party Form ID.
245 * @param int $form_id Kit Form ID.
246 */
247 public function replace_shortcodes_in_posts( $third_party_form_id, $form_id ) {
248
249 // Get Posts that contain the third party Form Shortcode.
250 $posts = $this->get_forms_in_posts();
251
252 // Bail if no Posts contain the third party Form Shortcode.
253 if ( empty( $posts ) ) {
254 return;
255 }
256
257 // Iterate through Posts and replace the third party Form Shortcode with the Kit Form Shortcode.
258 foreach ( $posts as $post_id ) {
259 // Get Post content.
260 $post_content = get_post_field( 'post_content', $post_id );
261
262 // Replace the third party Form Shortcode with the Kit Form Shortcode.
263 $post_content = $this->replace_shortcodes_in_content( $post_content, $third_party_form_id, $form_id );
264
265 // Update the Post content.
266 wp_update_post(
267 array(
268 'ID' => $post_id,
269 'post_content' => $post_content,
270 ),
271 false,
272 false // Don't fire after action hooks.
273 );
274
275 }
276
277 }
278
279 /**
280 * Replaces the third party form shortcode with the Kit form shortcode in the given string.
281 *
282 * @since 3.1.0
283 *
284 * @param string $content Content containing third party Form Shortcodes.
285 * @param string|int $third_party_form_id Third Party Form ID.
286 * @param int $form_id Kit Form ID.
287
288 * @return string
289 */
290 public function replace_shortcodes_in_content( $content, $third_party_form_id, $form_id ) {
291
292 // If there's no shortcode ID attribute, match shortcodes with or without any attribute.
293 if ( ! $this->shortcode_id_attribute ) {
294 $pattern = '/\[' // Start regex with an opening square bracket.
295 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
296 . '[^\]]*?\]/i'; // Match any other characters (non-greedy) up to the closing square bracket, case-insensitive.
297
298 return preg_replace(
299 $pattern,
300 '[convertkit_form form="' . $form_id . '"]',
301 $content
302 );
303 }
304
305 // Normalise the shortcode ID attribute to an array, so importers can
306 // declare a single attribute (string) or multiple attributes (array).
307 $id_attributes = (array) $this->shortcode_id_attribute;
308
309 // Run a replacement pass per attribute name.
310 foreach ( $id_attributes as $id_attribute ) {
311 $pattern = '/\[' // Start regex with an opening square bracket.
312 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
313 . '[^\]]*?' // Match any characters that are not a closing square bracket, non-greedy.
314 . '\b' . preg_quote( $id_attribute, '/' ) // Match the id attribute word boundary and escape as needed.
315 . '\s*=\s*' // Match optional whitespace around an equals sign.
316 . '(?:"' . preg_quote( (string) $third_party_form_id, '/' ) . '"|\'' . preg_quote( (string) $third_party_form_id, '/' ) . '\'|' . preg_quote( (string) $third_party_form_id, '/' ) . ')' // Match the form ID, double quotes, single quotes or unquoted.
317 . '[^\]]*?\]/i'; // Match any other characters (non-greedy) up to the closing square bracket, case-insensitive.
318
319 $content = preg_replace(
320 $pattern,
321 '[convertkit_form form="' . $form_id . '"]',
322 $content
323 );
324 }
325
326 return $content;
327
328 }
329
330 /**
331 * Returns an array of all unique form IDs from the posts that contain the third party form shortcode.
332 *
333 * @since 3.1.5
334 *
335 * @return array
336 */
337 public function get_form_ids_in_posts() {
338
339 // Get Post IDs that contain the third party form shortcode.
340 $post_ids = $this->get_forms_in_posts();
341
342 // If no post IDs are found, return an empty array.
343 if ( ! count( $post_ids ) ) {
344 return array();
345 }
346
347 // If the shortcode or block ID attribute is not set, the third party Plugin doesn't use IDs
348 // and only has one form.
349 if ( ! $this->shortcode_id_attribute && ! $this->block_id_attribute ) {
350 return array(
351 __( 'Default Form', 'convertkit' ),
352 );
353 }
354
355 // Iterate through Posts, extracting the Form IDs from the third party form shortcodes.
356 $form_ids = array();
357 foreach ( $post_ids as $post_id ) {
358 $content_form_ids = $this->get_form_ids_from_content( get_post_field( 'post_content', $post_id ) );
359 $form_ids = array_merge( $form_ids, $content_form_ids );
360 }
361
362 $form_ids = array_values( array_unique( $form_ids ) );
363
364 return $form_ids;
365
366 }
367
368 /**
369 * Returns an array of form IDs within the shortcode for the third party Form plugin.
370 *
371 * @since 3.1.5
372 *
373 * @param string $content Content containing third party Form Shortcodes.
374 * @return array
375 */
376 public function get_form_ids_from_content( $content ) {
377
378 // If there's no shortcode ID attribute, match shortcodes with or without any attribute and treat any match as a single "form".
379 if ( ! $this->shortcode_id_attribute ) {
380 $pattern = '/\[' // Start regex with an opening square bracket.
381 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
382 . '(?:\s+[^\]]*)?' // Optionally match any attributes (key/value pairs), non-greedy.
383 . '[^\]]*?\]/i'; // Match up to closing bracket, case-insensitive.
384
385 preg_match_all( $pattern, $content, $matches );
386
387 // If we matched at least one occurrence, just return an array with a single 0 (default/non-ID form).
388 if ( ! empty( $matches[0] ) ) {
389 return array( 0 );
390 }
391
392 return array();
393 }
394
395 // Normalise the shortcode ID attribute to an array, so importers can
396 // declare a single attribute (string) or multiple attributes (array).
397 $id_attributes = (array) $this->shortcode_id_attribute;
398
399 // Extract form IDs, running a match pass per attribute name.
400 $form_ids = array();
401 foreach ( $id_attributes as $id_attribute ) {
402 $pattern = '/\[' // Start regex with an opening square bracket.
403 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
404 . '(?:\s+[^\]]*)?' // Optionally match any attributes (key/value pairs), non-greedy.
405 . preg_quote( $id_attribute, '/' ) // Match the id attribute name.
406 . '\s*=\s*' // Optional whitespace, equals sign, optional whitespace.
407 . '(?:"([^"]+)"|\'([^\']+)\'|([^\s\]]+))' // Capture double quoted, single quoted or unquoted value.
408 . '[^\]]*?\]/i'; // Match up to closing bracket, case-insensitive.
409
410 preg_match_all( $pattern, $content, $matches );
411
412 // Extract form IDs: They could be in either $matches[1] (double quoted), $matches[2] (single quoted) or $matches[3] (unquoted).
413 $form_ids = array_merge(
414 $form_ids,
415 array_filter(
416 array_merge(
417 isset( $matches[1] ) ? $matches[1] : array(),
418 isset( $matches[2] ) ? $matches[2] : array(),
419 isset( $matches[3] ) ? $matches[3] : array()
420 )
421 )
422 );
423 }
424
425 return array_values( array_unique( $form_ids ) );
426
427 }
428
429 /**
430 * Replaces the third party form block with the Kit form block.
431 *
432 * @since 3.1.6
433 *
434 * @param string|int $third_party_form_id Third Party Form ID.
435 * @param int $form_id Kit Form ID.
436 */
437 public function replace_blocks_in_posts( $third_party_form_id, $form_id ) {
438
439 // Get Posts that contain the third party Form Block.
440 $posts = $this->get_forms_in_posts();
441
442 // Bail if no Posts contain the third party Form Block.
443 if ( empty( $posts ) ) {
444 return;
445 }
446
447 // Iterate through Posts and replace the third party Form Block with the Kit Form Block.
448 foreach ( $posts as $post_id ) {
449 $this->replace_blocks_in_post( $post_id, $third_party_form_id, $form_id );
450 }
451
452 }
453
454 /**
455 * Replaces the third party form block with the Kit form block in the given post.
456 *
457 * @since 3.1.6
458 *
459 * @param int $post_id Post ID.
460 * @param string|int $third_party_form_id Third Party Form ID.
461 * @param int $form_id Kit Form ID.
462 */
463 public function replace_blocks_in_post( $post_id, $third_party_form_id, $form_id ) {
464
465 // Get Post content.
466 $post_content = get_post_field( 'post_content', $post_id );
467
468 // Fetch Blocks from Content.
469 $blocks = parse_blocks( $post_content );
470
471 // If a single block was returned with blockName null, this content was not created using the block editor.
472 if ( count( $blocks ) === 1 && is_null( $blocks[0]['blockName'] ) ) {
473 return;
474 }
475
476 // Replace the third party Form Block with the Kit Form Block.
477 $post_content = $this->replace_blocks_in_content( $blocks, $third_party_form_id, $form_id );
478
479 // Double escape backslashes so that wp_update_post doesn't remove them.
480 // When content contains a single backslash (\), wp_update_post will strip it unless we double escape it (\\).
481 $post_content = str_replace( '\\', '\\\\', $post_content );
482
483 // Update the Post content.
484 wp_update_post(
485 array(
486 'ID' => $post_id,
487 'post_content' => $post_content,
488 ),
489 false,
490 false // Don't fire after action hooks.
491 );
492
493 }
494
495 /**
496 * Replaces the third party form block with the Kit form block in the given string.
497 *
498 * @since 3.1.6
499 *
500 * @param array $blocks Blocks.
501 * @param string|int $third_party_form_id Third Party Form ID.
502 * @param int $form_id Kit Form ID.
503 *
504 * @return string
505 */
506 public function replace_blocks_in_content( $blocks, $third_party_form_id, $form_id ) {
507
508 // Recursively convert blocks.
509 $blocks = $this->recursively_convert_blocks( $blocks, $third_party_form_id, $form_id );
510
511 // Serialize blocks.
512 return serialize_blocks( $blocks );
513
514 }
515
516 /**
517 * Recursively walks through an array of blocks and innerBlocks,
518 * converting third party form blocks to Kit form blocks.
519 *
520 * @since 3.1.6
521 *
522 * @param array $blocks Blocks.
523 * @param string|int $third_party_form_id Third Party Form ID.
524 * @param int $form_id Kit Form ID.
525 * @return array
526 */
527 private function recursively_convert_blocks( $blocks, $third_party_form_id, $form_id ) {
528
529 foreach ( $blocks as $index => $block ) {
530 // If this block has inner blocks, walk through the inner blocks.
531 if ( ! empty( $block['innerBlocks'] ) ) {
532 $blocks[ $index ]['innerBlocks'] = $this->recursively_convert_blocks( $block['innerBlocks'], $third_party_form_id, $form_id );
533 }
534
535 // Skip if a null block name.
536 if ( is_null( $block['blockName'] ) ) {
537 continue;
538 }
539
540 // Skip if not a third party form block.
541 if ( strpos( $block['blockName'], $this->block_name ) === false ) {
542 continue;
543 }
544
545 // If the block ID attribute is not set, the third party Plugin doesn't use IDs,
546 // so there's no need to check the $third_party_form_id matches the block attribute.
547 if ( $this->block_id_attribute ) {
548 // Normalise the block ID attribute to an array, so importers can
549 // declare a single attribute (string) or multiple attributes (array).
550 $id_attributes = (array) $this->block_id_attribute;
551
552 // Check if at least one attribute contains the third party form ID.
553 $matched = false;
554 foreach ( $id_attributes as $id_attribute ) {
555 if ( ! array_key_exists( $id_attribute, $block['attrs'] ) ) {
556 continue;
557 }
558
559 if ( stripos( $block['attrs'][ $id_attribute ], (string) $third_party_form_id ) === false ) {
560 continue;
561 }
562
563 $matched = true;
564 break;
565 }
566
567 // Skip if none of the configured ID attributes match.
568 if ( ! $matched ) {
569 continue;
570 }
571 }
572
573 // Replace third party form block with Kit form block.
574 $blocks[ $index ] = array(
575 'blockName' => 'convertkit/form',
576 'attrs' => array(
577 'form' => (string) $form_id,
578 ),
579 'innerBlocks' => array(),
580 'innerHTML' => '',
581 'innerContent' => array(),
582 );
583 }
584
585 return $blocks;
586
587 }
588
589 }
590