Development of a 1C-Bitrix banner management module

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.