פונקציות עזר (MISC)

מדריך למחלקת MISC: מחרוזות ומספרים, פורמט הצינורות bgl_implode, כתובות URL, כתובת IP, בקשות HTTP יוצאות, פלט ו-CLI, כולל מה שדורש זהירות.

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

MISC היא מחלקה סטטית עם פונקציות עזר קטנות שמשמשות את הליבה ואת קוד האתר: עיבוד מחרוזות ומספרים, אחסון רשימות מזהים בעמודה אחת, בניית כתובות, זיהוי IP, בקשות HTTP יוצאות ועוד. בעמוד הזה מתועדות הפונקציות הציבוריות שכדאי להכיר, עם חתימה מדויקת ועם הערות על התנהגות שמפתיעה. הפונקציות מחולקות לפי נושא.

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

מחרוזות ופלט#

פונקציהתיאור
MISC::special_chars($text)htmlspecialchars ועוד המרת ' ל-'. הפונקציה לפלט בתוך HTML
MISC::clear_quotes($str)מסיר כל " ו-' מהמחרוזת. זה ניקוי קוסמטי, לא הגנה (ראו CSRF, XSS ו-SQLi)
MISC::max_chars($str, $max)אם strlen($str) גדול מ-$max, חותך ב-mb_substr ומוסיף .... MaxChars הוא כינוי זהה
MISC::is_valid_email($str)בדיקת regex בסיסית: bool
MISC::IsSerialized($str)האם המחרוזת תוצאה של serialize (מנסה unserialize, ראו אזהרה)
MISC::MySubstr($string, $start, $length = null)חיתוך לפי בתים של windows-1255 (מתאים לעברית). דורש iconv
MISC::IsHebrew($string)ראו אזהרה למטה
echo MISC::special_chars($row["title"]);                 // בטוח בתוך תוכן HTML
echo MISC::max_chars($article["intro"], 160);            // "..." אם נחתך
if (!MISC::is_valid_email($_POST["email"])) { return "ERROR: email"; }
שימו לב
max_chars מודד בבתים ומחתכת בתווים התנאי משווה strlen (בתים), אבל החיתוך עצמו הוא mb_substr (תווים). בעברית כל אות תופסת שני בתים, ולכן מחרוזת של 60 אותיות (120 בתים) עם $max = 100 תקבל ... בסוף, אף שלא נחתך ממנה דבר.

שימו לב
IsHebrew לא עובדת כמו שהשם מרמז הביטוי הרגולרי כתוב עם סוגריים מרובעים כמפרידים ("[א-ת]"), ולכן PHP מפרש אותו כחיפוש של הרצף הליטרלי א-ת ולא כטווח אותיות. אל תסתמכו עליה לזיהוי עברית. בדיקה נכונה: preg_match('/\p{Hebrew}/u', $str).

שימו לב
IsSerialized מריצה unserialize הפונקציה מפענחת את המחרוזת בפועל. אל תעבירו אליה קלט שהגיע מהמשתמש, ושימו לב שגם MISC::decode מבצעת unserialize (ראו הצפנה).

מספרים והשוואות#

פונקציהתיאור
MISC::my_number_format($num)0 עבור NULL; אחרת number_format($num, 2, '.', ','), ואם הסיומת היא .00 היא נחתכת
MISC::is_pos($x)(float)$x > 0
MISC::is_int($x)למרות השם: is_numeric($x), ולכן "1.5" ו-"1e3" מחזירות true
MISC::value_in($search_in, $search_for)האם $search_in נמצא בין הערכים. אם $search_for מחרוזת, היא מפוצלת לפי ,. השוואה רופפת (==). מחזיר bool
echo MISC::my_number_format(1250);      // 1,250
echo MISC::my_number_format(1250.5);    // 1,250.50
var_dump(MISC::value_in(3, "1,2,3"));   // bool(true)
var_dump(MISC::value_in("b", ["a", "b"])); // bool(true)
הערה
value_in משווה ב-== השוואה רופפת אומרת ש-"1" שווה ל-1 ול-"01". כשזה חשוב (מזהים, טוקנים), השתמשו ב-in_array($x, $arr, true).

רשימות מזהים בעמודה אחת: bgl_implode ו-bgl_explode#

כמה טבלאות בליבה שומרות רשימת מזהים (קטגוריות, תגיות) בעמודת טקסט אחת בפורמט צינורות, כך שאפשר לחפש אחד מהם עם LIKE '%|11|%'.

פונקציהתיאור
MISC::bgl_implode($arr)מחזיר `"
MISC::bgl_explode($str)הפעולה ההפוכה: [] אם הקלט ריק, אחרת `array_filter(explode("
$stored = MISC::bgl_implode([11, 482]);        // "|11||482|"
$ids    = MISC::bgl_explode($stored);          // [11, 482] (כמחרוזות)

$id  = (int)$_GET["cat"];
$rows = DB::get_all("SELECT id, title FROM CRM_articles WHERE categories LIKE '%|" . $id . "|%'");
אל תשתמשו ב-
explode(",") על עמודות כאלה הפורמט הוא || בין ערכים ו-| בקצוות. שני הכיוונים מסננים ערכים "ריקים" עם array_filter, ולכן המזהה 0 (או "0") נעלם. array_filter שומר מפתחות, ולכן עטפו ב-array_values כשצריך רצף. ערכי המערך חוזרים כמחרוזות.

כתובות URL#

פונקציהתיאור
MISC::absolute_url($url)אם $url כבר כתובת מלאה (FILTER_VALIDATE_URL) מחזיר אותה. אחרת מוסיף http או https (לפי $_SERVER['HTTPS'] === 'on') ואת HTTP_HOST
MISC::add_querystring_var($url, $key, $val)מסיר את $key אם קיים ומוסיף key=val בסוף
MISC::remove_querystring_var($url, $key)מסיר פרמטר אחד מה-query
MISC::bgl_url_encode($url)urlencode לכל קטע בין / בנפרד
MISC::url_exists($url)בקשת HEAD עם cURL: true ל-200, 301 או 302
MISC::ping_url($url)בקשת HEAD עם timeout של 2 שניות, עוקבת אחרי הפניות. מחזירה את תוצאת curl_exec
$url = MISC::add_querystring_var("/search?q=cms&page=2", "page", 3);
// "/search?q=cms&page=3"

$abs = MISC::absolute_url("/contact");
// "https://example.co.il/contact"  (לפי ה-Host של הבקשה)

$path = MISC::bgl_url_encode("files/דוח שנתי.pdf");
// "files/%D7%93%D7%95%D7%97+%D7%A9%D7%A0%D7%AA%D7%99.pdf"
ערכים לא עוברים קידוד
add_querystring_var כותבת את $key ואת $val כמו שהם, ו-remove_querystring_var בונה מחדש את ה-query מערכים שפוענחו, בלי לקודד אותם שוב. ערך עם &, # או רווח שוברים את הכתובת. קודדו מראש: MISC::add_querystring_var($url, "q", rawurlencode($q)).

שימו לב
absolute_url סומכת על HTTP_HOST כותרת Host נשלטת על ידי הלקוח. אל תשתמשו בתוצאה בקישורים שנשלחים בדוא"ל (איפוס סיסמה, אישור) ובהפניות רגישות. השתמשו ב-CONFIG::$site_url.

כתובת IP ומיקום#

פונקציהתיאור
MISC::get_ip()HTTP_CLIENT_IP, ואם אין אז HTTP_X_FORWARDED_FOR, ואם אין אז REMOTE_ADDR
MISC::ip_info($ip = NULL, $purpose = "location", $deep_detect = TRUE)מידע גיאוגרפי על כתובת: country, countrycode, state, region, city, location (מערך) או address (מחרוזת). מחזיר NULL אם הבדיקה נכשלה
$info = MISC::ip_info(null, "location");
// ["city" => ..., "state" => ..., "country" => ..., "country_code" => "IL", "continent" => ..., "continent_code" => ...]

$code = MISC::ip_info(null, "countrycode");   // "IL" או NULL

ip_info ללא $ip תקף משתמשת ב-REMOTE_ADDR, ועם $deep_detect גם ב-HTTP_CF_CONNECTING_IP (Cloudflare), אחר כך HTTP_X_FORWARDED_FOR ו-HTTP_CLIENT_IP, כשהם כתובות תקינות. תוצאה מוצלחת נשמרת ב-SESSION תחת ip_check כדי לא לבצע בדיקה חוזרת באותו סשן, וכישלון לא נשמר. המידע מגיע משירות חיצוני עם גיבוי (get_ip_data_with_fallback), כך שהקריאה הראשונה בסשן היא בקשת רשת.

אזהרה
get_ip() ניתנת לזיוף שתי הכותרות הראשונות נשלטות על ידי הלקוח, והערך של X-Forwarded-For יכול להכיל רשימה של כתובות. אל תבססו עליה הגבלת קצב, חסימות או החלטות אבטחה. מאחורי Cloudflare או proxy מוכר, קראו ישירות את הכותרת של ה-proxy רק אחרי שווידאתם ש-REMOTE_ADDR שייך אליו. ראו מודל האבטחה.

בקשות HTTP יוצאות#

MISC::file_get_contents($url) היא עטיפה של cURL, שעוקבת אחרי הפניות, שולחת User-Agent של דפדפן ומחזירה את גוף התשובה רק כשקוד ה-HTTP הוא 200. בכל מקרה אחר היא מחזירה false.

$html = MISC::file_get_contents("https://example.com/feed.xml");
if ($html === false) {
    return "ERROR: feed unavailable";
}
אימות SSL כבוי, ואין הגנת SSRF

הפונקציה מבטלת CURLOPT_SSL_VERIFYPEER ו-CURLOPT_SSL_VERIFYHOST, ולכן לא מבדילה בין שרת אמיתי לשרת מתחזה. היא גם לא מגבילה כתובות פנימיות, ואין לה timeout, ולכן שרת שלא עונה יכול לתקוע את התהליך. אם הכתובת מגיעה מקלט משתמש, אמתו מארח ופרוטוקול לפני הקריאה (ראו העלאות קבצים). לשירותי API מאומתים השתמשו ב-cURL ישירות, עם אימות מופעל.

הצפנה פשוטה#

MISC::encode($str) ו-MISC::decode($str) מצפינות ערכים (AES-256-CBC) ומחזירות מחרוזת שמתאימה לכתובת URL. MISC::set_encrypt_secret_keys($key, $iv) מחליפה את מפתחות ברירת המחדל, ויש לקרוא לה באתר לפני כל שימוש.

בלי
set_encrypt_secret_keys המפתח ידוע לכולם ברירות המחדל כתובות בקוד, והליבה עצמה אינה קוראת לפונקציה. כל האתרים שלא קראו לה משתמשים באותו מפתח. פרטים מלאים, כולל סכנת החלפת מפתח באתר חי, בהצפנה.

אבטחת קלט, דפדפן ו-CLI#

פונקציהתיאור
MISC::init_security()מופעלת בתחילת בקשה. בודקת ערכי ומפתחות $_GET ומפתחות $_POST בלבד עם detect_sql_injection, ועוצרת עם die("SECURITY ERROR1") עד ERROR6. בדיקת הקבצים (is_file_secure) מושבתת בקוד
MISC::cli_check()ב-CLI הופכת ארגומנטים key=value ל-$_GET, ו-url= ל-REQUEST_URI, מגדירה HTTPS והדומיין של הפלטפורמה, ומעלה את CONFIG::init()
MISC::detect_cli_php(): stringמוצאת בינארי PHP מתאים להרצת CLI (ראו משימות מתוזמנות)
MISC::ping($url, $params = [])מפעילה בקשה לאתר עצמו כתהליך CLI ברקע, ולא ממתינה (ראו משימות מתוזמנות)
שימו לב
init_security היא מסנן נוחות היא מזהה דפוסי SQL מוכרים בשדות GET ובמפתחות POST, ועוצרת בקשות חשודות, אבל אינה מחליפה DB::escape או שימוש ב-builder, וערכי POST עצמם אינם נבדקים. ראו CSRF, XSS ו-SQLi.

עזרים לתצוגה ולמבנה העמוד#

פונקציהתיאור
MISC::breadcrumbs($list)שולחת את $list ככותרת HTTP בשם breadcrumbs (JSON) ומציבה אותה ב-MISC::$GLOBALS["breadcrumbs"] לשימוש התבנית
MISC::fancy_title($title) / MISC::fancy_subtitle($title)שולחות כותרות HTTP fancy_title ו-fancy_subtitle (עם rawurlencode), שבהן משתמש ממשק הניהול
MISC::get_modules_tbl()טבלת CRM_modules ממופה לפי moduleName, שמורה במטמון תחת modules_lang<N> ל-7 ימים (ראו Controllers)
MISC::uuid($data = null)UUID גרסה 4 (36 תווים). אם $data ניתן, חייב להיות 16 בתים
MISC::array_reverse($a)הופכת סדר מערך תוך שמירה על מפתחות, כולל מספריים
MISC::array_unshift(&$array, $key, $val)מוסיפה איבר עם מפתח לתחילת מערך (מעדכנת את $array ומחזירה אותו)
MISC::debug($data)רק למנהל מחובר: var_dump בתוך <pre> ו-die
MISC::service_unavailable($reason = "", $retry_after = 120)מסיימת את הבקשה עם 503 ו-Retry-After ועמוד קצר (ב-CLI: exit(1))
MISC::breadcrumbs([
    ["title" => "בית",   "url" => "/"],
    ["title" => "חדשות", "url" => "/news"],
]);

$orderRef = MISC::uuid();      // למשל "3f2b1c7e-6a4d-4c0e-9d1a-0b7c2e5f8a14"

service_unavailable נועדה לתקלות תלות (למשל מסד נתונים שלא זמין): היא מחזירה 503 ולא 200 או 404, כדי שמנועי חיפוש יבינו שהתקלה זמנית, ומונעת שמירת התשובה במטמון של העמודים. היא גם לא פונה למסד הנתונים.

לתקלות תלות השתמשו ב-503

כשקוד שלכם תלוי במשאב חיצוני שנפל, MISC::service_unavailable("payment api down") עדיפה על die("error"), שמחזיר 200 עם הודעת שגיאה כתוכן העמוד.

פונקציות ישנות שכדאי לא להשתמש בהן#

הפונקציות הבאות קיימות מטעמי תאימות לאחור או שאינן עובדות כמתוכנן. הן מופיעות כאן כדי שתדעו להימנע מהן.

פונקציההערה
MISC::email($to, $from, $subj, $msg, $bcc = "")mail() עם כותרות משורשרות, ללא ניקוי שורות חדשות. השתמשו ב-mail_tpl (דוא"ל)
MISC::add_mail_to_queue(...)כותבת ל-CRM_emails_queue. נקראת מ-mail_tpl ואינה נועדה לשימוש ישיר (דוא"ל)
MISC::sanitize_output($buffer)מחזירה את הקלט כמו שהוא. קוד הדחיסה אחרי ה-return לא רץ
MISC::ieversion()קוראת ל-ae_detect_ie() ללא self::, ולכן שגיאה קטלנית אם נקראת. MISC::ae_detect_ie() עצמה עובדת
MISC::heb2utf($s)ממירה עברית בקידוד windows-1255 ל-UTF-8, ומשתמשת במשתנה $t שלא אותחל (אזהרת PHP). לא לשימוש חדש
MISC::LogFile($filename, $dump)מתודה לא סטטית, ולכן קריאה אליה כ-MISC::LogFile ב-PHP 8 היא שגיאה. פותחת קובץ בכתיבה (w) ודורסת אותו בכל קריאה
MISC::create_editor, create_editor_mini, create_editor_newמחזירות HTML של עורך CKEditor מקומי (assets/ckeditor/ckeditor.js), עם htmlspecialchars על הערך ההתחלתי. בטפסי ניהול השתמשו בשדות הטפסים (שדות טופס)

ראו גם#

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