Development of an educational platform (LMS) on 1C-Bitrix

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
    1177
  • 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 an Educational Platform (LMS) on 1C-Bitrix

1C-Bitrix is not the first choice for an LMS. There is Moodle, Teachable, GetCourse. But companies choose Bitrix when they already have a site on it, need unified authentication with a corporate portal or online store, and have no desire to maintain multiple systems. In that case Bitrix handles it well enough: info blocks for storing courses, user groups for access control, agents for progress, and the payment module for monetisation.

Data Storage Architecture

An LMS on Bitrix is built on a combination of info blocks and HighLoad blocks (HL blocks, tables via D7 ORM).

Info blocks:

Info block Code Purpose
Courses lms_courses Main course entities
Lessons lms_lessons Lessons within a course
Quizzes lms_quizzes Assignments and tests

HL blocks (via ORM):

// User progress
class UserProgressTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_hl_lms_user_progress'; }

    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new IntegerField('USER_ID'),
            new IntegerField('COURSE_ID'),     // Info block course element ID
            new IntegerField('LESSON_ID'),     // Info block lesson element ID
            new EnumField('STATUS', ['values' => ['NOT_STARTED', 'IN_PROGRESS', 'COMPLETED']]),
            new IntegerField('PROGRESS_PERCENT'), // 0–100
            new DatetimeField('STARTED_AT'),
            new DatetimeField('COMPLETED_AT'),
            new IntegerField('TIME_SPENT_SEC'),   // Time spent on the lesson
        ];
    }
}

// Course enrolment
class CourseEnrollmentTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_hl_lms_enrollment'; }

    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new IntegerField('USER_ID'),
            new IntegerField('COURSE_ID'),
            new EnumField('STATUS', ['values' => ['ACTIVE', 'COMPLETED', 'EXPIRED', 'REFUNDED']]),
            new DatetimeField('ENROLLED_AT'),
            new DatetimeField('EXPIRES_AT'),  // NULL = unlimited
            new FloatField('PAID_AMOUNT'),
            new IntegerField('ORDER_ID'),     // Link to sale module order
        ];
    }
}

Course and Lesson Structure

A course is an element of the lms_courses info block with properties:

Property Code Type
Category CATEGORY List
Duration DURATION_HOURS Number
Level LEVEL List (Beginner/Intermediate/Expert)
Access type ACCESS_TYPE List (Free/Paid/Subscription)
Access group ACCESS_GROUP_ID Number (b_group ID)
Certificate HAS_CERTIFICATE Flag

A lesson is an element of the lms_lessons info block, linked to a course via the COURSE_ID property. Lesson order — via SORT. Lesson content type (LESSON_TYPE): Video / Text / Quiz / Webinar.

Course Access

Access is controlled through Bitrix user groups. Each course has its own group (b_group). After payment or enrolment the user is added to the group:

class EnrollmentService
{
    public function enroll(int $userId, int $courseId): void
    {
        $course = \CIBlockElement::GetByID($courseId)->GetNext();
        $groupId = (int)$course['PROPERTY_ACCESS_GROUP_ID_VALUE'];

        if ($groupId) {
            $currentGroups = \CUser::GetUserGroup($userId);
            if (!in_array($groupId, $currentGroups)) {
                $currentGroups[] = $groupId;
                \CUser::SetUserGroup($userId, $currentGroups);
            }
        }

        CourseEnrollmentTable::add([
            'USER_ID'     => $userId,
            'COURSE_ID'   => $courseId,
            'STATUS'      => 'ACTIVE',
            'ENROLLED_AT' => new \Bitrix\Main\Type\DateTime(),
        ]);
    }
}

Access to course pages is restricted at the PHP level:

// At the start of the lesson page
$enrollment = CourseEnrollmentTable::getList([
    'filter' => [
        'USER_ID'   => $USER->GetID(),
        'COURSE_ID' => $courseId,
        'STATUS'    => 'ACTIVE',
    ],
])->fetch();

if (!$enrollment) {
    LocalRedirect('/courses/' . $courseSlug . '/buy/');
}

Progress Tracking

Progress is recorded under several conditions depending on the lesson type:

  • Video: tracking via the player's timeupdate event; marked as "watched" when 80% of the video has been completed.
  • Text: marked when the user scrolls to the bottom of the page (IntersectionObserver on the last paragraph).
  • Quiz: upon successful completion (score ≥ passing threshold).
// AJAX endpoint for recording progress
// POST /local/ajax/lms_progress.php
$lessonId = (int)$_POST['lesson_id'];
$courseId = (int)$_POST['course_id'];
$userId   = $USER->GetID();
$percent  = min(100, (int)$_POST['percent']);

$existing = UserProgressTable::getList([
    'filter' => ['USER_ID' => $userId, 'LESSON_ID' => $lessonId],
])->fetch();

if ($existing) {
    if ($percent > $existing['PROGRESS_PERCENT']) {
        UserProgressTable::update($existing['ID'], [
            'PROGRESS_PERCENT' => $percent,
            'STATUS'           => $percent >= 100 ? 'COMPLETED' : 'IN_PROGRESS',
            'COMPLETED_AT'     => $percent >= 100 ? new \Bitrix\Main\Type\DateTime() : null,
        ]);
    }
} else {
    UserProgressTable::add([
        'USER_ID'          => $userId,
        'COURSE_ID'        => $courseId,
        'LESSON_ID'        => $lessonId,
        'STATUS'           => $percent >= 100 ? 'COMPLETED' : 'IN_PROGRESS',
        'PROGRESS_PERCENT' => $percent,
        'STARTED_AT'       => new \Bitrix\Main\Type\DateTime(),
        'COMPLETED_AT'     => $percent >= 100 ? new \Bitrix\Main\Type\DateTime() : null,
    ]);
}

Testing and Quizzes

Quiz questions are elements of the lms_quizzes info block with properties: question type (single choice, multiple choice, text answer), answer options (JSON in a string property), correct answer, and question weight.

Quiz results — HL block b_hl_lms_quiz_attempt:

CREATE TABLE b_hl_lms_quiz_attempt (
    ID           SERIAL PRIMARY KEY,
    USER_ID      INTEGER,
    QUIZ_ID      INTEGER,
    SCORE        INTEGER,       -- points scored
    MAX_SCORE    INTEGER,       -- maximum points
    PASSED       BOOLEAN,
    ANSWERS_JSON TEXT,          -- JSON with user answers
    CREATED_AT   TIMESTAMP
);

Certificates

Upon course completion (all lessons COMPLETED) — generate a PDF certificate using the FPDF or TCPDF library:

class CertificateGenerator
{
    public function generate(int $userId, int $courseId): string
    {
        $user   = \CUser::GetByID($userId)->Fetch();
        $course = \CIBlockElement::GetByID($courseId)->GetNext();

        $pdf = new \TCPDF();
        $pdf->AddPage('L'); // Landscape
        $pdf->setImageScale(1.25);

        // Certificate background image
        $pdf->Image('/local/templates/lms/img/certificate_bg.jpg', 0, 0, 297, 210);

        // Full name
        $pdf->SetFont('dejavusans', 'B', 32);
        $pdf->SetXY(50, 90);
        $pdf->Cell(200, 20, $user['LAST_NAME'] . ' ' . $user['NAME'], 0, 0, 'C');

        // Course name
        $pdf->SetFont('dejavusans', '', 18);
        $pdf->SetXY(50, 120);
        $pdf->Cell(200, 10, $course['NAME'], 0, 0, 'C');

        $filename = 'certificate_' . $userId . '_' . $courseId . '.pdf';
        $path     = '/upload/certificates/' . $filename;
        $pdf->Output($_SERVER['DOCUMENT_ROOT'] . $path, 'F');

        return $path;
    }
}

Development Timelines

Option Composition Duration
Basic LMS Courses, lessons, progress, group access 15–20 days
With quizzes and certificates + Quizzes, PDF generation 20–30 days
Full platform + Payment, subscriptions, analytics, webinars 35–50 days