Skip to content
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
36 changes: 35 additions & 1 deletion src/Integration/WooCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public function enrichProduct(array $payload, WP_Post $post): array
$payload = $this->attachTaxonomies($payload, $post);
$payload = $this->attachAttributes($payload, $product);

return $this->attachSku($payload, $product);
$payload = $this->attachSku($payload, $product);

return $this->attachThumbnail($payload, $product);
}

/**
Expand Down Expand Up @@ -182,6 +184,38 @@ private function attachSku(array $payload, object $product): array
return $this->appendSearchableLines($payload, ['sku: '.implode(', ', $skus)]);
}

/**
* Product image when the post has no featured image of its own
* (variations often inherit the parent's).
*
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
private function attachThumbnail(array $payload, object $product): array
{
if (! empty($payload['thumbnail']) || ! method_exists($product, 'get_image_id')) {
return $payload;
}

$imageId = (int) $product->get_image_id();

if ($imageId <= 0) {
return $payload;
}

$url = wp_get_attachment_image_url($imageId, 'large');

if (is_string($url) && $url !== '') {
if (str_starts_with(strtolower($url), 'http://')) {
$url = 'https://'.substr($url, 7);
}

$payload['thumbnail'] = $url;
}

return $payload;
}

/**
* @return array<int, string>
*/
Expand Down
23 changes: 23 additions & 0 deletions src/Sync/PagePreparer.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public function prepare(WP_Post $post, array $metaMappings = []): ?array
'meta' => array_filter($meta, fn ($value) => $value !== null && $value !== '' && $value !== []),
];

$payload['thumbnail'] = $this->featuredImageUrl($post);

$payload = apply_filters('datalumo_page_payload', $payload, $post);

if (! is_array($payload)) {
Expand Down Expand Up @@ -126,6 +128,27 @@ private function ensureWooCommerceNotices(): void
}
}

/**
* Featured image as an https URL, or null when the post has none
* (null is sent so a re-sync can clear a stored URL). `large` keeps
* search-card payloads smaller than the original. http is upgraded
* so HTTPS dashboards do not block the image.
*/
private function featuredImageUrl(WP_Post $post): ?string
{
$url = get_the_post_thumbnail_url($post, 'large');

if (! is_string($url) || $url === '') {
return null;
}

if (str_starts_with(strtolower($url), 'http://')) {
$url = 'https://'.substr($url, 7);
}

return str_starts_with(strtolower($url), 'https://') ? $url : null;
}

/**
* @return array<int, string>
*/
Expand Down
31 changes: 30 additions & 1 deletion tests/Unit/PagePreparerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Functions\when('get_post_time')->justReturn('2004-01-01T00:00:00+00:00');
Functions\when('get_post_modified_time')->justReturn('2004-01-02T00:00:00+00:00');
Functions\when('get_permalink')->justReturn('https://example.com/post');
Functions\when('get_the_post_thumbnail_url')->justReturn(false);
});

it('decodes HTML entities in the post title', function () {
Expand All @@ -30,7 +31,35 @@
$payload = (new PagePreparer())->prepare(new WP_Post());

expect($payload['name'])->toBe('Kleine’ Douglas redde het niet')
->and($payload['content_mime'])->toBe('text/html');
->and($payload['content_mime'])->toBe('text/html')
->and($payload['thumbnail'])->toBeNull();
});

it('upgrades an http featured image to https', function () {
Functions\when('get_the_title')->justReturn('Tote');
Functions\when('get_the_post_thumbnail_url')->justReturn('http://datalumo-fresh.test/wp-content/uploads/tote.png');

$payload = (new PagePreparer())->prepare(new WP_Post());

expect($payload['thumbnail'])->toBe('https://datalumo-fresh.test/wp-content/uploads/tote.png');
});

it('includes the featured image as a thumbnail', function () {
Functions\when('get_the_title')->justReturn('Guide');
Functions\when('get_the_post_thumbnail_url')->justReturn('https://example.com/wp-content/uploads/guide-1024x768.jpg');

$payload = (new PagePreparer())->prepare(new WP_Post());

expect($payload['thumbnail'])->toBe('https://example.com/wp-content/uploads/guide-1024x768.jpg');
});

it('skips a non-http featured image', function () {
Functions\when('get_the_title')->justReturn('Guide');
Functions\when('get_the_post_thumbnail_url')->justReturn('javascript:alert(1)');

$payload = (new PagePreparer())->prepare(new WP_Post());

expect($payload['thumbnail'])->toBeNull();
});

it('falls back when block rendering fatals', function () {
Expand Down
59 changes: 58 additions & 1 deletion tests/Unit/WooCommerceSkuTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ function wcProduct(
string $shortDescription = '',
array $attributes = [],
int $id = 1,
int $imageId = 0,
): object {
return new class($sku, $type, $children, $shortDescription, $attributes, $id)
return new class($sku, $type, $children, $shortDescription, $attributes, $id, $imageId)
{
public function __construct(
private string $sku,
Expand All @@ -20,6 +21,7 @@ public function __construct(
private string $shortDescription,
private array $attributes,
private int $id,
private int $imageId,
) {}

public function get_id(): int
Expand Down Expand Up @@ -51,6 +53,11 @@ public function get_children(): array
{
return $this->children;
}

public function get_image_id(): int
{
return $this->imageId;
}
};
}

Expand Down Expand Up @@ -228,3 +235,53 @@ public function get_options(): array
->and($payload['content'])->toContain('Color: Blue, Red')
->and($payload['content'])->not->toContain('Internal');
});

it('adds a product image when the thumbnail is null', function () {
Functions\when('wc_get_product')->justReturn(wcProduct(sku: 'MUG-1', imageId: 44));
Functions\when('wp_get_attachment_image_url')->justReturn('http://shop.example/mug-large.jpg');

$post = new WP_Post();
$post->ID = 4;
$post->post_type = 'product';

$payload = (new WooCommerce())->enrichProduct([
'content' => '<p>A mug.</p>',
'meta' => ['post_type' => 'product'],
'thumbnail' => null,
], $post);

expect($payload['thumbnail'])->toBe('https://shop.example/mug-large.jpg');
});

it('adds a product image when the payload has no thumbnail', function () {
Functions\when('wc_get_product')->justReturn(wcProduct(sku: 'MUG-1', imageId: 44));
Functions\when('wp_get_attachment_image_url')->justReturn('http://shop.example/mug-large.jpg');

$post = new WP_Post();
$post->ID = 4;
$post->post_type = 'product';

$payload = (new WooCommerce())->enrichProduct([
'content' => '<p>A mug.</p>',
'meta' => ['post_type' => 'product'],
], $post);

expect($payload['thumbnail'])->toBe('https://shop.example/mug-large.jpg');
});

it('does not overwrite an existing thumbnail', function () {
Functions\when('wc_get_product')->justReturn(wcProduct(sku: 'MUG-1', imageId: 44));
Functions\when('wp_get_attachment_image_url')->justReturn('https://shop.example/mug-large.jpg');

$post = new WP_Post();
$post->ID = 4;
$post->post_type = 'product';

$payload = (new WooCommerce())->enrichProduct([
'content' => '<p>A mug.</p>',
'meta' => ['post_type' => 'product'],
'thumbnail' => 'https://example.com/already.jpg',
], $post);

expect($payload['thumbnail'])->toBe('https://example.com/already.jpg');
});
Loading