Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 5 additions & 5 deletions lib/Cleantalk/ApbctWP/WcSpamOrdersListTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ private function renderOrderDetailsColumn($order_details)
$wc_product_class = '\WC_Product';
$product_title = $wc_product instanceof $wc_product_class ? $wc_product->get_title() : '';
}
$result .= "<b>" . $product_title . "</b>";
$result .= "<b>" . esc_html($product_title) . "</b>";
$result .= " - ";
$result .= $order_detail['quantity'];
$result .= esc_html($order_detail['quantity']);
$result .= "<br>";
}

Expand All @@ -242,11 +242,11 @@ private function renderCustomerDetailsColumn($customer_details)

$result = '';

$result .= "<b>" . ($customer_details["billing_first_name"] ?? '') . "</b>";
$result .= "<b>" . esc_html($customer_details["billing_first_name"] ?? '') . "</b>";
$result .= "<br>";
$result .= "<b>" . ($customer_details["billing_last_name"] ?? '') . "</b>";
$result .= "<b>" . esc_html($customer_details["billing_last_name"] ?? '') . "</b>";
$result .= "<br>";
$result .= "<b>" . ($customer_details["billing_email"] ?? '') . "</b>";
$result .= "<b>" . esc_html($customer_details["billing_email"] ?? '') . "</b>";

return $result;
}
Expand Down
185 changes: 185 additions & 0 deletions tests/ApbctWP/TestWcSpamOrdersListTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

use Cleantalk\ApbctWP\WcSpamOrdersListTable;
use PHPUnit\Framework\TestCase;

class TestWcSpamOrdersListTable extends TestCase
{
/**
* @var WcSpamOrdersListTable
*/
private $instance;

/**
* @var \ReflectionMethod
*/
private $renderOrderDetailsColumn;

/**
* @var \ReflectionMethod
*/
private $renderCustomerDetailsColumn;

protected function setUp(): void
{
// Create instance without calling constructor to avoid dependencies
$reflection = new ReflectionClass(WcSpamOrdersListTable::class);
$this->instance = $reflection->newInstanceWithoutConstructor();

// Make private methods accessible
$this->renderOrderDetailsColumn = $reflection->getMethod('renderOrderDetailsColumn');
$this->renderOrderDetailsColumn->setAccessible(true);

$this->renderCustomerDetailsColumn = $reflection->getMethod('renderCustomerDetailsColumn');
$this->renderCustomerDetailsColumn->setAccessible(true);
}

/**
* Test renderOrderDetailsColumn returns error on invalid JSON
*/
public function testRenderOrderDetailsColumnInvalidJson()
{
$result = $this->renderOrderDetailsColumn->invoke($this->instance, 'invalid json');

$this->assertEquals('<b>Product details decoding error.</b><br>', $result);
}

/**
* Test renderOrderDetailsColumn returns error on non-array JSON
*/
public function testRenderOrderDetailsColumnNonArrayJson()
{
$result = $this->renderOrderDetailsColumn->invoke($this->instance, '"just a string"');

$this->assertEquals('<b>Product details decoding error.</b><br>', $result);
}

/**
* Test renderOrderDetailsColumn with valid order details (no WooCommerce)
*/
public function testRenderOrderDetailsColumnValidDataWithoutWooCommerce()
{
$orderDetails = json_encode([
['product_id' => 1, 'quantity' => 2],
['product_id' => 2, 'quantity' => 5],
]);

$result = $this->renderOrderDetailsColumn->invoke($this->instance, $orderDetails);

// Without WooCommerce, product title should be 'Unavailable product'
$this->assertStringContainsString('<b>Unavailable product</b>', $result);
$this->assertStringContainsString(' - 2<br>', $result);
$this->assertStringContainsString(' - 5<br>', $result);
}

/**
* Test renderOrderDetailsColumn escapes HTML to prevent XSS
*/
public function testRenderOrderDetailsColumnEscapesHtmlInQuantity()
{
$orderDetails = json_encode([
['product_id' => 1, 'quantity' => '<script>alert("xss")</script>'],
]);

$result = $this->renderOrderDetailsColumn->invoke($this->instance, $orderDetails);

// Verify that script tags are escaped
$this->assertStringNotContainsString('<script>', $result);
$this->assertStringContainsString('&lt;script&gt;', $result);
}

/**
* Test renderOrderDetailsColumn with empty array
*/
public function testRenderOrderDetailsColumnEmptyArray()
{
$orderDetails = json_encode([]);

$result = $this->renderOrderDetailsColumn->invoke($this->instance, $orderDetails);

$this->assertEquals('', $result);
}

/**
* Test renderCustomerDetailsColumn returns error on invalid JSON
*/
public function testRenderCustomerDetailsColumnInvalidJson()
{
$result = $this->renderCustomerDetailsColumn->invoke($this->instance, 'invalid json');

$this->assertEquals('<b>Customer details decoding error.</b><br>', $result);
}

/**
* Test renderCustomerDetailsColumn with valid customer details
*/
public function testRenderCustomerDetailsColumnValidData()
{
$customerDetails = json_encode([
'billing_first_name' => 'John',
'billing_last_name' => 'Doe',
'billing_email' => 'john@example.com',
]);

$result = $this->renderCustomerDetailsColumn->invoke($this->instance, $customerDetails);

$this->assertStringContainsString('<b>John</b>', $result);
$this->assertStringContainsString('<b>Doe</b>', $result);
$this->assertStringContainsString('<b>john@example.com</b>', $result);
}

/**
* Test renderCustomerDetailsColumn escapes HTML to prevent XSS
*/
public function testRenderCustomerDetailsColumnEscapesHtml()
{
$customerDetails = json_encode([
'billing_first_name' => '<script>alert("xss")</script>',
'billing_last_name' => '<img onerror="alert(1)" src="">',
'billing_email' => '"><script>alert("email")</script>',
]);

$result = $this->renderCustomerDetailsColumn->invoke($this->instance, $customerDetails);

// Verify that malicious HTML is escaped
$this->assertStringNotContainsString('<script>', $result);
$this->assertStringNotContainsString('<img', $result);
$this->assertStringContainsString('&lt;script&gt;', $result);
$this->assertStringContainsString('&lt;img', $result);
}

/**
* Test renderCustomerDetailsColumn with missing fields
*/
public function testRenderCustomerDetailsColumnMissingFields()
{
$customerDetails = json_encode([
'billing_first_name' => 'John',
// billing_last_name and billing_email are missing
]);

$result = $this->renderCustomerDetailsColumn->invoke($this->instance, $customerDetails);

$this->assertStringContainsString('<b>John</b>', $result);
// Empty values for missing fields
$this->assertStringContainsString('<b></b>', $result);
}

/**
* Test renderCustomerDetailsColumn with empty values
*/
public function testRenderCustomerDetailsColumnEmptyValues()
{
$customerDetails = json_encode([
'billing_first_name' => '',
'billing_last_name' => '',
'billing_email' => '',
]);

$result = $this->renderCustomerDetailsColumn->invoke($this->instance, $customerDetails);

// Should contain empty bold tags
$expectedCount = substr_count($result, '<b></b>');
$this->assertEquals(3, $expectedCount);
}
}
Loading