FILES (system/collections/FILES.php) היא מחלקה סטטית קטנה עם שלושה תפקידים: רינדור תבניות (html_template, string_template), טעינת נכסים (scss, vue, include_dir) ועזרי מערכת קבצים (make_dir, remove_dir, is_img, get_extension). רוב האתרים פוגשים אותה בעקיפין: $this->view() בקונטרולר וה-theme קוראים ל-html_template, ו-PAGE קורא ל-scss ול-include_dir.
system/ בדף הזה הוא תיקיית הליבה הפרוסה (api/core בריפו). החתימות מועתקות מהקוד.
תבניות#
FILES::html_template()#
public static function html_template($tmpl_file, $varsArr = array(), $settings = array())
קוראת קובץ תבנית, מציבה בו משתנים, ומחזירה את ה-HTML כמחרוזת. זו הפונקציה שמאחורי $this->view("x", [...]) (קורא ל-html_template(CONFIG::$base_path . "/application/views/x.tpl", $vars)), $this->system_view() (תיקיית views של הליבה) ושל ה-theme (themes/<name>/index.tpl, ובמקרה של admin_login / admin_panel מתיקיית הליבה).
| פרמטר | משמעות |
|---|---|
$tmpl_file | נתיב לקובץ. יחסי ל-CWD של התהליך (שורש האתר בבקשת web) או מוחלט. נתיב שמתחיל ב-assets/ מתורגם ל-system/assets/ |
$varsArr | מערך משתנים לתבנית (["title" => "..."]) |
$settings["cache"] | false (ברירת מחדל). true מפעיל caching של Smarty ל-120 שניות. תקף רק במצב Smarty |
$settings["use_smarty"] | null (ברירת מחדל) ⇒ משתמשים ב-CONFIG::$use_smarty. ערך אחר דורס את ההגדרה הגלובלית לקריאה הזו |
שלושה מצבי רינדור, לפי הערך האפקטיבי של use_smarty:
| ערך | מצב | מה קורה |
|---|---|---|
true | Smarty | Smarty::fetch($tmpl_file). מוצבים GLOBALS (= MISC::$GLOBALS) וכל מפתחות $varsArr |
"plain" | קובץ גולמי | מחזירה את תוכן הקובץ כמות שהוא, בלי עיבוד ובלי משתנים. התוכן נשמר ב-$GLOBALS["imp_<נתיב>"] לשארית הבקשה |
כל ערך אחר (false) | eval | ראו אזהרה למטה |
// בתוך קונטרולר: application/views/news/item.tpl
return FILES::html_template(
CONFIG::$base_path . "/application/views/news/item.tpl",
["row" => $row, "title" => $row["title"]],
["cache" => false]
);
// זהה ל: return $this->view("news/item", ["row" => $row, "title" => $row["title"]]);
use_smarty שאינו true או "plain")
במצב הזה התבנית נעטפת במחרוזת PHP עם addslashes ומורצת ב-eval: כל $var, {$expr} או ${...} שבתוכן הקובץ מפוענח כ-PHP. מקבלים גם אובייקט $savedWords (מילים של LANGS) ומשתנים בשם המפתחות של $varsArr. המצב הזה שמור לתאימות לאחור: אל תפעילו אותו באתר חדש, והשאירו CONFIG_USER::$use_smarty = true.הנכס נקרא ללא isset. אתר שלא הגדיר אותו ב-CONFIG_USER יקבל שגיאת undeclared static property בכל רינדור תבנית. ראו CONFIG_USER.
FILES::string_template()#
public static function string_template($tmpl_string, $varsArr = array(), $settings = array())
מרנדרת מחרוזת (לא קובץ) כתבנית Smarty, דרך המשאב string:. תמיד Smarty, בלי קשר ל-CONFIG::$use_smarty. $settings מכיל רק cache (כמו ב-html_template). משמשת את mail_tpl לרינדור נושא, שם שולח וגוף של תבניות מייל שמאוחסנות ב-DB.
$html = FILES::string_template('שלום {$name}, ההזמנה {$order.id} התקבלה', [
"name" => "דנה",
"order" => ["id" => 1042],
]);
ל-Smarty של הליבה אין enableSecurity(), וההרחבה המותאמת מחזירה כל פונקציית PHP קיימת כ-modifier. לכן מי שיכול לשלוט בטקסט של התבנית (לדוגמה, עורך תבניות מייל בפאנל) יכול להפעיל פונקציות PHP בשרת. העבירו ל-string_template רק טקסט שנכתב על ידי מנהלים מהימנים, ולעולם לא קלט של גולש. משתנים שהגולש שולט בהם מועברים דרך $varsArr (ערך המשתנה אינו מפוענח כתבנית).
הרחבות Smarty (מה זמין בתבנית)#
מופע ה-Smarty נוצר מחדש בכל קריאה (create_smarty היא private), עם התצורה הבאה:
| הגדרה | ערך |
|---|---|
| תיקיית תבניות | ./ (ה-CWD) |
| תיקיית קומפילציה | cache/Smarty/templates_c/ (יחסית ל-CWD, עם use_sub_dirs) |
| תיקיית cache | cache/Smarty/cache/ |
GLOBALS | MISC::$GLOBALS מוצב אוטומטית בכל תבנית |
חיפוש פונקציות, modifiers ובלוקים:
- פונקציות (
{crm_storage_url ...}): מתוך תוסף שנטען מ-system/addons/Smarty/libs/plugins(קבציםfunction.<name>.phpעםsmarty_function_<name>), או כל פונקציה גלובלית בשםsmarty_function_<name>. - בלוקים: קבצי
block.<name>.php, או פונקציה גלובליתsmarty_block_<name>. - Modifiers (
{$x|name}): modifier שנרשם מקובץmodifier.<name>.php, ואם אין, כל פונקציית PHP גלובלית בשם הזה.
הפונקציות שהליבה מספקת: crm_get_banner, crm_get_google_banner, crm_live_edit, crm_storage_tumb, crm_storage_url.
// application/includes/smarty_helpers.php (נטען על ידי PAGE::load)
function smarty_function_money($params, &$smarty)
{
return number_format((float)$params["amount"], 2) . " ₪";
}
<p>מחיר: {money amount=$row.price}</p>
אין תיקיית plugins ייעודית לאתר. התיקיות שהוגדרו ב-system/Smarty/ (configs, plugins) אינן קיימות בפועל, ומחפשים בהן רק אם נוצרו. הדרך הנקייה להרחבה מהאתר היא פונקציית smarty_function_* / smarty_block_* ב-application/includes. ראו תבניות Smarty.
טעינת קבצים#
FILES::include_dir()#
public static function include_dir($directoryincl)
עושה require_once לכל קובץ *.php ישירות בתיקייה (לא רקורסיבי), ממוינים לפי שם קובץ. תיקייה נטענת פעם אחת בלבד לבקשה: הנתיב נרשם ב-MISC::$GLOBALS["arrIncDirs"] והשוואה היא למחרוזת המדויקת (a/b ו-a/b/ נחשבות שונות). תיקייה שלא קיימת מדולגת בשקט. ערך החזרה: "" אם הנתיב כבר נטען, אחרת אין (null).
FILES::include_dir(CONFIG::$base_path . "/application/includes");
FILES::include_dir("application/admin/includes");
PAGE::load() קורא ל-include_dir על application/includes (צד לקוח) ועל application/admin/includes (אדמין), ולכן כל קובץ .php שתניחו שם ירוץ בכל בקשה, לפי סדר שמות הקבצים. אם יש תלות בין קבצים, קבעו את הסדר בשם (01_config.php, 02_helpers.php). ראו מבנה התיקיות של אתר.
FILES::scss()#
public static function scss($file)
מקבלת נתיב או כתובת של קובץ style, ומחזירה מחרוזת CSS מקומפל (או false). משמשת את PAGE::IncludeJsCss לנכסי print (CSS inline), ואת הקונטרולר minify לקבצי .scss.css.
| קלט | מה קורה |
|---|---|
x.scss | קומפילציה עם ScssPhp (פלט COMPRESSED) |
x.scss.css | אותו דבר: מקור הקומפילציה הוא x.scss (הסיומת .css היא רק כדי שהבקשה תנותב אל PHP). אם x.scss לא קיים אבל x.scss.css פיזי כן, הקובץ הפיזי מוחזר כמו שהוא |
x.css | קריאה ישירה מהדיסק, בלי קומפילציה, בלי cache ובלי שכתוב url() |
כתובת http(s):// של האתר עצמו | מנורמלת לנתיב יחסי (site_url / original_site_url / env_url) |
| כתובת חיצונית, נתיב ריק, סיומת אחרת, קובץ שלא נמצא, או שגיאת קומפילציה | false |
פרטים שחשוב להכיר:
?queryו-#fragmentנחתכים לפני העיבוד.- איתור הקובץ הפיזי לפי הסדר: נתיב מוחלט קיים,
assets/...⇒system/assets/...,CONFIG::$base_path/<נתיב>,DOCUMENT_ROOT/<נתיב>. - ה-
@importנפתרים מתיקיית הקובץ הראשי בלבד. - בתוצאה, כל
url(...)יחסי (שאינוhttp,data:או שמתחיל ב-/) משוכתב לנתיב מוחלט מהשורש, כדי שהוא יעבוד גם כשה-CSS מוזרק inline. - התוצאה נשמרת ב-
cache/<SERVER_NAME>/scss/<file>ver=<attaches_version>.css(כתיבה אטומית דרך קובץ זמני, ו-touchל-24 שעות קדימה). קובץ cache ריק נחשב פגום ומקומפל מחדש.
$css = FILES::scss("themes/main/critical.scss.css");
if ($css !== false) {
PAGE::add_meta("<style>" . $css . "</style>", "head");
}
התיעוד בקוד מספר שה-cache מתחדש לפי filemtime, אבל בפועל המתודה לא משווה זמני שינוי: אם קובץ cache תקין קיים היא מחזירה אותו. עריכת x.scss לא תשפיע עד שמעלים את attaches_version (ראו PARAMS ו-LANGS) או מוחקים את תיקיית cache/<domain>/scss. ראו צינור הנכסים.
חריגה מ-ScssPhp נתפסת ומוחזר false, בלי לוג ובלי הודעה. אם סגנון "נעלם", בדקו את ה-SCSS בנפרד. כמו כן, אין נרמול של .. בנתיב: העבירו רק נתיבים שאתם שולטים בהם ולא קלט של גולש.
FILES::vue()#
public static function vue($url, $m_settings)
טוענת bundle של Vue שנבנה ל-HTML (דף index.html של Vite, לדוגמה) ומחזירה מחרוזת HTML שאפשר להחזיר מקונטרולר. $m_settings הוא מערך (חובה, אין ברירת מחדל) ומכיל מפתח אחד: script (קוד JS שיתווסף בסוף בלוק ה-module, ברירת מחדל "").
מה היא עושה:
- טוענת את הקובץ ב-
html_template($url, [], ["use_smarty" => false])(מצב eval, ראו למעלה), ושולפת את תוכן ה-<head>. - מתוך ה-head:
<script src>מקומי הופך ל-import,<script src>חיצוני (CDN) נשמר כתגית רגילה, ו-<link rel="stylesheet">מקומי נרשם עםPAGE::add_asset_advanced(..., ["area" => "body_end", "defer" => true]); stylesheet חיצוני נכנס ל-PAGE::add_metaבאזורhead. תגיות<link>אחרות (icon, manifest) מתעלמים מהן. - מחזירה: ה-HTML של ה-body, תגיות הסקריפטים החיצוניים, ובלוק
<script type='module'>שמייבא כל סקריפט מקומי כ-import '/<src>?ver=<time()>';ואחריו$m_settings["script"].
// application/controllers/dashboard.php
class dashboard extends wz_controller
{
function index()
{
return FILES::vue("application/vue/dist/index.html", [
"script" => "window.APP_CONFIG = " . json_encode(["apiBase" => "/dashboard/"]) . ";",
]);
}
}
(1) ?ver=time() משתנה בכל בקשה, ולכן הדפדפן לא שומר את ה-bundle ב-cache. (2) הקובץ עובר במצב eval, ולכן כל רצף $ או ${ בתוך ה-HTML (למשל template literal בקוד inline) יפוענח כ-PHP ועלול לשבור את הדף. bundle שמכיל ${ בתוך ה-HTML עצמו אינו מתאים לפונקציה הזו. ראו פאנלים מבוססי Vue.
קבצים ותיקיות#
FILES::get_extension()#
public static function get_extension($filename)
מחזירה את הסיומת לפי pathinfo($filename)['extension'], באותיות כפי שנכתבו (בלי lowercase), ובלי הנקודה. לקובץ בלי סיומת אין המפתח, ולכן מתקבלת אזהרת Undefined array key והערך null.
FILES::get_extension("media/Report.PDF"); // "PDF"
FILES::make_dir()#
public static function make_dir($path, $mode = 0777)
יוצרת תיקייה רקורסיבית, כולל תיקיות אב חסרות. מחזירה true אם התיקייה קיימת או נוצרה, false אם היצירה נכשלה. ההרשאות נקבעות לפי $mode תוך איפוס umask, כך שבפועל התיקייה היא 0777 (כתיבה לכולם), אלא אם ביקשתם אחרת.
if (!FILES::make_dir(CONFIG::$base_path . "/media/exports/2026", 0755)) {
throw new Exception("cannot create exports folder");
}
עצי cache ו-media/Storage שנוצרים דרך הפונקציה הזו פתוחים לכתיבה לכל משתמש במערכת. באחסון משותף העבירו 0755 במפורש.
FILES::_make_dir()#
public static function _make_dir($path, $mode = 0777)
יוצרת תיקייה אחת (לא רקורסיבית) עם mkdir שקט ואיפוס זמני של umask. מחזירה את תוצאת mkdir (bool). זה עוזר פנימי של make_dir; השתמשו ב-make_dir.
FILES::remove_dir()#
public static function remove_dir($directory, $empty = FALSE)
מוחקת תיקייה ואת כל תוכנה בצורה רקורסיבית.
| פרמטר | משמעות |
|---|---|
$directory | נתיב. לוכסן סופי מוסר |
$empty | FALSE (ברירת מחדל): מוחקת גם את התיקייה עצמה. TRUE: מרוקנת אותה ומשאירה אותה |
מחזירה true בהצלחה. מחזירה false אם הנתיב אינו קיים, אינו תיקייה, אינו קריא, או אם rmdir נכשל. תיקיות-משנה נמחקות תמיד לגמרי (גם כש-$empty = TRUE).
FILES::remove_dir(CONFIG::$base_path . "/cache/" . $_SERVER["SERVER_NAME"] . "/scss", true); // מרוקנת ומשאירה
הפונקציה לא בודקת שהנתיב נמצא בתוך האתר, ו-is_dir עוקב אחרי קישורים סימבוליים לתיקיות: תוכן היעד של symlink יימחק. אל תעבירו לה קלט שמקורו בגולש או בטופס, ובנו את הנתיב מקבוע ידוע. הקריאות unlink אינן שקטות: קובץ שאי אפשר למחוק יפיק אזהרה והמחיקה תמשיך.
FILES::is_img()#
public static function is_img($str)
מחזירה true אם המחרוזת מסתיימת באחת מהסיומות gif, jpg, jpeg, png, tiff, jfif, webp (אותיות קטנות בלבד). null או מחרוזת ריקה מחזירים false. אפשר להעביר סיומת ("png") או שם קובץ שלם.
FILES::is_img("png"); // true
FILES::is_img("photo.webp"); // true
FILES::is_img("PHOTO.PNG"); // false (רגיש לאותיות)
FILES::is_img("logo.svg"); // false (svg, avif ו-bmp אינם ברשימה)
ההתאמה היא לסוף המחרוזת בלבד, ולכן גם "mypng" נחשב תמונה. השתמשו בה על סיומת שכבר חולצה (get_extension עם strtolower), לא כבדיקת אבטחה להעלאת קבצים (ראו כללי העלאת קבצים).