Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f4356bb
Setup Wizard: Use Named Steps
n7studios Feb 2, 2026
35f2c2e
Update Landing Page Wizard
n7studios Feb 2, 2026
ecf7c71
Member Content Wizard
n7studios Feb 2, 2026
4983d32
PHPStan compat.
n7studios Feb 2, 2026
e344d6a
Setup Wizard: Reinstate ‘Exit Wizard’ in Footer
n7studios Feb 2, 2026
56c8b37
Fix tests
n7studios Feb 2, 2026
feee18a
Setup Wizard: Form Importers
n7studios Feb 2, 2026
11d6f28
Added Tests
n7studios Feb 2, 2026
4c188fb
Fix logic for OAuth failure in Setup Wizard
n7studios Feb 3, 2026
068d1db
Merge branch 'main' into setup-wizard-named-steps
n7studios Feb 4, 2026
6da4d63
Check step name is registered using `get_current_step`
n7studios Feb 4, 2026
82bc394
Merge remote-tracking branch 'origin/setup-wizard-named-steps' into s…
n7studios Feb 4, 2026
734c947
Check if block/shortcode defined when fetching and replacing forms
n7studios Feb 4, 2026
12866bb
Importers: Support non-integer form IDs
n7studios Feb 4, 2026
945ef8b
Added Campaign Monitor importer
n7studios Feb 4, 2026
14fd36f
Added Tests
n7studios Feb 4, 2026
f07fead
Tests: Fix Heartbeat Warning in Siteground Speed Optimizer in WordPre…
n7studios Feb 4, 2026
672f1c5
Merge branch 'setup-wizard-form-importers' into importer-campaign-mon…
n7studios Feb 4, 2026
33f52c0
Isolate test; ignore warnings from Siteground Plugin
n7studios Feb 4, 2026
b7738dc
Revert DB changes
n7studios Feb 4, 2026
6f95696
Merge branch 'fix-tests-siteground-optimiser' into setup-wizard-named…
n7studios Feb 4, 2026
e0d2a4f
Merge branch 'setup-wizard-named-steps' into setup-wizard-form-importers
n7studios Feb 4, 2026
e233b28
Merge branch 'setup-wizard-form-importers' into importer-campaign-mon…
n7studios Feb 4, 2026
2810cee
Isolate Siteground Tests and set options to prevent heartbeat script …
n7studios Feb 5, 2026
97922f4
Isolate tests
n7studios Feb 5, 2026
cbb3066
Set a value so Heartbeat script isn’t deregistered
n7studios Feb 5, 2026
26640a2
Ignore error on Plugin activation
n7studios Feb 5, 2026
ba09e0f
Set Heartbeat values in DB dump
n7studios Feb 5, 2026
54ff2b5
Ignore errors on Siteground Plugin activation
n7studios Feb 5, 2026
a015028
Set options in test
n7studios Feb 5, 2026
ba86950
Reinstate all tests
n7studios Feb 5, 2026
fd70dac
Merge remote-tracking branch 'origin/fix-tests-siteground-optimiser' …
n7studios Feb 5, 2026
ffd284a
Merge branch 'setup-wizard-named-steps' into setup-wizard-form-importers
n7studios Feb 5, 2026
a24e0b6
Merge remote-tracking branch 'origin/setup-wizard-form-importers' int…
n7studios Feb 5, 2026
f965f99
Merge pull request #1007 from Kit/importer-campaign-monitor
n7studios Feb 7, 2026
dcb0b32
Merge pull request #1005 from Kit/setup-wizard-form-importers
n7studios Feb 7, 2026
7e49331
Merge pull request #1004 from Kit/setup-wizard-named-steps
n7studios Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 69 additions & 9 deletions admin/class-convertkit-admin-setup-wizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ class ConvertKit_Admin_Setup_Wizard {
*
* @since 1.9.8.4
*
* @var int
* @var string
*/
public $step = 1;
public $step = 'start';

/**
* The programmatic name of the setup screen.
Expand Down Expand Up @@ -170,7 +170,7 @@ public function maybe_load_setup_screen() {
}

// Define the step the user is on in the setup process.
$this->step = ( filter_has_var( INPUT_GET, 'step' ) ? absint( filter_input( INPUT_GET, 'step', FILTER_SANITIZE_NUMBER_INT ) ) : 1 );
$this->step = $this->get_current_step();

// Process any posted form data.
$this->process_form();
Expand All @@ -193,6 +193,66 @@ public function maybe_load_setup_screen() {

}

/**
* Returns the current step in the setup process.
*
* @since 3.1.7
*
* @return string Current step.
*/
public function get_current_step() {

$step = ( filter_has_var( INPUT_GET, 'step' ) ? filter_input( INPUT_GET, 'step', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : 'start' );

// Fallback to 'start' if the step is a registered step.
if ( ! array_key_exists( $step, $this->steps ) ) {
$step = 'start';
}

return $step;

}

/**
* Get the number of the current step.
*
* @since 3.1.7
*
* @return int Step number.
*/
public function get_current_step_number() {

return array_search( $this->step, array_keys( $this->steps ), true ) + 1;

}

/**
* Get the step by number.
*
* @since 3.1.7
*
* @param int $number Step number (1 based index).
* @return string Step name/key.
*/
public function get_step_key_by_number( $number ) {

return array_keys( $this->steps )[ $number - 1 ];

}

/**
* Get the total number of steps.
*
* @since 3.1.7
*
* @return int Total steps.
*/
public function get_total_steps() {

return count( $this->steps );

}

/**
* Process submitted form data for the given setup wizard name and current step.
*
Expand All @@ -205,7 +265,7 @@ private function process_form() {
*
* @since 1.9.8.4
*
* @param int $step Current step number.
* @param string $step Current step.
*/
do_action( 'convertkit_admin_setup_wizard_process_form_' . $this->page_name, $this->step );

Expand All @@ -231,24 +291,24 @@ private function define_step_urls() {
);

// Define the previous step URL if we're not on the first or last step.
if ( $this->step > 1 && $this->step < count( $this->steps ) ) {
if ( $this->get_current_step_number() > 1 && $this->get_current_step_number() < $this->get_total_steps() ) {
$this->previous_step_url = add_query_arg(
array(
'page' => $this->page_name,
'convertkit-modal' => $this->is_modal(),
'step' => ( $this->step - 1 ),
'step' => $this->get_step_key_by_number( $this->get_current_step_number() - 1 ),
),
admin_url( 'options.php' )
);
}

// Define the next step URL if we're not on the last page.
if ( $this->step < count( $this->steps ) ) {
if ( $this->get_current_step_number() < $this->get_total_steps() ) {
$this->next_step_url = add_query_arg(
array(
'page' => $this->page_name,
'convertkit-modal' => $this->is_modal(),
'step' => ( $this->step + 1 ),
'step' => $this->get_step_key_by_number( $this->get_current_step_number() + 1 ),
),
admin_url( 'options.php' )
);
Expand All @@ -268,7 +328,7 @@ private function load_screen_data() {
*
* @since 1.9.8.4
*
* @param int $step Current step number.
* @param string $step Current step.
*/
do_action( 'convertkit_admin_setup_wizard_load_screen_data_' . $this->page_name, $this->step );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php
/**
* ConvertKit Admin Importer CampaignMonitor class.
*
* @package ConvertKit
* @author ConvertKit
*/

/**
* Import and migrate data from CampaignMonitor to Kit.
*
* @package ConvertKit
* @author ConvertKit
*/
class ConvertKit_Admin_Importer_CampaignMonitor extends ConvertKit_Admin_Importer {

/**
* Holds the programmatic name of the importer (lowercase, no spaces).
*
* @since 3.1.7
*
* @var string
*/
public $name = 'campaignmonitor';

/**
* Holds the title of the importer (for display in the importer list).
*
* @since 3.1.7
*
* @var string
*/
public $title = 'CampaignMonitor';

/**
* Holds the shortcode name for ActiveCampaign forms.
*
* @since 3.1.7
*
* @var string
*/
public $shortcode_name = 'cm_form';

/**
* Holds the ID attribute name for CampaignMonitor forms.
*
* @since 3.1.7
*
* @var string
*/
public $shortcode_id_attribute = 'form_id';

/**
* Constructor
*
* @since 3.1.7
*/
public function __construct() {

// Register this as an importer, if CampaignMonitor forms exist.
add_filter( 'convertkit_get_form_importers', array( $this, 'register' ) );

}

/**
* Returns an array of CampaignMonitor form IDs and titles.
*
* @since 3.1.7
*
* @return array
*/
public function get_forms() {

// Forms are cached in the Plugin Settings.
$settings = get_option( 'forms_for_campaign_monitor_forms' );

// Bail if the CampaignMonitor Plugin Settings are not set.
if ( ! $settings ) {
return array();
}

// Build array of forms.
$forms = array();
foreach ( $settings as $form_id => $form ) {
// $form is a Campaign Monitor forms\core\Form object. It'll be a __PHP_Incomplete_Class if the Campaign Monitor plugin is not active.
// To consistently access the protected form name property, we have to cast to an array.
$form = (array) $form;

// Access the protected form name property.
// When casting __PHP_Incomplete_Class to an array, protected properties are prefixed with \0*\0.
$forms[ $form_id ] = $form["\0*\0name"];
}

return $forms;

}

}
100 changes: 67 additions & 33 deletions admin/importers/class-convertkit-admin-importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,17 @@ public function import( $mappings ) {

// Iterate through the mappings, replacing the third party form shortcodes and blocks with the Kit form shortcodes and blocks.
foreach ( $mappings as $third_party_form_id => $kit_form_id ) {
$this->replace_blocks_in_posts( (int) $third_party_form_id, (int) $kit_form_id );
$this->replace_shortcodes_in_posts( (int) $third_party_form_id, (int) $kit_form_id );
// Skip empty Kit Form IDs i.e. no mapping was provided for this third party form.
if ( empty( $kit_form_id ) ) {
continue;
}

if ( $this->block_name ) {
$this->replace_blocks_in_posts( $third_party_form_id, (int) $kit_form_id );
}
if ( $this->shortcode_name ) {
$this->replace_shortcodes_in_posts( $third_party_form_id, (int) $kit_form_id );
}
}

}
Expand All @@ -136,24 +145,49 @@ public function get_forms_in_posts() {

global $wpdb;

// Search post_content for the third party form block or shortcode and return array of post IDs.
$results = $wpdb->get_col(
$wpdb->prepare(
"
SELECT ID
FROM {$wpdb->posts}
WHERE post_status = %s
AND (
post_content LIKE %s
OR post_content LIKE %s
)
",
'publish',
'%[' . $this->shortcode_name . '%',
'%<!-- wp:' . $this->block_name . '%'
// Build WHERE clauses and values.
$post_content_clauses = array();
$post_content_values = array();

if ( $this->shortcode_name ) {
$post_content_clauses[] = 'post_content LIKE %s';
$post_content_values[] = '%[' . $this->shortcode_name . '%';
}
if ( $this->block_name ) {
$post_content_clauses[] = 'post_content LIKE %s';
$post_content_values[] = '%<!-- wp:' . $this->block_name . '%';
}

// Bail early if nothing to search for.
if ( empty( $post_content_clauses ) ) {
return array();
}

// Prepare SQL using wpdb->prepare.
// call_user_func_array() is used so variable length arrays can be passed to prepare().
$query = call_user_func_array(
array( $wpdb, 'prepare' ),
array_merge(
array(
"
SELECT ID
FROM {$wpdb->posts}
WHERE post_status = %s
AND (
" . implode( ' OR ', $post_content_clauses ) . '
)
',
),
array_merge(
array( 'publish' ),
$post_content_values
)
)
);

// Run query.
$results = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

return $results ? $results : array();

}
Expand Down Expand Up @@ -189,8 +223,8 @@ public function has_forms_in_posts() {
*
* @since 3.1.0
*
* @param int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @param string|int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
*/
public function replace_shortcodes_in_posts( $third_party_form_id, $form_id ) {

Expand Down Expand Up @@ -229,9 +263,9 @@ public function replace_shortcodes_in_posts( $third_party_form_id, $form_id ) {
*
* @since 3.1.0
*
* @param string $content Content containing third party Form Shortcodes.
* @param int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @param string $content Content containing third party Form Shortcodes.
* @param string|int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.

* @return string
*/
Expand Down Expand Up @@ -353,8 +387,8 @@ public function get_form_ids_from_content( $content ) {
*
* @since 3.1.6
*
* @param int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @param string|int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
*/
public function replace_blocks_in_posts( $third_party_form_id, $form_id ) {

Expand All @@ -378,9 +412,9 @@ public function replace_blocks_in_posts( $third_party_form_id, $form_id ) {
*
* @since 3.1.6
*
* @param int $post_id Post ID.
* @param int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @param int $post_id Post ID.
* @param string|int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
*/
public function replace_blocks_in_post( $post_id, $third_party_form_id, $form_id ) {

Expand Down Expand Up @@ -419,9 +453,9 @@ public function replace_blocks_in_post( $post_id, $third_party_form_id, $form_id
*
* @since 3.1.6
*
* @param array $blocks Blocks.
* @param int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @param array $blocks Blocks.
* @param string|int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
*
* @return string
*/
Expand All @@ -441,9 +475,9 @@ public function replace_blocks_in_content( $blocks, $third_party_form_id, $form_
*
* @since 3.1.6
*
* @param array $blocks Blocks.
* @param int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @param array $blocks Blocks.
* @param string|int $third_party_form_id Third Party Form ID.
* @param int $form_id Kit Form ID.
* @return array
*/
private function recursively_convert_blocks( $blocks, $third_party_form_id, $form_id ) {
Expand Down
Loading