Skip to content

Doofinder Search Integration

Flow ID: IN-10 | Module(s): ecommercen/doofinder/, src/Feeds/Output/ | Complexity: Medium

Business Overview

Doofinder is an external SaaS search platform that powers on-site search for selected Ecommercen clients. Unlike Solr (which indexes data locally), Doofinder operates on a pull-based model: it periodically fetches XML feeds from the shop. Ecommercen exposes three dedicated XML feeds -- products, blog articles, and vendors -- that Doofinder crawls on a configured schedule.

The product feed is enriched with Greek-to-Greeklish transliterations (and reverse), best-seller flags, top-stock indicators, product line names, tag attributes, and full category hierarchies. This enables Doofinder to provide autocomplete, typo-tolerant, and language-flexible search results in the storefront.

API Reference

Feed Endpoints (Public XML)

EndpointControllerRoot ElementItem Element
GET /xml/doofinder/productsAdvProductsXml::index()<products><product>
GET /xml/doofinder/blogAdvBlogXml::index()<blog><article>
GET /xml/doofinder/vendorsAdvVendorsXml::index()<products><product>

All endpoints support language-prefixed routes: /{lang}/xml/doofinder/products (e.g., /en/xml/doofinder/products).

Product Feed Fields

FieldSourceDescription
id / product_idshop_product.idProduct identifier
titleshop_product_mui.nameLocalized product name
linkGeneratedFull product URL with currency
image_linkshop_product imagesMain product image, resized to w280
price / sale_priceComputedOriginal and final prices with currency rate applied
availabilityStock checkin stock or out of stock (respects negative_stock)
gtinproduct_codesComma-separated barcodes
brandshop_vendor_mui.nameVendor/brand name
lineProduct linesProduct line name (if any)
product_codeproduct_codesSKU
title_el_to_enComputedGreek-to-Greeklish transliteration of name
title_en_to_elComputedEnglish-to-Greek transliteration of name
best_sellerTop 500 salesyes / no
top_stockTop 500 stockyes / no
attributesProduct tagsTags joined with / separator
categoryCategory hierarchyFull category path(s) with > separator, %%-delimited
category_en_to_elComputedGreeklish version of category paths

Vendor Feed Fields

FieldSourceDescription
idVENDOR_{id}Prefixed vendor identifier
title / brand / brand_nameshop_vendor_mui.nameVendor name in 3 fields
linkGeneratedVendor page URL
image_linkshop_vendor.logoVendor logo, resized to w280
itemsComputedActive product count for vendor
*_el_to_en / *_en_to_elComputedTransliterations for all text fields

Blog Feed Fields

FieldSourceDescription
idblog.idBlog post identifier
titleblog_mui.titleLocalized title
linkblog_mui.url or slugBlog post URL
imageblog.banner_imageBanner image, resized to w280
descriptionblog_mui.small_descriptionPlain-text excerpt
title_el_to_en / title_en_to_elComputedTransliterations

Code Flow

Product Feed Generation

HTTP GET /xml/doofinder/products
  -> AdvProductsXml::index()
     -> getProductData() (via AdvXml base class, applies stock/pricing)
     -> parseForXml($products)
        -> AdvDoofinderModel::getCategoriesCombo()  (cached via pscache)
        -> For each product:
           -> parseItem($product, $categories)
              -> getMainProductImage(), resizeImageOnTheFly()
              -> applyCurrencyRate() for price/sale_price
              -> convertGreekToGreeklish(), convertEnglishToGreek()
              -> Map category IDs to full hierarchy paths
     -> OutputDoofinderXml::createXml($data)
     -> Feed::output()  (sets Content-Type: text/xml, outputs)

Data Enrichment (in Model)

AdvDoofinderModel::getProducts()
  -> Raw SQL query joining shop_product, product_codes, shop_product_mui, shop_vendor, shop_vendor_mui
  -> Subqueries for: categories_ids, variations, product_tags
  -> Filters: price > 0, soft_delete = 0, active = 1
  -> Post-processing:
     -> getTopStockProductsIds(500) -> marks top_stock = yes/no
     -> getBestSellersWithLimit(500) -> marks best_seller = yes/no (cached)
     -> getProductsLines() -> attaches product line names

Category Hierarchy Building

AdvDoofinderModel::getCategoriesCombo()
  -> Fetches all categories with parent_id, name, published status
  -> buildHierarchy(0, categories) recursively
     -> Builds "Parent > Child > Grandchild" paths
     -> Skips unpublished categories and their descendants
  -> Returns [category_id => fullPath] map

Architecture

ecommercen/doofinder/
  controllers/
    AdvProductsXml.php      # Product feed controller (extends AdvXml)
    AdvBlogXml.php          # Blog feed controller (extends AdvXml)
    AdvVendorsXml.php       # Vendor feed controller (extends AdvXml)
  models/
    AdvDoofinderModel.php   # Core model with SQL queries and enrichment logic

application/modules/doofinder/
  models/
    Doofinder_model.php     # Client-overridable model (extends AdvDoofinderModel, empty)

src/Feeds/Output/
  OutputDoofinderXml.php    # XML output formatter (extends OutputGenericXml)

Class Hierarchy

  • Controllers: AdvProductsXml / AdvBlogXml / AdvVendorsXml all extend AdvXml (the shared feed base controller in ecommercen/feeds/core/), which provides stock resolution, currency handling, XML config, and caching.
  • Model: AdvDoofinderModel extends Adv_base_model. The application/modules/doofinder/models/Doofinder_model.php is an empty extension point for client overrides.
  • Output: OutputDoofinderXml extends OutputGenericXml, providing the standard <?xml ... ?> envelope with configurable root/item element names.

Key Difference from Solr

AspectDoofinderSolr
TypeExternal SaaSInternal search engine
Data flowPull (Doofinder fetches XML)Push (app indexes to Solr)
SetupFeed URLs configured in Doofinder dashboardSolr server configured in .env
DeploymentNo server infrastructure neededRequires Solr server

Data Model

Tables Queried

TableRole
shop_productCore product data (price, stock, active, soft_delete)
shop_product_muiLocalized product name, slug, description
shop_vendor / shop_vendor_muiVendor/brand information
product_codesProduct SKU/barcodes
shop_product_category / shop_product_category_muiCategory hierarchy
shop_product_category_lpProduct-to-category link table
shop_product_tags / shop_product_tags_muiProduct tags
shop_product_product_tagsProduct-to-tag link table
shop_product_tag_categories / shop_product_tag_categories_muiTag category metadata
product_variation_valuesVariation data (used for grouping)
blog / blog_muiBlog posts

Configuration

Routes (application/config/routes.php)

php
$route['xml/doofinder/products'] = 'doofinder/productsXml/index';
$route['xml/doofinder/blog'] = 'doofinder/blogXml/index';
$route['xml/doofinder/vendors'] = 'doofinder/vendorsXml/index';
// Language-prefixed variants
$route['(\w{2})/xml/doofinder/products'] = 'doofinder/productsXml/index';
$route['(\w{2})/xml/doofinder/blog'] = 'doofinder/blogXml/index';
$route['(\w{2})/xml/doofinder/vendors'] = 'doofinder/vendorsXml/index';

Caching

Category hierarchies are cached via pscache with the L2 default TTL. Best-seller product IDs are also cached at the L2 level. No Doofinder-specific registry or .env keys are required -- the feeds are public XML endpoints.

Client Extension Points

Model Override

Create application/modules/doofinder/models/Doofinder_model.php extending AdvDoofinderModel to customize:

  • Product query SQL (override getProducts())
  • Category hierarchy building (override buildHierarchy() or getCategoriesCombo())
  • Blog/vendor queries (override getBlogs(), getVendors())

Controller Override

Create controllers in application/modules/doofinder/controllers/ extending the Adv*Xml classes to customize:

  • Feed field mapping (override parseItem())
  • Additional data enrichment (override parseForXml())
  • XML configuration (override getXmlConfig() -- see AdvBlogXml for custom root/item names)

Feed Output Override

The OutputDoofinderXml class can be extended in custom/Feeds/Output/ for structural XML changes.

Business Rules

  1. Product inclusion: Only products with price > 0, active = 1, soft_delete = 0 are included.
  2. Best sellers: Top 500 products by sales volume are flagged best_seller = yes.
  3. Top stock: Top 500 products by stock quantity are flagged top_stock = yes.
  4. Category paths: Only published categories (and their published ancestors) appear in the hierarchy. Unpublished categories cause their entire subtree to be excluded.
  5. Language support: Feed content is language-aware via shop_*_mui tables. Greek/Greeklish transliterations are always generated regardless of the active language.
  6. Currency: Product prices are converted using the active currency rate (applyCurrencyRate()).
  7. Blog filtering: Only published blog posts with blog_date <= today are included.
  8. Vendor filtering: Only vendors with at least one active, priced product (HAVING items > 0) are included. Maximum discount across all vendor products is computed.