Server-side mode routes recommendation requests through Magento to ensure personalized pricing, complex business rules, and custom data processing. This advanced implementation guide covers template customization and data extension.
Server-Side Mode Overview
How Server-Side Mode Works
- PureClarity provides SKUs - Recommendation algorithm returns product identifiers
- Magento processes SKUs - Server loads current product data with customer context
- Custom logic applied - Pricing rules, inventory, and business logic processed
- Template renders output - Knockout.js template displays final recommendations
- HTML delivered to page - Complete recommendation content served
Differences from Client-Side Mode
Template Management:
- Server-side: Templates managed in Magento theme files
- Client-side: Templates managed in PureClarity admin
Data Processing:
- Server-side: Real-time Magento data with customer context
- Client-side: Static data from pre-generated feeds
Customization:
- Server-side: Full access to Magento functionality
- Client-side: Limited to feed data and PureClarity template editor
Changes made in PureClarity Admin’s Template Editor or Recommender Designer will not affect server-side mode displays. All template customization must be done in Magento.
Template Customization
Default Template Location
The product recommender template is located at:
vendor/pureclarity/pureclarity-magento-2/view/frontend/web/template/product-recommender.html
Override Template in Your Theme
To customize the template, copy it to your theme:
Target location:
app/design/frontend/[Vendor]/[theme]/Pureclarity_Core/web/template/product-recommender.html
Example file structure:
app/design/frontend/
└── MyCompany/
└── mytheme/
└── Pureclarity_Core/
└── web/
└── template/
└── product-recommender.html
Never modify the original template in the vendor directory as it will be overwritten during extension updates.
Basic Template Structure
The template uses Knockout.js data binding:
<!-- ko foreach: products -->
<div class="product-item" data-bind="attr: {id: 'product-' + id}">
<div class="product-image">
<img data-bind="attr: {src: image, alt: name}" />
</div>
<div class="product-info">
<h3 class="product-name" data-bind="text: name"></h3>
<div class="product-price" data-bind="text: price"></div>
<!-- Custom fields can be added here -->
<div class="custom-data" data-bind="text: my_custom_field"></div>
</div>
<div class="product-actions">
<button class="btn-cart" data-bind="attr: {href: url}">
Add to Cart
</button>
</div>
</div>
<!-- /ko -->
Available Data Fields
Standard product data available in templates:
id
- Product ID
name
- Product name
price
- Formatted price string
url
- Product page URL
image
- Product image URL
description
- Product description
sku
- Product SKU
Additional fields through customization:
- Custom attributes
- Calculated pricing
- Inventory information
- Customer-specific data
Custom Data Extension
Extension Point
Extend product data using plugins on the ProductData
class:
Class: \Pureclarity\Core\Model\Serverside\Response\ProductData
Method: populateProductData()
- Adds data to the array sent to Knockout
Implementation Example
1. Create di.xml configuration:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Pureclarity\Core\Model\Serverside\Response\ProductData">
<plugin name="custom_serverside_data"
type="MyCompany\MyModule\Plugin\ServersideDataPlugin"
sortOrder="10"
disabled="false" />
</type>
</config>
2. Create the plugin class:
<?php
namespace MyCompany\MyModule\Plugin;
use Pureclarity\Core\Model\Serverside\Response\ProductData;
class ServersideDataPlugin
{
private $customerSession;
private $pricingHelper;
public function __construct(
\Magento\Customer\Model\Session $customerSession,
\MyCompany\MyModule\Helper\Pricing $pricingHelper
) {
$this->customerSession = $customerSession;
$this->pricingHelper = $pricingHelper;
}
/**
* Add custom data to product recommendations
*
* @param ProductData $subject
* @param array $data
* @return array
*/
public function afterPopulateProductData(ProductData $subject, array $data)
{
// Add custom pricing logic
$data['price'] = $this->calculateCustomPrice($data);
// Add custom fields
$data['my_custom_field'] = $this->getCustomFieldValue($data);
$data['customer_tier'] = $this->getCustomerTier();
$data['stock_status'] = $this->getStockStatus($data['id']);
return $data;
}
/**
* Calculate customer-specific pricing
*/
private function calculateCustomPrice(array $productData)
{
$customer = $this->customerSession->getCustomer();
return $this->pricingHelper->getCustomerPrice($productData['id'], $customer);
}
/**
* Get custom field value
*/
private function getCustomFieldValue(array $productData)
{
// Your custom logic here
return 'Custom Value for Product ' . $productData['id'];
}
/**
* Get customer tier information
*/
private function getCustomerTier()
{
$customer = $this->customerSession->getCustomer();
return $customer->getCustomAttribute('customer_tier') ?? 'standard';
}
/**
* Get current stock status
*/
private function getStockStatus($productId)
{
// Implement stock checking logic
return 'in_stock';
}
}
Advanced Custom Pricing Example
private function calculateCustomPrice(array $productData)
{
$customer = $this->customerSession->getCustomer();
$productId = $productData['id'];
// Get base price
$basePrice = $productData['price_raw'] ?? 0;
// Apply customer-specific discount
$customerDiscount = $this->getCustomerDiscount($customer);
$discountedPrice = $basePrice * (1 - $customerDiscount);
// Apply volume pricing
$volumePrice = $this->applyVolumePricing($productId, $customer, $discountedPrice);
// Apply contract pricing if applicable
$finalPrice = $this->applyContractPricing($productId, $customer, $volumePrice);
// Format for display
return $this->formatPrice($finalPrice);
}
Advanced Template Customization
Responsive Design Template
<!-- ko foreach: products -->
<div class="product-item col-xs-12 col-sm-6 col-md-4 col-lg-3">
<div class="product-card">
<div class="product-image-container">
<img data-bind="attr: {src: image, alt: name}"
class="product-image img-responsive" />
<!-- Custom badge for special pricing -->
<!-- ko if: customer_tier === 'premium' -->
<span class="premium-badge">Premium Price</span>
<!-- /ko -->
</div>
<div class="product-details">
<h4 class="product-name" data-bind="text: name"></h4>
<!-- Custom pricing display -->
<div class="price-container">
<!-- ko if: has_special_price -->
<span class="regular-price" data-bind="text: regular_price"></span>
<span class="special-price" data-bind="text: price"></span>
<!-- /ko -->
<!-- ko ifnot: has_special_price -->
<span class="price" data-bind="text: price"></span>
<!-- /ko -->
</div>
<!-- Custom stock indicator -->
<div class="stock-indicator" data-bind="css: stock_status">
<span data-bind="text: stock_message"></span>
</div>
<!-- Custom attributes -->
<!-- ko if: custom_attribute -->
<div class="custom-info" data-bind="text: custom_attribute"></div>
<!-- /ko -->
</div>
<div class="product-actions">
<!-- ko if: stock_status === 'in_stock' -->
<button class="btn btn-primary btn-cart"
data-bind="click: $parent.addToCart">
Add to Cart
</button>
<!-- /ko -->
<!-- ko if: stock_status === 'out_of_stock' -->
<button class="btn btn-secondary" disabled>
Out of Stock
</button>
<!-- /ko -->
<a class="btn btn-link view-details"
data-bind="attr: {href: url}">
View Details
</a>
</div>
</div>
</div>
<!-- /ko -->
Template with Custom Styling
<style>
.pureclarity-recommendations {
margin: 20px 0;
}
.product-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
transition: box-shadow 0.3s;
}
.product-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.premium-badge {
position: absolute;
top: 10px;
right: 10px;
background: gold;
color: #333;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.price-container .regular-price {
text-decoration: line-through;
color: #999;
margin-right: 10px;
}
.price-container .special-price {
color: #e74c3c;
font-weight: bold;
}
.stock-indicator.in_stock {
color: #27ae60;
}
.stock-indicator.out_of_stock {
color: #e74c3c;
}
</style>
<div class="pureclarity-recommendations">
<!-- ko foreach: products -->
<!-- Template content here -->
<!-- /ko -->
</div>
Caching Strategies
public function afterPopulateProductData(ProductData $subject, array $data)
{
// Use static cache for expensive operations
static $priceCache = [];
static $customerTierCache = null;
$productId = $data['id'];
// Cache customer tier for session
if ($customerTierCache === null) {
$customerTierCache = $this->getCustomerTier();
}
// Cache pricing calculations
if (!isset($priceCache[$productId])) {
$priceCache[$productId] = $this->calculateCustomPrice($data);
}
$data['price'] = $priceCache[$productId];
$data['customer_tier'] = $customerTierCache;
return $data;
}
Batch Processing
public function afterPopulateProductData(ProductData $subject, array $data)
{
// Collect product IDs for batch processing
static $productIds = [];
static $batchData = [];
$productIds[] = $data['id'];
// Process in batches of 10
if (count($productIds) >= 10) {
$batchData = array_merge($batchData, $this->processBatch($productIds));
$productIds = [];
}
// Apply batch data if available
if (isset($batchData[$data['id']])) {
$data = array_merge($data, $batchData[$data['id']]);
}
return $data;
}
Error Handling and Fallbacks
Graceful Degradation
public function afterPopulateProductData(ProductData $subject, array $data)
{
try {
// Attempt custom pricing calculation
$customPrice = $this->calculateCustomPrice($data);
$data['price'] = $customPrice;
} catch (\Exception $e) {
// Log error but don't break recommendations
$this->logger->error('Custom pricing failed', [
'product_id' => $data['id'],
'error' => $e->getMessage()
]);
// Use fallback pricing
$data['price'] = $this->formatPrice($data['price_raw'] ?? 0);
}
return $data;
}
API Integration with Timeouts
private function getExternalData($productId)
{
try {
// Set timeout for external API
$this->httpClient->setTimeout(2); // 2 second timeout
$response = $this->httpClient->get("/api/product/{$productId}");
return $response->getData();
} catch (\Exception $e) {
// Return empty array on failure
$this->logger->warning('External API failed', [
'product_id' => $productId,
'error' => $e->getMessage()
]);
return [];
}
}
Testing Server-Side Implementation
Unit Testing Custom Data
public function testCustomDataIsAddedToProduct()
{
// Mock dependencies
$mockCustomerSession = $this->createMock(\Magento\Customer\Model\Session::class);
$mockPricingHelper = $this->createMock(\MyCompany\MyModule\Helper\Pricing::class);
// Create plugin instance
$plugin = new ServersideDataPlugin($mockCustomerSession, $mockPricingHelper);
// Test data
$inputData = ['id' => 123, 'name' => 'Test Product', 'price_raw' => 29.99];
// Mock the subject
$mockSubject = $this->createMock(ProductData::class);
// Execute plugin
$result = $plugin->afterPopulateProductData($mockSubject, $inputData);
// Assert custom fields are added
$this->assertArrayHasKey('my_custom_field', $result);
$this->assertArrayHasKey('customer_tier', $result);
}
Integration Testing
public function testServersideRecommendationsDisplay()
{
// Set up test customer with special pricing
$customer = $this->createTestCustomer(['tier' => 'premium']);
$this->customerSession->loginById($customer->getId());
// Create test products
$products = $this->createTestProducts(5);
// Request server-side recommendations
$response = $this->serversideProcessor->processRecommendation([
'zone' => 'test-zone',
'products' => array_column($products, 'id')
]);
// Verify custom data is included
$this->assertNotEmpty($response['products']);
$this->assertArrayHasKey('customer_tier', $response['products'][0]);
}
Configuration and Setup
Development Resources
Troubleshooting
Summary
Server-side mode implementation enables:
- Custom pricing logic with real-time calculations
- Template customization through Magento theme system
- Data extension via plugin system
- Performance optimization through caching and batch processing
- Error handling with graceful degradation
Use server-side mode when your requirements exceed the capabilities of client-side recommendations and feed-based data delivery.