Setting up downloading certificates for 1C-Bitrix products

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
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • 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
    565
  • 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
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Certificate Download Setup for Products in 1C-Bitrix

Difference between displaying and downloading certificates — not only the "Download" button. Proper download setup means: access control, correct filenames, protection from direct URL enumeration, and if needed — download tracking.

Direct Links vs. Controlled Download

If files stored in /upload/ — direct links work, but anyone knowing URL downloads without auth. For public certificates OK. For internal docs — no.

Direct link (simplest):

$fileInfo = \CFile::GetFileArray($fileId);
echo '<a href="' . $fileInfo['SRC'] . '" download>';

Controlled delivery via handler (for access control or nice filenames):

Create /local/ajax/download-cert.php:

<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

$fileId = (int)$_GET['id'];
$productId = (int)$_GET['product_id'];

// Check file really tied to product (protection from enumeration)
$prop = \CIBlockElement::GetProperty(
    CATALOG_IBLOCK_ID,
    $productId,
    [],
    ['CODE' => 'CERTIFICATE']
);
$allowed = false;
while ($p = $prop->Fetch()) {
    if ((int)$p['VALUE'] === $fileId) {
        $allowed = true;
        break;
    }
}

if (!$allowed) {
    header('HTTP/1.0 403 Forbidden');
    die();
}

$fileInfo = \CFile::GetFileArray($fileId);
if (!$fileInfo) {
    header('HTTP/1.0 404 Not Found');
    die();
}

$filePath = $_SERVER['DOCUMENT_ROOT'] . $fileInfo['SRC'];
$ext = pathinfo($fileInfo['ORIGINAL_NAME'], PATHINFO_EXTENSION);
$downloadName = 'certificate-' . $productId . '.' . $ext;

header('Content-Type: ' . $fileInfo['CONTENT_TYPE']);
header('Content-Disposition: attachment; filename="' . $downloadName . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit();

Download link:

<a href="/local/ajax/download-cert.php?id=<?= $fileId ?>&product_id=<?= $productId ?>">
    Download certificate
</a>

Download Tracking

To know how many times certificate downloaded — log to separate table or via Bitrix events. Simplest — custom table or HL-infoblock CertDownloads with fields: UF_PRODUCT_ID, UF_FILE_ID, UF_USER_ID, UF_DATE, UF_IP.

Batch Download Multiple Certificates

If product has multiple certificates and want download all as ZIP — use PHP ZipArchive:

$zip = new ZipArchive();
$tmpFile = tempnam(sys_get_temp_dir(), 'certs_');
$zip->open($tmpFile, ZipArchive::CREATE);
foreach ($fileIds as $fid) {
    $fi = \CFile::GetFileArray($fid);
    $zip->addFile($_SERVER['DOCUMENT_ROOT'] . $fi['SRC'], $fi['ORIGINAL_NAME']);
}
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="certificates.zip"');
readfile($tmpFile);
unlink($tmpFile);

Protect Upload Folder

To fully block direct access to files — put them in /upload/protected/ and add to .htaccess:

Deny from all

Then all downloads via your handler, direct URLs don't work.

Stage Time
Setup direct download links 1–2 h
Develop controlled handler 3–5 h
Download tracking (optional) 2–3 h
Batch ZIP download (optional) 2–4 h