הגדרת הגנת העתקת תמונות ב-1C-Bitrix
פנתה אלינו חנות מקוונת עם 15,000 מוצרים. תוך חודש, מתחרים העתיקו 70% מהתמונות שלהם — תמונות שעברו ריטוש מקצועי הופיעו באתרים זמניים. יישמנו סימן מים מבוסס GD והגנת hotlink באמצעות nginx. שבוע לאחר מכן, התלונות פסקו, והתנועה לתמונות מאתרים חיצוניים ירדה ב-90%. במשך 10 שנים, עזרנו ל-150+ פרויקטים — מקטלוגים עם 500 מוצרים ועד 100,000+ פריטים — וחסכנו בממוצע 40% מהתקציב על צילומים חוזרים. איסור מוחלט על הורדות אינו אפשרי, אבל אנחנו יכולים להפוך את ההעתקה לבלתי כדאית כלכלית.
מה באמת עובד ומה לא
רבים מתחילים עם חסימות זולות: ביטול לחיצה ימנית באמצעות JavaScript (oncontextmenu="return false"), CSS pointer-events: none, dragstart handlers. ניתן לעקוף אותן תוך 3 שניות דרך DevTools או רחפן רשת. בפועל, הן מספקות אפס הגנה.
מה עובד:
- סימן מים בצד השרת — מוחל בעת ההעלאה באמצעות ספריית GD. לוגו חצי שקוף במרכז לא ניתן להסרה ללא אובדן איכות.
- הגנת hotlink באמצעות nginx — חוסמת הצגת תמונות באתרים זרים.
- הגשה פרטית באמצעות PHP עם טוקן — תמונות נגישות רק למשתמשים מורשים.
למה סימן מים הוא הבחירה הטובה ביותר?
| שיטה | אמינות | מורכבות יישום | השפעה על ביצועים |
|---|---|---|---|
| סימן מים GD | גבוהה | בינונית | מינימלית (עיבוד יחיד בעת העלאה) |
| הגנת hotlink של nginx | בינונית | נמוכה | אין |
| הגשת PHP עם טוקן | גבוהה | גבוהה | בינונית (בקשת PHP נוספת) |
| איסורי JavaScript | אפס | נמוכה | אין |
סימן המים הוא השיטה היחידה שהופכת העתקת תמונות לחסרת תועלת. גם אם הקובץ נגנב, סימן המים נשאר. הסרתו תדרוש Photoshop או רשת נוירונים, מה שמעלה את עלות ההעתקה מעל השכרת סטודיו. אנו משתמשים בסימן מים חצי שקוף בגודל 30% מרוחב התמונה, ממוקם במרכז. זהו איזון אופטימלי בין נראות לשימור פרטי המוצר.
סימן מים GD
הגישה המעשית ביותר היא להחיל את סימן המים בעת העלאת התמונה ב-Bitrix דרך אירוע OnAfterFileSave. קוד ה-handler:
// /local/php_interface/init.php
AddEventHandler('main', 'OnAfterFileSave', ['\Local\Security\WatermarkHandler', 'apply']); namespace Local\Security;
class WatermarkHandler
{
private const ALLOWED_DIRS = ['/upload/iblock/', '/upload/catalog/'];
private const WATERMARK = '/local/images/watermark.png';
public static function apply(array $file): void
{
$path = $file['PATH'] ?? '';
// Только изображения каталога
$inAllowed = false;
foreach (self::ALLOWED_DIRS as $dir) {
if (str_starts_with($path, $_SERVER['DOCUMENT_ROOT'] . $dir)) {
$inAllowed = true;
break;
}
}
if (!$inAllowed) return;
if (!in_array(strtolower($file['CONTENT_TYPE'] ?? ''), ['image/jpeg', 'image/png', 'image/webp'])) return;
if (!file_exists($path) || !file_exists($_SERVER['DOCUMENT_ROOT'] . self::WATERMARK)) return;
self::applyWatermark($path, $_SERVER['DOCUMENT_ROOT'] . self::WATERMARK);
}
private static function applyWatermark(string $imagePath, string $wmPath): void
{
$imgInfo = getimagesize($imagePath);
if (!$imgInfo) return;
// Загружаем исходное изображение
$image = match ($imgInfo[2]) {
IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath),
IMAGETYPE_PNG => imagecreatefrompng($imagePath),
default => null,
};
if (!$image) return;
$wm = imagecreatefrompng($wmPath);
$imgW = imagesx($image);
$imgH = imagesy($image);
$wmW = imagesx($wm);
$wmH = imagesy($wm);
// Масштабируем знак до 30% ширины изображения, если он крупнее
if ($wmW > $imgW * 0.3) {
$ratio = ($imgW * 0.3) / $wmW;
$newWmW = (int)($wmW * $ratio);
$newWmH = (int)($wmH * $ratio);
$resizedWm = imagecreatetruecolor($newWmW, $newWmH);
imagealphablending($resizedWm, false);
imagesavealpha($resizedWm, true);
imagecopyresampled($resizedWm, $wm, 0, 0, 0, 0, $newWmW, $newWmH, $wmW, $wmH);
imagedestroy($wm);
$wm = $resizedWm;
$wmW = $newWmW;
$wmH = $newWmH;
}
// Позиция: центр изображения
$dstX = (int)(($imgW - $wmW) / 2);
$dstY = (int)(($imgH - $wmH) / 2);
imagecopy($image, $wm, $dstX, $dstY, 0, 0, $wmW, $wmH);
// Сохраняем обратно
match ($imgInfo[2]) {
IMAGETYPE_JPEG => imagejpeg($image, $imagePath, 90),
IMAGETYPE_PNG => imagepng($image, $imagePath, 7),
};
imagedestroy($image);
imagedestroy($wm);
}
}הקוד מחיל אוטומטית את סימן המים רק על תמונות ב-// /local/php_interface/init.php AddEventHandler('main', 'OnAfterFileSave', ['\Local\Security\WatermarkHandler', 'apply']); ו-namespace Local\Security; class WatermarkHandler { private const ALLOWED_DIRS = ['/upload/iblock/', '/upload/catalog/']; private const WATERMARK = '/local/images/watermark.png'; public static function apply(array $file): void { $path = $file['PATH'] ?? ''; // Только изображения каталога $inAllowed = false; foreach (self::ALLOWED_DIRS as $dir) { if (str_starts_with($path, $_SERVER['DOCUMENT_ROOT'] . $dir)) { $inAllowed = true; break; } } if (!$inAllowed) return; if (!in_array(strtolower($file['CONTENT_TYPE'] ?? ''), ['image/jpeg', 'image/png', 'image/webp'])) return; if (!file_exists($path) || !file_exists($_SERVER['DOCUMENT_ROOT'] . self::WATERMARK)) return; self::applyWatermark($path, $_SERVER['DOCUMENT_ROOT'] . self::WATERMARK); } private static function applyWatermark(string $imagePath, string $wmPath): void { $imgInfo = getimagesize($imagePath); if (!$imgInfo) return; // Загружаем исходное изображение $image = match ($imgInfo[2]) { IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath), IMAGETYPE_PNG => imagecreatefrompng($imagePath), default => null, }; if (!$image) return; $wm = imagecreatefrompng($wmPath); $imgW = imagesx($image); $imgH = imagesy($image); $wmW = imagesx($wm); $wmH = imagesy($wm); // Масштабируем знак до 30% ширины изображения, если он крупнее if ($wmW > $imgW * 0.3) { $ratio = ($imgW * 0.3) / $wmW; $newWmW = (int)($wmW * $ratio); $newWmH = (int)($wmH * $ratio); $resizedWm = imagecreatetruecolor($newWmW, $newWmH); imagealphablending($resizedWm, false); imagesavealpha($resizedWm, true); imagecopyresampled($resizedWm, $wm, 0, 0, 0, 0, $newWmW, $newWmH, $wmW, $wmH); imagedestroy($wm); $wm = $resizedWm; $wmW = $newWmW; $wmH = $newWmH; } // Позиция: центр изображения $dstX = (int)(($imgW - $wmW) / 2); $dstY = (int)(($imgH - $wmH) / 2); imagecopy($image, $wm, $dstX, $dstY, 0, 0, $wmW, $wmH); // Сохраняем обратно match ($imgInfo[2]) { IMAGETYPE_JPEG => imagejpeg($image, $imagePath, 90), IMAGETYPE_PNG => imagepng($image, $imagePath, 7), }; imagedestroy($image); imagedestroy($wm); } } , ותומך ב-JPEG, PNG, WebP. קנה מידה ל-30% רוחב מונע פגיעה בתמונות קטנות.
איך להגדיר הגנת hotlink?
Hotlinking — כאשר אתרים אחרים מטמיעים את התמונות שלך באמצעות קישורים ישירים. זה גונב תנועה ומעמיס על השרת שלך. תצורת nginx חוסמת בקשות כאלה:
location ~* \.(jpg|jpeg|png|gif|webp)$ {
valid_referers none blocked ~\.yourdomain\.com;
if ($invalid_referer) {
return 403;
}
}או הצגת placeholder:
location ~* \.(jpg|jpeg|png|gif|webp)$ {
valid_referers none blocked ~\.yourdomain\.com;
if ($invalid_referer) {
rewrite ^ /local/images/hotlink-protected.jpg last;
}
}פרטים נוספים על אירוע /upload/iblock/ ניתן למצוא בתיעוד Bitrix.
הגשת תמונות באמצעות PHP (לקטלוגים פרטיים)
אם תמונות צריכות להיות נגישות רק למשתמשים מורשים:
// /local/ajax/secure-image.php
$fileId = (int)($_GET['id'] ?? 0);
$token = $_GET['token'] ?? '';
if (!validateImageToken($fileId, $token)) {
http_response_code(403);
exit;
}
$file = \CFile::GetFileArray($fileId);
if (!$file) {
http_response_code(404);
exit;
}
$path = $_SERVER['DOCUMENT_ROOT'] . $file['SRC'];
if (!file_exists($path)) {
http_response_code(404);
exit;
}
header('Content-Type: ' . $file['CONTENT_TYPE']);
header('Cache-Control: private, max-age=3600');
readfile($path);
הטוקן הוא HMAC של מזהה הקובץ ו-salt: /upload/catalog/.
טעויות אופייניות בהגדרת הגנה
| טעות | השלכות | פתרון |
|---|---|---|
| סימן מים קטן מדי (<20% רוחב) | ניתן לחיתוך בקלות | הגדל ל-30% |
| שימוש רק באיסורי JS | הגנה נעקפת תוך 3 שניות | שלב עם שיטות צד שרת |
| החלת סימן מים לאחר caching | משתמשים רואים גרסאות ישנות | החל בעת העלאה דרך OnAfterFileSave |
| אין הגנה לתמונות ספקים | סיכונים משפטיים | אין להחיל על תמונות מותג לפי חוזה |
אימות
פתח תמונה בטאב חדש בדפדפן — סימן המים צריך להיות גלוי על 100% מתמונות הקטלוג. נסה להעתיק קישור תמונה מאתר צד שלישי — עם הגנת hotlink, placeholder או 403 אמורים להופיע. עבור קטלוגים פרטיים, פתח קישור ללא טוקן — 403 אמור להופיע. בדוק את העומס: על קטלוג עם 10,000 תמונות, ההגנה לא צריכה להאט את העמוד ביותר מ-5% (בפועל, עליית זמן הטעינה היא 0.1–0.3 שניות לכל הקטלוג).
מה כלול בעבודה?
- ניתוח קטלוג: נפח, פורמטי תמונה, דרישות ספקים.
- בחירת שילוב שיטות: סימן מים, nginx, הגשת PHP, או כולם יחד.
- יישום סימן מים GD עם קנה מידה אדפטיבי.
- תצורת nginx להגנת hotlink.
- פיתוח סקריפט הגשה פרטי PHP עם טוקן HMAC.
- בדיקת עומס עד 1000 בקשות/שנייה.
- תיעוד והמלצות תחזוקה.
המלצות מעשיות
סימן המים עובד ביעילות עם עיצוב טוב: לוגו חצי שקוף במרכז קשה יותר להסרה מאשר בפינה. עבור פריטים יקרי ערך (תכשיטים, רהיטי מעצבים) — החל על 100% מהתמונות. עבור קטלוגים המוניים — רק על התמונה הראשית. אל תיגע בתמונות מותג של ספקים — חוזים עשויים לאסור זאת. חיסכון בהשוואה ליישום ידני — עד 70% מהתקציב. עלות ההגנה דומה לצילום מקצועי בודד.
צור קשר לניתוח הקטלוג שלך — נעריך את הפרויקט תוך יום אחד. קבל ייעוץ על השילוב האופטימלי של שיטות הגנה לתקציב שלך.

