STORAGE: העלאה ואחסון קבצים

איך STORAGE שומר קבצים: טבלת storage, נתיבים ושמות קבצים (uuid או id), אפשרויות add, אחסון מקומי, FTP ו-S3, קריאת כתובות, ומחיקה.

⏱ 7 דק' קריאה 1328 מילים ערוך דף זה ב-GitHub

STORAGE היא המחלקה שמקבלת קובץ (מ-$_FILES, מנתיב בדיסק, מכתובת URL או מ-base64), שומרת אותו במקום הנכון, רושמת אותו בטבלת CRM_storage ומחזירה מזהה מספרי. מכאן והלאה כל מקום באתר מחזיק את המזהה ומבקש ממנה כתובת או תמונה מוקטנת. בעמוד הזה: איך קובץ נשמר, מה האפשרויות של add(), איך קוראים את הקובץ בחזרה, ואיך מוחקים. הגנות ההעלאה מתוארות באבטחת העלאות, יצירת גדלים בכלי תמונה.

system/ בעמוד הזה הוא תיקיית הליבה הפרוסה (api/core בריפו), ו-{admin} הוא CONFIG::$admin_url. המחלקה נמצאת ב-system/collections/STORAGE.php; הפירוט המלא של כל המתודות נמצא גם ברפרנס STORAGE.

מודל הנתונים#

כל קובץ הוא שורה בטבלה CRM_storage (בבילדר: storage) וקובץ פיזי אחד, ועוד קבצי נגזרת (תמונות מוקטנות) לצידו.

עמודהמשמעות
idהמזהה שמחזירה add(). זה מה ששדות אחרים (תמונה ראשית, גלריה) שומרים
uuidמזהה אקראי, כשסוג השם הוא uuid
nameהשם המקורי של הקובץ, בלי סיומת. לתצוגה בלבד
extהסיומת (אותיות קטנות)
pathהתיקייה (יחסית לשורש האתר), עם שם ה-bucket לפניה ב-S3
remoteמיקום האחסון: ""/מקומי, ftp או s3
data, copyrights, descriptionמטא-דאטה חופשית
object_type, object_id, ordקישור הקובץ לישות (למשל גלריה של כתבה) ומיון
folder_idהתיקייה הווירטואלית במנהל המדיה
filename_typeuuid או id: איך נקרא הקובץ הפיזי
tumbרשימה (בפורמט pipe) של הנגזרות שכבר נוצרו
file_hashmd5 של התוכן, כשהעמודה קיימת (מזהה קבצים זהים)
uploaded_byמזהה המנהל שהעלה, כשההעלאה הייתה מהפאנל

מה נשמר איפה#

הקובץ הפיזי נשמר בשם <uuid>.<ext> (ברירת המחדל) או <id>.<ext>, בתיקייה path. שם המקור של המשתמש לא משמש בנתיב: הוא נשמר רק בעמודה name. זה מונע התנגשויות וגם מעקר ניסיונות להשתמש בשם הקובץ כדי לכתוב למקום אחר.

media/Storage/3f2c9b1e-....jpg                 הקובץ
media/Storage/3f2c9b1e-...._tumb_400Xauto.jpg   נגזרת
media/Storage/3f2c9b1e-...._wtm_20X-20.jpg      סימן מים

סוג השם נקבע גלובלית ב-init.php:

STORAGE::set_filename_type("uuid");   // או "id"

תיקיית היעד ברירת מחדל: media/Storage. תיקיות חסרות נוצרות אוטומטית על ידי FILES::make_dir() במצב 0777, ולכן מכווצים אותן אחרי ההתקנה (התקשחות).

STORAGE::add()#

public static function add($file_path, $opts = array())
פרמטרמשמעות
$file_pathערך מ-$_FILES (מערך עם tmp_name), נתיב בדיסק, או כתובת שמתחילה ב-http (נשלפת ב-cURL)
$optsמערך אפשרויות, בטבלה למטה

ערך החזרה: עם save => true (ברירת המחדל): id של השורה החדשה. עם save => false: שם הקובץ שהוחלף. במקרה כשל: false. סיומת אסורה מסתיימת ב-die("STORAGE SECURITY ERROR!").

האפשרויות#

אפשרותברירת מחדלמשמעות
savetruefalse = מחליף תוכן של קובץ קיים באותו שם ובאותה כתובת (ללא שורה חדשה)
clearfalseמחק את הקובץ המקור אחרי ההעתקה (מופעל אוטומטית לקבצים שהורדו מ-URL)
pathmedia/Storageתיקיית היעד, יחסית לשורש האתר או נתיב מוחלט
remoteCONFIG::$storage_remote"", ftp, s3
nameשם המקור בלי סיומתהערך לעמודה name
object_type, object_id, ordריקקישור לישות (נשמר רק כשהראשון והשני קיימים; object_id מומר ל-int)
data, copyrights, descriptionריקמטא-דאטה
folder_id0תיקיית מדיה
filename_typeSTORAGE::$filename_typeuuid או id לקובץ הזה
img_convertריקהמרת פורמט: jpg, png, gif, webp
img_max_width, img_max_heightריקהקטנה פרופורציונלית עד המידות (לעולם לא מגדילה)
img_width, img_heightריקגודל מדויק, עם חיתוך

דוגמאות#

העלאה מטופס, עם הקטנה והמרה ל-webp וקישור לכתבה:

// application/controllers/news_upload.php (מקור: טופס multipart עם שדה "photo")
if (!ADMIN::is_admin()) return "ERROR forbidden";     // אימות הוא אחריותכם

$id = STORAGE::add($_FILES["photo"], [
    "object_type"    => "news",
    "object_id"      => (int)$_GET["news_id"],
    "img_convert"    => "webp",
    "img_max_width"  => 1600,
    "img_max_height" => 1600,
]);

if ($id === false) return "ERROR upload failed";

DB::update("news")->set_var("image", $id)->update((int)$_GET["news_id"]);

ייבוא מכתובת חיצונית או מקובץ בדיסק:

$id = STORAGE::add("https://example.org/pic.jpg", ["path" => "media/Imported"]);
$id = STORAGE::add(CONFIG::$base_path . "/import/logo.png");
ייבוא מכתובת הוא בקשה יוצאת מהשרת

כשמעבירים כתובת http..., STORAGE מורידה אותה בעצמה ב-cURL ועוקבת אחרי הפניות, ללא הגבלת דומיין. בליבה רק קוד של אדמינים קורא לזה. אל תעבירו לפונקציה כתובת שהמשתמש הקליד בלי אימות של הדומיין ושל הפרוטוקול: זו דלת לבקשות פנימיות (SSRF).

מ-base64 (למשל חתימה מ-canvas או צילום מהמצלמה):

public static function addFromBase64($base64, $name, $ext, $path, $remote = "local", $extra_settings = [])
$id = STORAGE::addFromBase64($_POST["signature"], "signature", "png", "media/Signatures");

אם הערך מתחיל ב-data:image/...;base64,, הסיומת נגזרת ממנו כשלא סופקה. הפונקציה מחזירה false כשהמידע ריק או פגום. אחרת מעבירה את הקובץ ל-add() עם $extra_settings.

יעדי אחסון#

remoteמה קורההגדרות ב-CONFIG_USER
ריק (מקומי)copy() אל CONFIG::$base_path/<path>/<file>אין
ftpftp_put לשרת FTP, יוצר תיקיות לפי הצורך. אם ההעלאה נכשלת, השורה שנרשמה נמחקת$storage_remote = "ftp", $storage_remote_url, $storage_remote_ftp_host, $storage_remote_ftp_username, $storage_remote_ftp_password
s3putObject ל-bucket עם ACL public-read$storage_remote = "s3" ומפתחות ב-MISC::$GLOBALS (Storage_remote_s3_key, Storage_remote_s3_secret, Storage_remote_s3_bucket)

כתובת הקובץ נבנית לפי היעד: אתר מקומי CONFIG::$site_url . path/file, ב-FTP storage_remote_url, וב-S3 https://s3.amazonaws.com/<bucket>/....

התנהגות S3

בענף ה-S3 של add() אין break אחרי ההעלאה, כך שהקוד ממשיך אל ענף ההעתקה המקומי ויוצר גם עותק מקומי. אם אתם עובדים עם S3, בדקו את התיקייה המקומית ונקו עותקים מיותרים, ואל תניחו שהדיסק המקומי ריק.

קריאת קבצים#

STORAGE::get($id)                          // השורה, עם url, url_rel ו-filename מחושבים
STORAGE::get_url($id)                      // כתובת מלאה, או "" אם אין
STORAGE::get_thumb($id, 400, 300)          // כתובת של תמונה מוקטנת (נוצרת בפעם הראשונה)
STORAGE::get_all("news", $newsId)          // כל הקבצים של ישות, ממוינים לפי ord
STORAGE::fileExists($id)                   // האם הקובץ הפיזי קיים (ביעד המתאים)

get($id) שומרת תוצאה בזיכרון לאורך הבקשה. אם שיניתם שורה בעצמכם בתוך אותה בקשה, קראו ל-STORAGE::forget($id) כדי לקרוא מחדש.

מתודהחתימההערה
getget($id)מקבל מזהה או שורה קיימת (מערך). מחזיר false אם אין
get_allget_all($object_type, $object_id, $ord = "ord ASC")כל שורה עוברת get()
get_urlget_url($id)"" כשאין קובץ
get_file_identifierget_file_identifier($data)uuid או id, לפי filename_type של השורה
get_by_hashget_by_hash($hash, $exclude_id = 0)מציאת קובץ זהה לפי md5
get_local_fileget_local_file($id, $name = false)נתיב מקומי; קובץ מרוחק מועתק לתיקיית ה-temp
fileExistsfileExists($id)בדיקה פיזית בדיסק, ב-FTP (בקשת HTTP) או ב-S3

בתבנית Smarty מעבירים מהקונטרולר את הכתובת המוכנה:

$this->view("news/item", [
    "title" => $row["title"],
    "image" => STORAGE::get_thumb($row["image"], 800, 450),
]);
<img src="{$image|escape}" alt="{$title|escape}" width="800" height="450">

מחיקה#

public static function remove($id, $only_files = false)

מוחקת את השורה (אלא אם $only_files הוא true), את הקובץ הפיזי ואת הנגזרות שלו. במקומי נמחקים הקובץ וכל <identifier>_tumb_*; ב-FTP נמחקים _tumb_ ו-_wtm_; ב-S3 נמחקים הקובץ ו-_tumb_. לא מחזירה ערך.

מחיקה לא בודקת הפניות
remove() לא יודעת מי עוד מחזיק את המזהה. אם שדה בכתבה עדיין מצביע על הקובץ, הוא יצביע על שורה שלא קיימת. מחקו או נקו את ההפניה לפני, או אחרי, באותה פעולה. וכמו כל מחיקה, היא צריכה הרשאה: אל תחשפו אותה ישירות לקלט משתמש.

מה לא לעשות#

  • אל תכתבו קבצי העלאה ישירות עם move_uploaded_file או copy() מהקוד שלכם: אתם מדלגים על חסימת הסיומות, על רישום הטבלה ועל ה-CDN. השתמשו ב-STORAGE::add().
  • אל תבנו נתיב מקובץ מ-$_GET או $_POST (למשל STORAGE::$temp_folder . "/" . $_POST["x"]) בלי basename() ובדיקה, כדי לא לפתוח מעבר לתיקייה.
  • אל תשרשרו נתונים שבאו מהמשתמש (למשל נתוני חיתוך) לשאילתת SQL גולמית סביב STORAGE. save_thumb_db($sid, $ratio, $cropper_data) משרשרת את $cropper_data ל-SQL כמו שהוא, ולכן מעבירים לה רק ערך שאתם יצרתם.

ראו גם#

מצאתם טעות או חוסר? תקנו את הדף או פתחו Issue בריפו. התיעוד נכתב מתוך הקוד של ליבה 5.0.115.