Skip to content

Commit

Permalink
Merge pull request #733 from ConvertKit/broadcasts-import-images
Browse files Browse the repository at this point in the history
Broadcasts: Import Images
  • Loading branch information
n7studios authored Oct 29, 2024
2 parents b191172 + dd9e64c commit 7e4e1e3
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 13 deletions.
44 changes: 44 additions & 0 deletions admin/section/class-convertkit-admin-settings-broadcasts.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,20 @@ public function register_fields() {
)
);

add_settings_field(
'import_images',
__( 'Import Images', 'convertkit' ),
array( $this, 'import_images_callback' ),
$this->settings_key,
$this->name,
array(
'name' => 'import_images',
'label_for' => 'import_images',
'label' => __( 'If enabled, the imported Broadcast\'s inline images will be stored in the Media Library, instead of served by Kit.', 'convertkit' ),
'description' => '',
)
);

add_settings_field(
'published_at_min_date',
__( 'Earliest Date', 'convertkit' ),
Expand Down Expand Up @@ -509,6 +523,29 @@ public function import_thumbnail_callback( $args ) {

}

/**
* Renders the input for the Import Images setting.
*
* @since 2.6.3
*
* @param array $args Setting field arguments (name,description).
*/
public function import_images_callback( $args ) {

// Output field.
echo $this->get_checkbox_field( // phpcs:ignore WordPress.Security.EscapeOutput
$args['name'],
'on',
$this->settings->import_images(), // phpcs:ignore WordPress.Security.EscapeOutput
$args['label'], // phpcs:ignore WordPress.Security.EscapeOutput
$args['description'], // phpcs:ignore WordPress.Security.EscapeOutput
array(
'enabled',
)
);

}

/**
* Renders the input for the date setting.
*
Expand Down Expand Up @@ -585,6 +622,13 @@ public function sanitize_settings( $settings ) {
$settings['import_thumbnail'] = '';
}

// If the 'Include Images' setting isn't checked, it won't be included
// in the array of settings, and the defaults will enable this.
// Therefore, if the setting doesn't exist, set it to blank.
if ( ! array_key_exists( 'import_images', $settings ) ) {
$settings['import_images'] = '';
}

// Merge settings with defaults.
$settings = wp_parse_args( $settings, $this->settings->get_defaults() );

Expand Down
92 changes: 79 additions & 13 deletions includes/class-convertkit-broadcasts-importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ class ConvertKit_Broadcasts_Importer {
*/
private $broadcasts_settings = false;

/**
* Holds the Media Library class.
*
* @since 2.6.3
*
* @var bool|ConvertKit_Media_Library
*/
private $media_library = false;

/**
* Constructor. Registers actions and filters to output ConvertKit Forms and Landing Pages
* on the frontend web site.
Expand Down Expand Up @@ -50,6 +59,7 @@ public function refresh( $broadcasts ) {

// Initialize required classes.
$this->broadcasts_settings = new ConvertKit_Settings_Broadcasts();
$this->media_library = new ConvertKit_Media_Library();
$settings = new ConvertKit_Settings();
$log = new ConvertKit_Log( CONVERTKIT_PLUGIN_PATH );

Expand Down Expand Up @@ -85,7 +95,6 @@ public function refresh( $broadcasts ) {
return;
}

// Iterate through each Broadcast.
foreach ( $broadcasts as $broadcast_id => $broadcast ) {
// If a WordPress Post exists for this Broadcast ID, we previously imported it - skip it.
if ( $this->broadcast_exists_as_post( $broadcast_id ) ) {
Expand Down Expand Up @@ -115,7 +124,10 @@ public function refresh( $broadcasts ) {
continue;
}

// Create Post as a draft.
// Create Post as a draft, without content or a Featured Image.
// This gives us a Post ID we can then use if we need to import
// the Featured Image and/or Broadcast images to the Media Library,
// storing them against the Post ID just created.
$post_id = wp_insert_post(
$this->build_post_args(
$broadcast,
Expand All @@ -133,6 +145,23 @@ public function refresh( $broadcasts ) {
continue;
}

// Parse the Broadcast's content, storing it in the Post.
$post_id = wp_update_post(
array(
'ID' => $post_id,
'post_content' => $this->parse_broadcast_content( $broadcast['content'], $post_id ),
),
true
);

// Skip if an error occured.
if ( is_wp_error( $post_id ) ) {
if ( $settings->debug_enabled() ) {
$log->add( 'ConvertKit_Broadcasts_Importer::refresh(): Broadcast #' . $broadcast_id . '. Error on wp_update_post() when adding Broadcast content: ' . $post_id->get_error_message() );
}
continue;
}

// If a Product is specified, apply it as the Restrict Content setting.
if ( $broadcast['is_paid'] && $broadcast['product_id'] ) {
// Fetch Post's settings.
Expand Down Expand Up @@ -173,7 +202,7 @@ public function refresh( $broadcasts ) {
// Maybe log if an error occured updating the Post to the publish status.
if ( is_wp_error( $post_id ) ) {
if ( $settings->debug_enabled() ) {
$log->add( 'ConvertKit_Broadcasts_Importer::refresh(): Broadcast #' . $broadcast_id . '. Error on wp_update_post(): ' . $post_id->get_error_message() );
$log->add( 'ConvertKit_Broadcasts_Importer::refresh(): Broadcast #' . $broadcast_id . '. Error on wp_update_post() when transitioning post status from draft to publish: ' . $post_id->get_error_message() );
}
}
if ( $settings->debug_enabled() ) {
Expand Down Expand Up @@ -235,7 +264,6 @@ private function build_post_args( $broadcast, $author_id, $category_id = false )
'post_type' => 'post',
'post_title' => $broadcast['title'],
'post_excerpt' => ( ! is_null( $broadcast['description'] ) ? $broadcast['description'] : '' ),
'post_content' => $this->parse_broadcast_content( $broadcast['content'] ),
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', strtotime( $broadcast['published_at'] ) ),
'post_author' => $author_id,
);
Expand Down Expand Up @@ -272,12 +300,17 @@ private function build_post_args( $broadcast, $author_id, $category_id = false )
/**
* Parses the given Broadcast's content, removing unnecessary HTML tags and styles.
*
* If 'Import Images' is enabled in the Plugin settings, imports images to the
* Media Library, replacing the <img> `src` with the WordPress Media Library
* Image URL.
*
* @since 2.2.9
*
* @param string $broadcast_content Broadcast Content.
* @return string Parsed Content.
* @param int $post_id WordPress Post ID.
* @return string Parsed Content.
*/
private function parse_broadcast_content( $broadcast_content ) {
private function parse_broadcast_content( $broadcast_content, $post_id ) {

$content = $broadcast_content;

Expand Down Expand Up @@ -324,6 +357,45 @@ private function parse_broadcast_content( $broadcast_content ) {
$node->parentNode->removeChild( $node ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
}

// If the Import Images setting is enabled, iterate through all images within the Broadcast, importing them and changing their
// URLs to the WordPress Media Library hosted versions.
if ( $this->broadcasts_settings->import_images() ) {

foreach ( $xpath->query( '//img' ) as $node ) {
$image = array(
'src' => $node->getAttribute( 'src' ), // @phpstan-ignore-line
'alt' => $node->getAttribute( 'alt' ), // @phpstan-ignore-line
);

// Skip if this image isn't served from https://embed.filekitcdn.com, as it isn't
// a user uploaded image to the Broadcast.
if ( strpos( $image['src'], 'https://embed.filekitcdn.com' ) === false ) {
continue;
}

// Import Image into the Media Library.
$image_id = $this->media_library->import_remote_image(
$image['src'],
$post_id,
$image['alt']
);

// If the image could not be imported, serve the original CDN version.
if ( is_wp_error( $image_id ) ) {
continue;
}

// Get image URL from Media Library.
$image_url = wp_get_attachment_image_src(
$image_id,
'full'
);

// Replace this image's `src` attribute with the Media Library Image URL.
$node->setAttribute( 'src', $image_url[0] ); // @phpstan-ignore-line
}
}

// Save HTML to a string.
$content = $html->saveHTML();

Expand Down Expand Up @@ -460,19 +532,13 @@ private function add_broadcast_image_to_post( $broadcast, $post_id ) {
return false;
}

// Initialize class.
$media_library = new ConvertKit_Media_Library();

// Import Image into the Media Library.
$image_id = $media_library->import_remote_image(
$image_id = $this->media_library->import_remote_image(
$broadcast['thumbnail_url'],
$post_id,
$broadcast['thumbnail_alt']
);

// Destroy class.
unset( $media_library );

// Bail if an error occured.
if ( is_wp_error( $image_id ) ) {
return $image_id;
Expand Down
14 changes: 14 additions & 0 deletions includes/class-convertkit-settings-broadcasts.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ public function import_thumbnail() {

}

/**
* Returns whether to import the thumbnail to the Featured Image.
*
* @since 2.6.3
*
* @return bool
*/
public function import_images() {

return ( $this->settings['import_images'] === 'on' ? true : false );

}

/**
* Returns the earliest date that Broadcasts should be imported,
* based on their published_at date.
Expand Down Expand Up @@ -220,6 +233,7 @@ public function get_defaults() {
'post_status' => 'publish',
'category_id' => '',
'import_thumbnail' => 'on',
'import_images' => '',

// By default, only import Broadcasts as Posts for the last 30 days.
'published_at_min_date' => gmdate( 'Y-m-d', strtotime( '-30 days' ) ),
Expand Down
1 change: 1 addition & 0 deletions tests/_support/Helper/Acceptance/ConvertKitBroadcasts.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function setupConvertKitPluginBroadcasts($I, $settings = false)
switch ( $key ) {
case 'enabled':
case 'import_thumbnail':
case 'import_images':
case 'enabled_export':
case 'no_styles':
if ( $value ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,66 @@ public function testBroadcastsImportWithImportThumbnailDisabled(AcceptanceTester
}
}

/**
* Tests that Broadcasts import with inline images copied to WordPress when the Import Images
* option is enabled.
*
* @since 2.6.3
*
* @param AcceptanceTester $I Tester.
*/
public function testBroadcastsImportWithImportImagesEnabled(AcceptanceTester $I)
{
// Enable Broadcasts to Posts.
$I->setupConvertKitPluginBroadcasts(
$I,
[
'enabled' => true,
'import_thumbnail' => false,
'import_images' => true,
'published_at_min_date' => '01/01/2020',
]
);

// Run the WordPress Cron event to import Broadcasts to WordPress Posts.
$I->runCronEvent($I, $this->cronEventName);

// Wait a few seconds for the Cron event to complete importing Broadcasts.
$I->wait(7);

// Load the Posts screen.
$I->amOnAdminPage('edit.php');

// Check that no PHP warnings or notices were output.
$I->checkNoWarningsAndNoticesOnScreen($I);

// Confirm expected Broadcasts exist as Posts.
$I->see($_ENV['CONVERTKIT_API_BROADCAST_FIRST_TITLE']);
$I->see($_ENV['CONVERTKIT_API_BROADCAST_SECOND_TITLE']);
$I->see($_ENV['CONVERTKIT_API_BROADCAST_THIRD_TITLE']);

// Get created Post IDs.
$postIDs = [
(int) str_replace('post-', '', $I->grabAttributeFrom('tbody#the-list > tr:nth-child(2)', 'id')),
(int) str_replace('post-', '', $I->grabAttributeFrom('tbody#the-list > tr:nth-child(3)', 'id')),
(int) str_replace('post-', '', $I->grabAttributeFrom('tbody#the-list > tr:nth-child(4)', 'id')),
];

// Set cookie with signed subscriber ID, so Member Content broadcasts can be viewed.
$I->setCookie('ck_subscriber_id', $_ENV['CONVERTKIT_API_SIGNED_SUBSCRIBER_ID']);

// View the first post.
$I->amOnPage('?p=' . $postIDs[0]);

// Check that no PHP warnings or notices were output.
$I->checkNoWarningsAndNoticesOnScreen($I);

// Confirm no images are served from Kit's CDN, and they are served from the WordPress Media Library
// (uploads folder).
$I->dontSeeInSource('embed.filekitcdn.com');
$I->seeInSource($_ENV['TEST_SITE_WP_URL'] . '/wp-content/uploads/2023/08');
}

/**
* Tests that Broadcasts do not import when enabled in the Plugin's settings
* and an Earliest Date is specified that is newer than any Broadcasts sent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function testAccessibility(AcceptanceTester $I)
$I->seeInSource('<label for="author_id">');
$I->seeInSource('<label for="category_id">');
$I->seeInSource('<label for="import_thumbnail">');
$I->seeInSource('<label for="import_images">');
$I->seeInSource('<label for="published_at_min_date">');
$I->seeInSource('<label for="no_styles">');
}
Expand Down Expand Up @@ -80,6 +81,7 @@ public function testEnableDisableImport(AcceptanceTester $I)
$I->seeElement('span[aria-labelledby="select2-_wp_convertkit_settings_broadcasts_author_id-container"]');
$I->seeElement('span[aria-labelledby="select2-_wp_convertkit_settings_broadcasts_category_id-container"]');
$I->seeElement('input#import_thumbnail');
$I->seeElement('input#import_images');
$I->seeElement('div.convertkit-select2-container');
$I->seeElement('input#published_at_min_date');

Expand All @@ -105,6 +107,7 @@ public function testEnableDisableImport(AcceptanceTester $I)
$I->dontSeeElement('span[aria-labelledby="select2-_wp_convertkit_settings_broadcasts_author_id-container"]');
$I->dontSeeElement('span[aria-labelledby="select2-_wp_convertkit_settings_broadcasts_category_id-container"]');
$I->dontSeeElement('input#import_thumbnail');
$I->dontSeeElement('input#import_images');
$I->dontSeeElement('input#published_at_min_date');

// Check the next import date and time is not displayed.
Expand All @@ -129,6 +132,7 @@ public function testSaveSettings(AcceptanceTester $I)
$I->fillSelect2Field($I, '#select2-_wp_convertkit_settings_broadcasts_author_id-container', 'admin');
$I->fillSelect2Field($I, '#select2-_wp_convertkit_settings_broadcasts_category_id-container', 'Kit Broadcasts to Posts');
$I->checkOption('#import_thumbnail');
$I->checkOption('#import_images');
$I->fillField('_wp_convertkit_settings_broadcasts[published_at_min_date]', '01/01/2023');
$I->checkOption('#enabled_export');
$I->checkOption('#no_styles');
Expand All @@ -145,6 +149,7 @@ public function testSaveSettings(AcceptanceTester $I)
$I->seeInField('_wp_convertkit_settings_broadcasts[author_id]', 'admin');
$I->seeInField('_wp_convertkit_settings_broadcasts[category_id]', 'Kit Broadcasts to Posts');
$I->seeCheckboxIsChecked('#import_thumbnail');
$I->seeCheckboxIsChecked('#import_images');
$I->seeInField('_wp_convertkit_settings_broadcasts[published_at_min_date]', '2023-01-01');
$I->seeCheckboxIsChecked('#enabled_export');
$I->seeCheckboxIsChecked('#no_styles');
Expand Down

0 comments on commit 7e4e1e3

Please sign in to comment.