Developing a Banner Management Module for 1C-Bitrix
Banners in Bitrix are traditionally stored in an infoblock: image, link, display dates. For three banners on the homepage — fine. When there are dozens of banners displayed in different site zones, you need rotation, impression limits, CTR statistics, and audience targeting — the infoblock can't handle the task. A specialized module provides a full advertising system within the site.
Data Model
Module vendor.banners:
-
b_vendor_banner— banners: id, name, zone_id, image_id, image_mobile_id, url, target, alt, title, html_code (for HTML-banners), date_from, date_to, priority, weight (for rotation), impressions_limit, impressions_count, clicks_count, status (active/inactive/draft), targeting (JSON), created_at -
b_vendor_banner_zone— placement zones: id, code, name, description, width, height, max_banners, is_active -
b_vendor_banner_stat— statistics (daily snapshots): banner_id, date, impressions, clicks, ctr -
b_vendor_banner_impression— raw impression data (optional, for detailed analytics): id, banner_id, user_id, session_id, ip, user_agent, created_at
Placement Zones
Zones are named locations on the site: header_top, sidebar_right, content_after_article, footer_banner. In the template, we include the component:
$APPLICATION->IncludeComponent('vendor:banners.zone', '', [
'ZONE_CODE' => 'header_top',
'MAX_BANNERS' => 1, // how many banners to display simultaneously
]);
The component selects active banners for the zone, applies targeting and rotation, renders HTML.
Banner Selection Algorithm
class BannerSelector
{
public function select(string $zoneCode, int $maxBanners): array
{
$now = new DateTime();
$zone = ZoneTable::getByCode($zoneCode);
// Get candidates: active, within dates, limit not exhausted, matching targeting
$candidates = BannerTable::getList([
'filter' => [
'ZONE_ID' => $zone['ID'],
'STATUS' => 'active',
'<=DATE_FROM' => $now,
['LOGIC' => 'OR', '>=DATE_TO' => $now, '=DATE_TO' => false],
['LOGIC' => 'OR', '=IMPRESSIONS_LIMIT' => 0, '>IMPRESSIONS_LIMIT' => new SqlExpression('IMPRESSIONS_COUNT')],
],
])->fetchAll();
// Targeting filter
$candidates = array_filter($candidates, fn($b) => $this->matchesTargeting($b));
// Weighted random sampling
return $this->weightedRandom($candidates, $maxBanners);
}
private function weightedRandom(array $items, int $count): array
{
// Implementation of weighted reservoir sampling
$totalWeight = array_sum(array_column($items, 'WEIGHT'));
// ...
}
}
Targeting
Targeting is stored in JSON field targeting:
{
"geo": {"city_ids": [1, 15, 42]},
"user_groups": [3, 9],
"device": "mobile",
"pages": ["/catalog/", "/sale/"],
"new_visitors_only": true
}
Checked through TargetingChecker — similar to segment evaluation from personalization module. Integration with geolocation module for geo targeting.
Impression and Click Counting
Impressions — incremented when banner is rendered:
// At the end of component work
BannerTable::update($bannerId, [
'IMPRESSIONS_COUNT' => new SqlExpression('IMPRESSIONS_COUNT + 1'),
]);
StatTable::increment($bannerId, 'impressions');
Clicks — via redirect proxy:
GET /bitrix/components/vendor/banners.click/?id=42&token=abc&url=https://example.com/product/
Server records click, validates token (protection from fraud), redirects user.
// Fraud protection: one click per IP per banner per day
$alreadyClicked = BannerClickCacheTable::check($bannerId, $_SERVER['REMOTE_ADDR']);
if (!$alreadyClicked) {
BannerTable::update($bannerId, ['CLICKS_COUNT' => new SqlExpression('CLICKS_COUNT + 1')]);
StatTable::increment($bannerId, 'clicks');
BannerClickCacheTable::set($bannerId, $_SERVER['REMOTE_ADDR']);
}
HTML Banners
For Rich Media and animated banners, HTML format is supported. Field html_code contains arbitrary HTML (CSS animation, embedded video). Edited in the administrative interface via TinyMCE with restriction of dangerous tags.
Administrative Interface
- Banner list with preview, status, CTR
- Create/edit form: image upload, targeting configuration
- Zone management
- Statistics: graph of impressions/clicks, CTR, comparison with previous period
- Quick activate/deactivate
Development Timeline
| Stage | Duration |
|---|---|
| ORM-tables, zone and banner model | 1 day |
| Selection algorithm, weighted rotation | 1 day |
| Targeting, geolocation integration | 1 day |
| Impression and click counting, fraud protection | 1 day |
| HTML-banners, editor | 1 day |
| Statistics, CTR graphs | 1 day |
| Administrative interface | 2 days |
| Testing | 1 day |
Total: 9 working days. Synchronization with external advertising networks (Google DFP, Yandex) — separate task.







