Developing a Content Personalization Module for 1C-Bitrix
Personalization is showing different content to different users depending on their characteristics. To a new visitor — explaining product value. To a returning non-buyer — emphasizing benefits. To a customer — cross-selling related to the last order. Without personalization, everyone sees the same landing page. Bitrix has no tools for this — only user groups with access restriction, which is a completely different thing.
Data Model
Module vendor.personalize:
-
b_vendor_ps_rule— personalization rules: id, name, priority, conditions (JSON), action (JSON), is_active, created_at -
b_vendor_ps_segment— audience segments: id, name, conditions (JSON), is_active -
b_vendor_ps_user_segment— user segment membership: user_id, segment_id, assigned_at -
b_vendor_ps_impression— impressions of personalized content: id, rule_id, user_id, session_id, element_id, created_at -
b_vendor_ps_profile— behavior profile: user_id or session_id, data (JSON: viewed sections, categories, source, RFM-metrics)
Segmentation
Segments are defined by a set of conditions. Example conditions:
{
"logic": "AND",
"rules": [
{"type": "visit_count", "operator": ">=", "value": 3},
{"type": "has_orders", "operator": "=", "value": false},
{"type": "last_visit_days", "operator": "<=", "value": 7},
{"type": "traffic_source", "operator": "=", "value": "google"}
]
}
This is the "warm users" segment — visited 3+ times in the last 7 days, came from search, haven't purchased yet.
Condition evaluation:
class SegmentEvaluator
{
public function evaluate(array $conditions, PersonalityProfile $profile): bool
{
$logic = $conditions['logic'] ?? 'AND';
$results = [];
foreach ($conditions['rules'] as $rule) {
$results[] = $this->evaluateRule($rule, $profile);
}
return $logic === 'AND'
? !in_array(false, $results)
: in_array(true, $results);
}
private function evaluateRule(array $rule, PersonalityProfile $profile): bool
{
return match ($rule['type']) {
'visit_count' => $this->compare($profile->getVisitCount(), $rule['operator'], $rule['value']),
'has_orders' => $profile->hasOrders() === (bool)$rule['value'],
'last_visit_days' => $this->compare($profile->getDaysSinceLastVisit(), $rule['operator'], $rule['value']),
'traffic_source' => $profile->getTrafficSource() === $rule['value'],
'city_id' => in_array($profile->getCityId(), (array)$rule['value']),
default => true,
};
}
}
Behavioral Profile
With each visit, the session/user profile is updated:
// Initialized in init.php via OnProlog event
PersonalityProfileCollector::collect([
'page' => $_SERVER['REQUEST_URI'],
'referrer' => $_SERVER['HTTP_REFERER'] ?? null,
'utm_source' => $_GET['utm_source'] ?? null,
'iblock_section' => $APPLICATION->GetCurDir(), // current section
]);
Profile is stored in b_vendor_ps_profile as JSON and contains: visit counter, viewed categories, first and last visit date, first visit source, order presence flag.
Personalized Blocks
Component vendor:personalize.block — replacement for static content block:
// In page template:
$APPLICATION->IncludeComponent('vendor:personalize.block', '', [
'ELEMENT_ID' => 'hero_cta', // block identifier on page
]);
The component finds an active personalization rule for the current user and renders appropriate content. Rule actions can be:
-
show_text— show text/HTML -
show_iblock_element— show specific infoblock element -
show_banner— show banner fromb_vendor_ps_banner -
redirect— redirect to URL
Segment Recalculation
Segment membership is not determined on every request — it's expensive. Agent recalculates segments at night:
// For each active segment — recalculate from b_vendor_ps_profile
// Result — INSERT/DELETE in b_vendor_ps_user_segment
SegmentCalculator::recalculate();
For new session users (unauthorized) — real-time evaluation based on session data.
Analytics
- CTR of personalized blocks vs standard (comparison with control group)
- User distribution across segments
- Conversion by segments to orders
Development Timeline
| Stage | Duration |
|---|---|
| ORM-tables, segment and rule model | 1 day |
| Behavioral profile, data collection | 2 days |
| Segmentation condition evaluation | 2 days |
| Segment recalculation agent | 1 day |
| Personalized blocks component | 2 days |
| Analytics and administrative interface | 2 days |
| Testing | 1 day |
Total: 11 working days. Integration with ML recommendation models (collaborative filtering) — separate project.







