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 / 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.6, at admin/importers/class-convertkit-admin-importer.php

612 lines 18.1 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 // Double escape backslashes so that wp_update_post doesn't remove them.
266 $post_content = str_replace( '\\', '\\\\', $post_content );
267
268 // Update the Post content.
269 wp_update_post(
270 array(
271 'ID' => $post_id,
272 'post_content' => $post_content,
273 ),
274 false,
275 false // Don't fire after action hooks.
276 );
277
278 }
279
280 }
281
282 /**
283 * Replaces the third party form shortcode with the Kit form shortcode in the given string.
284 *
285 * @since 3.1.0
286 *
287 * @param string $content Content containing third party Form Shortcodes.
288 * @param string|int $third_party_form_id Third Party Form ID.
289 * @param int $form_id Kit Form ID.
290
291 * @return string
292 */
293 public function replace_shortcodes_in_content( $content, $third_party_form_id, $form_id ) {
294
295 // If there's no shortcode ID attribute, match shortcodes with or without any attribute.
296 if ( ! $this->shortcode_id_attribute ) {
297 $pattern = '/\[' // Start regex with an opening square bracket.
298 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
299 . '(?=[\s\]\/])' // Ensure the shortcode name is complete, so e.g. [name_other] isn't matched.
300 . '[^\]]*?\]/i'; // Match any other characters (non-greedy) up to the closing square bracket, case-insensitive.
301
302 return preg_replace(
303 $pattern,
304 '[convertkit_form form="' . $form_id . '"]',
305 $content
306 );
307 }
308
309 // Normalise the shortcode ID attribute to an array, so importers can
310 // declare a single attribute (string) or multiple attributes (array).
311 $id_attributes = (array) $this->shortcode_id_attribute;
312
313 // Run a replacement pass per attribute name.
314 foreach ( $id_attributes as $id_attribute ) {
315 $pattern = '/\[' // Start regex with an opening square bracket.
316 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
317 . '(?=[\s\]\/])' // Ensure the shortcode name is complete, so e.g. [name_other] isn't matched.
318 . '[^\]]*?' // Match any characters that are not a closing square bracket, non-greedy.
319 . '\s' . preg_quote( $id_attribute, '/' ) // Match the id attribute, preceded by whitespace so e.g. data-id isn't matched.
320 . '\s*=\s*' // Match optional whitespace around an equals sign.
321 . '(?:"' . preg_quote( (string) $third_party_form_id, '/' ) . '"|\'' . preg_quote( (string) $third_party_form_id, '/' ) . '\'|' . preg_quote( (string) $third_party_form_id, '/' ) . '(?=[\s\]\/]))' // Match the exact form ID, double quotes, single quotes or unquoted.
322 . '[^\]]*?\]/i'; // Match any other characters (non-greedy) up to the closing square bracket, case-insensitive.
323
324 $content = preg_replace(
325 $pattern,
326 '[convertkit_form form="' . $form_id . '"]',
327 $content
328 );
329 }
330
331 return $content;
332
333 }
334
335 /**
336 * Returns an array of all unique form IDs from the posts that contain the third party form shortcode.
337 *
338 * @since 3.1.5
339 *
340 * @return array
341 */
342 public function get_form_ids_in_posts() {
343
344 // Get Post IDs that contain the third party form shortcode.
345 $post_ids = $this->get_forms_in_posts();
346
347 // If no post IDs are found, return an empty array.
348 if ( ! count( $post_ids ) ) {
349 return array();
350 }
351
352 // If the shortcode or block ID attribute is not set, the third party Plugin doesn't use IDs
353 // and only has one form.
354 if ( ! $this->shortcode_id_attribute && ! $this->block_id_attribute ) {
355 return array(
356 __( 'Default Form', 'convertkit' ),
357 );
358 }
359
360 // Iterate through Posts, extracting the Form IDs from the third party form shortcodes.
361 $form_ids = array();
362 foreach ( $post_ids as $post_id ) {
363 $content_form_ids = $this->get_form_ids_from_content( get_post_field( 'post_content', $post_id ) );
364 $form_ids = array_merge( $form_ids, $content_form_ids );
365 }
366
367 $form_ids = array_values( array_unique( $form_ids ) );
368
369 return $form_ids;
370
371 }
372
373 /**
374 * Returns an array of form IDs within the shortcode for the third party Form plugin.
375 *
376 * @since 3.1.5
377 *
378 * @param string $content Content containing third party Form Shortcodes.
379 * @return array
380 */
381 public function get_form_ids_from_content( $content ) {
382
383 // If there's no shortcode ID attribute, match shortcodes with or without any attribute and treat any match as a single "form".
384 if ( ! $this->shortcode_id_attribute ) {
385 $pattern = '/\[' // Start regex with an opening square bracket.
386 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
387 . '(?=[\s\]\/])' // Ensure the shortcode name is complete, so e.g. [name_other] isn't matched.
388 . '(?:\s+[^\]]*)?' // Optionally match any attributes (key/value pairs), non-greedy.
389 . '[^\]]*?\]/i'; // Match up to closing bracket, case-insensitive.
390
391 preg_match_all( $pattern, $content, $matches );
392
393 // If we matched at least one occurrence, just return an array with a single 0 (default/non-ID form).
394 if ( ! empty( $matches[0] ) ) {
395 return array( 0 );
396 }
397
398 return array();
399 }
400
401 // Normalise the shortcode ID attribute to an array, so importers can
402 // declare a single attribute (string) or multiple attributes (array).
403 $id_attributes = (array) $this->shortcode_id_attribute;
404
405 // Extract form IDs, running a match pass per attribute name.
406 $form_ids = array();
407 foreach ( $id_attributes as $id_attribute ) {
408 $pattern = '/\[' // Start regex with an opening square bracket.
409 . preg_quote( $this->shortcode_name, '/' ) // Match the shortcode name, escaping any regex special chars.
410 . '(?=[\s\]\/])' // Ensure the shortcode name is complete, so e.g. [name_other] isn't matched.
411 . '(?:\s+[^\]]*)?' // Optionally match any attributes (key/value pairs), non-greedy.
412 . preg_quote( $id_attribute, '/' ) // Match the id attribute name.
413 . '\s*=\s*' // Optional whitespace, equals sign, optional whitespace.
414 . '(?:"([^"]+)"|\'([^\']+)\'|([^\s\]]+))' // Capture double quoted, single quoted or unquoted value.
415 . '[^\]]*?\]/i'; // Match up to closing bracket, case-insensitive.
416
417 preg_match_all( $pattern, $content, $matches );
418
419 // Extract form IDs: They could be in either $matches[1] (double quoted), $matches[2] (single quoted) or $matches[3] (unquoted).
420 $form_ids = array_merge(
421 $form_ids,
422 array_filter(
423 array_merge(
424 isset( $matches[1] ) ? $matches[1] : array(),
425 isset( $matches[2] ) ? $matches[2] : array(),
426 isset( $matches[3] ) ? $matches[3] : array()
427 )
428 )
429 );
430 }
431
432 return array_values( array_unique( $form_ids ) );
433
434 }
435
436 /**
437 * Replaces the third party form block with the Kit form block.
438 *
439 * @since 3.1.6
440 *
441 * @param string|int $third_party_form_id Third Party Form ID.
442 * @param int $form_id Kit Form ID.
443 */
444 public function replace_blocks_in_posts( $third_party_form_id, $form_id ) {
445
446 // Get Posts that contain the third party Form Block.
447 $posts = $this->get_forms_in_posts();
448
449 // Bail if no Posts contain the third party Form Block.
450 if ( empty( $posts ) ) {
451 return;
452 }
453
454 // Iterate through Posts and replace the third party Form Block with the Kit Form Block.
455 foreach ( $posts as $post_id ) {
456 $this->replace_blocks_in_post( $post_id, $third_party_form_id, $form_id );
457 }
458
459 }
460
461 /**
462 * Replaces the third party form block with the Kit form block in the given post.
463 *
464 * @since 3.1.6
465 *
466 * @param int $post_id Post ID.
467 * @param string|int $third_party_form_id Third Party Form ID.
468 * @param int $form_id Kit Form ID.
469 */
470 public function replace_blocks_in_post( $post_id, $third_party_form_id, $form_id ) {
471
472 // Get Post content.
473 $post_content = get_post_field( 'post_content', $post_id );
474
475 // Fetch Blocks from Content.
476 $blocks = parse_blocks( $post_content );
477
478 // If a single block was returned with blockName null, this content was not created using the block editor.
479 if ( count( $blocks ) === 1 && is_null( $blocks[0]['blockName'] ) ) {
480 return;
481 }
482
483 // Replace the third party Form Block with the Kit Form Block.
484 $post_content = $this->replace_blocks_in_content( $blocks, $third_party_form_id, $form_id );
485
486 // Double escape backslashes so that wp_update_post doesn't remove them.
487 // When content contains a single backslash (\), wp_update_post will strip it unless we double escape it (\\).
488 $post_content = str_replace( '\\', '\\\\', $post_content );
489
490 // Update the Post content.
491 wp_update_post(
492 array(
493 'ID' => $post_id,
494 'post_content' => $post_content,
495 ),
496 false,
497 false // Don't fire after action hooks.
498 );
499
500 }
501
502 /**
503 * Replaces the third party form block with the Kit form block in the given string.
504 *
505 * @since 3.1.6
506 *
507 * @param array $blocks Blocks.
508 * @param string|int $third_party_form_id Third Party Form ID.
509 * @param int $form_id Kit Form ID.
510 *
511 * @return string
512 */
513 public function replace_blocks_in_content( $blocks, $third_party_form_id, $form_id ) {
514
515 // Recursively convert blocks.
516 $blocks = $this->recursively_convert_blocks( $blocks, $third_party_form_id, $form_id );
517
518 // Serialize blocks.
519 return serialize_blocks( $blocks );
520
521 }
522
523 /**
524 * Recursively walks through an array of blocks and innerBlocks,
525 * converting third party form blocks to Kit form blocks.
526 *
527 * @since 3.1.6
528 *
529 * @param array $blocks Blocks.
530 * @param string|int $third_party_form_id Third Party Form ID.
531 * @param int $form_id Kit Form ID.
532 * @return array
533 */
534 private function recursively_convert_blocks( $blocks, $third_party_form_id, $form_id ) {
535
536 foreach ( $blocks as $index => $block ) {
537 // If this block has inner blocks, walk through the inner blocks.
538 if ( ! empty( $block['innerBlocks'] ) ) {
539 $blocks[ $index ]['innerBlocks'] = $this->recursively_convert_blocks( $block['innerBlocks'], $third_party_form_id, $form_id );
540 }
541
542 // Skip if a null block name.
543 if ( is_null( $block['blockName'] ) ) {
544 continue;
545 }
546
547 // Skip if not a third party form block.
548 if ( $block['blockName'] !== $this->block_name ) {
549 continue;
550 }
551
552 // If the block ID attribute is not set, the third party Plugin doesn't use IDs,
553 // so there's no need to check the $third_party_form_id matches the block attribute.
554 if ( $this->block_id_attribute ) {
555 // Normalise the block ID attribute to an array, so importers can
556 // declare a single attribute (string) or multiple attributes (array).
557 $id_attributes = (array) $this->block_id_attribute;
558
559 // Check if at least one attribute contains the third party form ID.
560 $matched = false;
561 foreach ( $id_attributes as $id_attribute ) {
562 if ( ! array_key_exists( $id_attribute, $block['attrs'] ) ) {
563 continue;
564 }
565
566 if ( ! $this->block_id_attribute_matches( $block['attrs'][ $id_attribute ], $third_party_form_id ) ) {
567 continue;
568 }
569
570 $matched = true;
571 break;
572 }
573
574 // Skip if none of the configured ID attributes match.
575 if ( ! $matched ) {
576 continue;
577 }
578 }
579
580 // Replace third party form block with Kit form block.
581 $blocks[ $index ] = array(
582 'blockName' => 'convertkit/form',
583 'attrs' => array(
584 'form' => (string) $form_id,
585 ),
586 'innerBlocks' => array(),
587 'innerHTML' => '',
588 'innerContent' => array(),
589 );
590 }
591
592 return $blocks;
593
594 }
595
596 /**
597 * Returns whether the given block ID attribute value matches the third party form ID.
598 *
599 * @since 3.4.5
600 *
601 * @param mixed $value Block ID attribute value.
602 * @param string|int $third_party_form_id Third Party Form ID.
603 * @return bool
604 */
605 protected function block_id_attribute_matches( $value, $third_party_form_id ) {
606
607 return (string) $value === (string) $third_party_form_id;
608
609 }
610
611 }
612