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

  1. PureClarity provides SKUs - Recommendation algorithm returns product identifiers
  2. Magento processes SKUs - Server loads current product data with customer context
  3. Custom logic applied - Pricing rules, inventory, and business logic processed
  4. Template renders output - Knockout.js template displays final recommendations
  5. 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>

Performance Optimization

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.