Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broadcasts: Import Images #733

Merged
merged 9 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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