MISC (רפרנס)

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

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

MISC (system/collections/MISC.php) היא מחלקה סטטית של עזרים כלליים. היא גדלה לאורך השנים ואוספת דברים שאין ביניהם קשר: שער האבטחה של הבקשה, הצפנה, שליחת מייל, עבודה עם כתובות, פונקציות מחרוזת, זיהוי IP, ועורכי CKEditor. חלק מהמתודות בה פשוט שבורות או ישנות, והדף מסמן אותן במפורש כדי שלא תסתמכו עליהן.

system/ בדף הזה הוא תיקיית הליבה הפרוסה (api/core בריפו). החתימות מועתקות מהקוד, וכך גם ההתנהגות המתוארת: כל מתודה נקראה מול גוף הפונקציה.

מה כדאי להכיר קודם

רוב הקוד באתרים משתמש בקבוצה קטנה: MISC::email, MISC::encode / MISC::decode, MISC::get_ip, MISC::ip_info, MISC::uuid, MISC::absolute_url, MISC::ping ו-MISC::breadcrumbs. את שאר המתודות עדיף לא להכניס לקוד חדש. פרק "עזרים ישנים או שבורים" בסוף הדף מרכז אותן.

מאפיינים ציבוריים#

מאפייןמשמעות
MISC::$GLOBALSמערך משותף לבקשה אחת (array() בהתחלה). מחזיק בין היתר app_html, body_class, minify_files, minify_groups, arrIncDirs ו-breadcrumbs. רוב המפתחות נקבעים ונקראים על ידי הליבה, ואין רשימה סגורה שלהם
MISC::$JSONVARSמאגר ישן של משתנים לשליחה ל-JavaScript. אינו בשימוש בקוד החדש

שערי אבטחה ושגיאות#

MISC::init_security()#

public static function init_security()

שער ההגנה הראשון של הבקשה: מסנן heuristic ש-core.php קורא לו בראש תהליך הטעינה. הוא מריץ את detect_sql_injection על הערכים והמפתחות של $_GET (הודעות SECURITY ERROR1 ו-SECURITY ERROR2), על המפתחות של $_POST (SECURITY ERROR4), ומעביר כל קובץ ב-$_FILES דרך is_file_secure (SECURITY ERROR5 ו-SECURITY ERROR6). בהתאמה הוא מסיים את הבקשה עם die והודעה.

פרמטריםאין
החזרהאין. כשמזהה בקשה חשודה לא חוזרת בכלל
זה לא WAF, וזה לא שקול להגנה בקוד

הבדיקה היא heuristic. בדיקת הערכים של $_POST מושבתת (הקוד מסומן TEMP ומוער, ושגיאה 3 לא קיימת בפועל), ורק המפתחות נבדקים. בדיקת הקבצים כבויה גם היא (ראו is_file_secure). ההודעה נשלחת בסטטוס HTTP 200 ולא ב-403, כך שמנועי חיפוש וכלי ניטור רואים אותה כדף תקין. אל תסמכו עליה במקום DB::escape, פרמטרים מוגבלים ובדיקת הרשאות. ראו מודל האבטחה ו-CSRF, XSS והזרקת SQL.

MISC::detect_sql_injection()#

static function detect_sql_injection($val)

בודקת מחרוזת אחת מול רשימת מילות מפתח של SQL. מחזירה true כשנמצאו לפחות ארבע התאמות, או כשהמחרוזת מכילה select * או union all. אחרת false.

פרמטרמשמעות
$valמחרוזת לבדיקה

החזרה: bool. זה חלק מ-init_security() ואינו כלי הגנה עצמאי: ספירת מילים נותנת גם false positives (טקסט חופשי שמכיל מילים כמו select, from, where) וגם false negatives.

MISC::is_file_secure()#

static function is_file_secure($file)

נועדה לבדוק שקובץ שהועלה אינו מסוכן.

הבדיקה כבויה

גוף הפונקציה מתחיל ב-return true; // Disable check, ולכן היא מחזירה true לכל קובץ. אל תסתמכו עליה כדי לאשר העלאות. בדקו סיומת, סוג MIME ותוכן בעצמכם.

MISC::cli_check()#

static function cli_check()

מכינה את הסביבה כשהסקריפט רץ משורת פקודה (cron, MISC::ping). היא קוראת את argv בצורת key=value: הארגומנט url=... הופך ל-$_SERVER['REQUEST_URI'], וכל ארגומנט אחר נכנס ל-$_GET. מגדירה HTTPS=on, מריצה CONFIG::init(), מסמנת ignore_domain_check ומשתמשת בדומיין של הפלטפורמה הראשונה.

פרמטריםאין
החזרהאין

אין צורך לקרוא לה ידנית: core.php קורא לה בתחילת הטעינה (ב-web היא לא עושה כלום). ראו Cron.

MISC::service_unavailable()#

public static function service_unavailable($reason = "", $retry_after = 120)

מסיימת את הבקשה בתשובת "נסו שוב מאוחר יותר" אמיתית: סטטוס 503 עם Retry-After, במקום דף שבור בסטטוס 200 או 404 מטעה. נקראת כשתלות קריטית (בעיקר מסד הנתונים) אינה זמינה.

פרמטרמשמעות
$reasonטקסט פנימי שנכתב ל-error_log (ב-CLI: ל-STDERR). אינו מוצג לגולש
$retry_afterערך הכותרת Retry-After בשניות. ברירת מחדל 120

החזרה: לא חוזרת, מסתיימת ב-exit.

מה היא עושה (ב-web):

  1. מנקה את כל ה-output buffers, כדי שלא יישלח דף חלקי.
  2. שולחת 503, Retry-After וכותרות no-store.
  3. מכבה את מטמון העמוד (PAGE::$cache_this_page = false), כדי שהשגיאה לא תישמר.
  4. כותבת ל-error_log ומדפיסה עמוד שגיאה בעברית עם noindex.

ב-CLI היא כותבת ל-STDERR ויוצאת עם קוד 1. יש הגנה מפני כניסה חוזרת. בכוונה היא לא קוראת ל-ADMIN::is_admin(), כי זה דורש מסד נתונים, וזה בדיוק מה שנפל.

try {
    $row = DB::get_row("SELECT 1 FROM CRM_modules LIMIT 1");
} catch (Exception $e) {
    MISC::service_unavailable("db down: " . $e->getMessage(), 300);
}

ראו עמודי שגיאה.

MISC::debug()#

public static function debug($data)

מדפיסה var_dump בתוך <pre> ומסיימת את הבקשה.

פרמטרמשמעות
$dataכל ערך
עובדת רק למנהל

לגולש רגיל או למשתמש לא מחובר המתודה לא עושה כלום, בלי הודעה. למנהל היא עושה die ולכן עוצרת את כל הבקשה. לא להשאיר בקוד production.

הצפנה וקידוד#

ראו גם את הדף על הצפנה.

MISC::set_encrypt_secret_keys()#

static function set_encrypt_secret_keys($key, $iv)

קובעת את המפתח וה-IV שישמשו את encrypt_decrypt.

פרמטרמשמעות
$keyמחרוזת סוד. נעשה עליה sha256 לפני השימוש כמפתח AES-256
$ivמחרוזת סוד נוספת. 16 התווים הראשונים של sha256 שלה הם ה-IV
הליבה לא קוראת לה

אם האתר לא קורא ל-set_encrypt_secret_keys() בעצמו, encrypt_decrypt, encode ו-decode משתמשות במפתח וב-IV ברירת מחדל שכתובים בקוד המקור, וזהים בכל האתרים שמריצים את אותה גרסת ליבה. מי שיודע את ברירת המחדל יכול לפענח כל ערך שהוצפן כך, ולזייף ערכים. קראו לפונקציה פעם אחת בעת האתחול (למשל ב-CONFIG_USER) עם סודות ייחודיים לאתר מהכספת, ואל תכתבו אותם בקוד.

MISC::encrypt_decrypt()#

static function encrypt_decrypt($action, $string)

הצפנת AES-256-CBC. המפתח הוא sha256 של סוד המפתח, וה-IV הוא 16 התווים הראשונים של sha256 של סוד ה-IV.

פרמטרמשמעות
$action'encrypt' להצפנה, 'decrypt' לפענוח
$stringהמחרוזת לעיבוד. בהצפנה: טקסט רגיל. בפענוח: הפלט של הצפנה קודמת

החזרה: בהצפנה, מחרוזת base64. בפענוח, הטקסט המקורי. לא מאמתת שלמות (אין MAC), ולכן ciphertext שונה ידנית עשוי להתפענח לזבל או להיכשל.

MISC::encode()#

public static function encode($str)

מקודדת ערך PHP כלשהו למחרוזת בטוחה ל-URL: serialize, אחר כך encrypt_decrypt('encrypt'), אחר כך base64 עם החלפת +/= ב--_,. מחרוזת ריקה מחזירה מחרוזת ריקה.

פרמטרמשמעות
$strערך לקידוד. בפועל כל ערך שניתן ל-serialize, לא רק מחרוזת

החזרה: string.

MISC::decode()#

public static function decode($str)

ההפך של encode: מחליפה בחזרה את התווים, מפענחת base64, מפענחת AES ואז קוראת ל-unserialize.

פרמטרמשמעות
$strפלט של MISC::encode

החזרה: הערך המקורי, או false/null כשהקלט פגום.

decode מריצה unserialize על קלט שהגולש מחזיק

בדרך כלל התוצאה של encode נשלחת לגולש (בקישור, ב-cookie או בשדה נסתר) ומתקבלת חזרה. כל עוד המפתח הוא ברירת המחדל, גולש יכול לבנות מחרוזת שתפוענח ל-unserialize של אובייקט לבחירתו. גם עם מפתח סודי, העדיפו להצפין ערך פשוט (מזהה, מחרוזת JSON) ולא אובייקטים, ואמתו את התוצאה אחרי הפענוח.

$token = MISC::encode(["user" => 17, "exp" => time() + 3600]);
$data  = MISC::decode($token);
if (!is_array($data) || ($data["exp"] ?? 0) < time()) {
    // ערך פגום או שפג תוקפו
}

MISC::uuid()#

static function uuid($data = null)

מחזירה UUID מגרסה 4 (36 תווים). משמשת בליבה לשמות קבצים זמניים ולמזהי אלמנטים.

פרמטרמשמעות
$dataאופציונלי: בדיוק 16 בתים בינאריים. בלעדיו נוצרים 16 בתים אקראיים עם random_bytes

החזרה: string בצורה xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. אורך שונה מ-16 בתים נבדק רק ב-assert, שכבוי ברוב סביבות הייצור.

$name = MISC::uuid() . ".jpg";

מייל#

ראו גם את הדף על מייל.

MISC::email()#

public static function email($to,$from,$subj,$msg, $bcc = "")

שולחת מייל HTML באמצעות mail() של PHP. הנושא מקודד base64 (=?UTF-8?B?...?=), ושם השולח בפורמט Name <addr> מקודד בנפרד.

פרמטרמשמעות
$toכתובת היעד
$fromaddr@example.com או Name <addr@example.com>. משמש גם ל-From וגם ל-Reply-To
$subjנושא (טקסט רגיל, לא מקודד)
$msgגוף ההודעה כ-HTML
$bccאופציונלי: כתובת העתק מוסתר

החזרה: bool מ-mail(). true אומר רק שה-MTA המקומי קיבל את ההודעה.

הזרקת כותרות
$from, $to ו-$bcc נכנסים לכותרות כמו שהם. שורות הניקוי של \r\n מוערות בקוד, ולכן ערך שמכיל \r\n יכול להוסיף כותרות (Bcc, Cc) או להחליף את גוף ההודעה. לעולם אל תעבירו לשדות האלה קלט של גולש (כתובת מטופס יצירת קשר, למשל) בלי לנקות \r ו-\n ולאמת עם filter_var($x, FILTER_VALIDATE_EMAIL). עדיף לשלוח דרך mail_tpl, שעובד עם תור (add_mail_to_queue).

$from = filter_var($_POST["email"] ?? "", FILTER_VALIDATE_EMAIL);
if ($from) {
    MISC::email("office@example.com", "Site <noreply@example.com>", "פנייה חדשה", "<p>מאת: " . MISC::special_chars($from) . "</p>");
}

בדוגמה הכתובת שהגולש הקליד היא רק בתוך הגוף ואחרי אימות, וה-From קבוע.

MISC::MakeEmailAttach()#

function MakeEmailAttach($to,$from,$subj,$msg, $attach)

שליחת מייל עם קבצים מצורפים (multipart).

פרמטרמשמעות
$to, $from, $subj, $msgכמו ב-email()
$attachמערך נתיב_בדיסק => שם_קובץ_במייל

החזרה: bool מ-mail().

שבורה כפי שהיא כתובה

הפונקציה אינה מוגדרת static, ולכן הקריאה MISC::MakeEmailAttach(...) נכשלת ב-PHP 8 (קריאה סטטית למתודה לא סטטית). בכותרת From: שלה יש ; בסוף, הנושא אינו מקודד, והיא מושפעת מאותה בעיית הזרקת כותרות של email(). לא נמצאו קריאות אליה בליבה. השתמשו ב-mail_tpl או בספריית מייל חיצונית.

MISC::add_mail_to_queue()#

public static function add_mail_to_queue($mail, $priority = 1, $mail_tpl= "", $is_sent=false,$pid=false)

מוסיפה שורה לטבלת התור emails_queue (CRM_emails_queue), שממנה נשלחים מיילים ברקע. נקראת על ידי mail_tpl בליבה.

פרמטרמשמעות
$mailמערך נתוני המייל. נשמר מסודר (serialize)
$priorityעדיפות. ברירת מחדל 1
$mail_tplשם התבנית (sysName) לצורך תיעוד והצגה
$is_senttrue כדי לרשום מייל שכבר נשלח: נקבעים is_sent=1, log=1 ו-send_date
$pidאופציונלי: מזהה תהליך לקישור לרשומה

החזרה: אין (הערך של ->insert() אינו מוחזר).

לא שולחת מייל

המתודה רק מוסיפה שורה לתור. השליחה עצמה נעשית על ידי תהליך הרקע של התור. כדי לשלוח מייל בקוד אתר השתמשו ב-mail_tpl ולא בה ישירות.

MISC::is_valid_email()#

public static function is_valid_email($str)

בודקת כתובת מייל בביטוי רגולרי ^[\w\-\.]+\@[\w\-\.]+\.[\w\-]+$ (בלי רגישות לאותיות).

פרמטרמשמעות
$strכתובת לבדיקה

החזרה: bool. לא מקבלת + בחלק המקומי (a+b@x.com נדחית), ולא כתובות עם דומיין בעברית. לאימות מדויק יותר העדיפו filter_var($s, FILTER_VALIDATE_EMAIL).

כתובות ומחרוזת שאילתה#

MISC::absolute_url()#

static function absolute_url($url)

הופכת כתובת יחסית לאבסולוטית.

פרמטרמשמעות
$urlכתובת. אם היא עוברת FILTER_VALIDATE_URL היא מוחזרת כמו שהיא

החזרה: string: אחרת מוצמדים לה הפרוטוקול (https כש-$_SERVER['HTTPS'] === 'on', אחרת http) ו-$_SERVER['HTTP_HOST']. אין בדיקה שהכתובת מתחילה ב-/, ולכן absolute_url("a/b") נותנת https://hosta/b (בלי לוכסן).

MISC::absolute_url("/he/contact");   // https://example.com/he/contact
HTTP_HOST מגיע מהלקוח

הערך נלקח מהכותרת Host. בלי הגבלת דומיינים בשרת, כתובת כזו שמוצגת במייל (איפוס סיסמה, למשל) עלולה להצביע לדומיין זר. בקישורים רגישים העדיפו דומיין קבוע מההגדרות.

MISC::add_querystring_var()#

public static function add_querystring_var($url, $key, $val)

מוסיפה או מחליפה פרמטר בכתובת: קודם מסירה את הקיים (remove_querystring_var), אחר כך מצמידה ?key=val או &key=val.

פרמטרמשמעות
$urlכתובת, עם או בלי שאילתה
$keyשם הפרמטר
$valערך. אינו מקודד

החזרה: string.

שני באגים ידועים

הערך אינו עובר urlencode, ולכן ערך עם &, רווח או # שובר את הכתובת. הבדיקה strpos($url,"?") != false נכשלת כשה-? נמצא בתו הראשון (מיקום 0), אז כתובת שמתחילה ב-? מקבלת ? נוסף במקום &. קודדו בעצמכם: MISC::add_querystring_var($u, "q", rawurlencode($q)).

MISC::add_querystring_var("/shop?page=2", "sort", "price");   // /shop?page=2&sort=price

MISC::remove_querystring_var()#

public static function remove_querystring_var($url, $key)

מסירה פרמטר מכתובת ומרכיבה אותה מחדש.

פרמטרמשמעות
$urlכתובת
$keyשם הפרמטר להסרה

החזרה: string. כתובת בלי שאילתה מוחזרת כמו שהיא.

מפרקת ומרכיבה בלי קידוד

השאילתה מפורקת עם parse_str ומורכבת מחדש ללא urlencode. פרמטר מערך (a[]=1) הופך למחרוזת Array, וערכים עם תווים מיוחדים משתנים. אל תשתמשו בה על כתובות עם פרמטרים מערכיים.

MISC::bgl_url_encode()#

static function bgl_url_encode($url)

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

פרמטרמשמעות
$urlנתיב מופרד בלוכסנים

החזרה: string. שימושית לנתיבי קבצים עם עברית. שימו לב ש-urlencode הופך רווח ל-+ ולא ל-%20, מה שנכון לשאילתה ולא תמיד לנתיב.

MISC::bgl_url_encode("uploads/תמונה 1.jpg");   // uploads/%D7%AA%D7%9E%D7%95%D7%A0%D7%94+1.jpg

MISC::url_exists()#

static function url_exists($url)

שולחת בקשת HEAD (cURL, עם מעקב אחרי הפניות ו-CONNECTTIMEOUT של 5 שניות) ובודקת שהכתובת קיימת.

פרמטרמשמעות
$urlכתובת מלאה

החזרה: bool: true כשקוד הסטטוס הסופי הוא 200, 301 או 302. כיוון שעוקבים אחרי הפניות, בפועל מתקבל כמעט תמיד הקוד של היעד הסופי. אין CURLOPT_TIMEOUT כולל, ולכן שרת שמגיב לאט עלול לתקוע את הבקשה.

SSRF

הפונקציה מתחברת לכל כתובת שהיא מקבלת, כולל http://127.0.0.1 ו-http://169.254.169.254. אל תעבירו לה כתובת שהגולש הקליד בלי לאמת מארח וסכמה.

MISC::file_get_contents()#

static function file_get_contents($url)

מורידה כתובת עם cURL (כותרות דפדפן, מעקב הפניות) ומחזירה את הגוף.

פרמטרמשמעות
$urlכתובת מלאה

החזרה: string כשקוד התשובה 200, אחרת false (גם שגיאת רשת וגם 404 או 301 סופי נותנים false).

אימות TLS כבוי

הפונקציה מכבה CURLOPT_SSL_VERIFYPEER ו-CURLOPT_SSL_VERIFYHOST, ולכן כל מי שנמצא בדרך יכול להחליף את התשובה בלי שהשרת ישים לב. אל תשתמשו בה להורדת קוד, הגדרות, מחירים או כל דבר שמשפיע על הרשאות או כסף. כמו url_exists, היא גם פתוחה ל-SSRF כשהכתובת מגיעה מגולש.

MISC::breadcrumbs()#

static function breadcrumbs($list)

קובעת את פירורי הלחם של עמוד ניהול: שולחת אותם בכותרת תגובה בשם breadcrumbs (JSON) ושומרת ב-MISC::$GLOBALS["breadcrumbs"].

פרמטרמשמעות
$listמערך של פריטים, כל אחד ["name" => "...", "url" => "..."]. בקוד הליבה נפוץ פריט אחד עם name בלבד

החזרה: אין.

MISC::breadcrumbs([["name" => "הזמנות"]]);

כיוון שזו כותרת HTTP, יש לקרוא לה לפני שנשלח output כלשהו.

MISC::fancy_title() ו-MISC::fancy_subtitle()#

public static function fancy_title($title)
public static function fancy_subtitle($title)

שולחות את הכותרת וכותרת המשנה של עמוד הניהול כשהן כותרות תגובה (fancy_title, fancy_subtitle), כשהערך עובר rawurlencode. ה-JavaScript של הניהול קורא אותן מהתגובה לבקשת ה-AJAX ומציג בראש המסך.

פרמטרמשמעות
$titleהטקסט

החזרה: אין. כמו breadcrumbs, צריכות לבוא לפני כל output.

מחרוזות ומספרים#

MISC::special_chars()#

public static function special_chars($text)

מקודדת תווי HTML: htmlspecialchars ואז החלפת ' ב-&#39;.

פרמטרמשמעות
$textטקסט

החזרה: string בטוחה להדפסה בתוך תוכן HTML או בתוך ערך מאפיין עם גרשיים כפולים או בודדים. זה לא מגן על הקשר JavaScript או URL.

echo '<input value="' . MISC::special_chars($row["name"]) . '">';

MISC::clear_quotes()#

public static function clear_quotes($str)

מוחקת את כל הגרשיים (" ו-') מהמחרוזת.

פרמטרמשמעות
$strטקסט

החזרה: string. זה לא ניקוי אבטחה: היא פשוט מוחקת, ואינה מטפלת ב-<, ב-\ או בתווי בקרה.

MISC::max_chars() ו-MISC::MaxChars()#

public static function max_chars($str, $max)
public static function MaxChars($str, $max)

חותכת טקסט ארוך ל-$max תווים ומוסיפה .... MaxChars היא כינוי שקורא ל-max_chars.

פרמטרמשמעות
$strטקסט
$maxמספר תווים מרבי

החזרה: string.

בדיקה בבתים וחיתוך בתווים

התנאי משתמש ב-strlen (בתים), והחיתוך ב-mb_substr (תווים). בעברית כל תו הוא שני בתים, ולכן טקסט בן 6 אותיות עם $max = 10 נחתך ומקבל ... לפני שהוא באמת עבר את המגבלה: כלומר מתקבלת מחרוזת שלמה עם שלוש נקודות מיותרות. אם חשוב לכם, כתבו mb_strlen($s) > $max ? mb_substr($s, 0, $max) . "..." : $s.

MISC::my_number_format()#

public static function my_number_format($num)

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

פרמטרמשמעות
$numמספר או מחרוזת מספרית

החזרה: string, או המספר 0 כשהקלט null (או כל ערך שהשוואה רופפת מול NULL נותנת עבורו true, כמו 0 ו-"").

MISC::my_number_format(1234.5);   // "1,234.50"
MISC::my_number_format(1200);     // "1,200"

MISC::is_pos()#

public static function is_pos($x)

החזרה: bool: (float)$x > 0. מחרוזת שאינה מספר נחשבת 0.

MISC::is_int()#

public static function is_int ($x)

החזרה: bool. למרות השם, הפונקציה מחזירה is_numeric($x), כלומר גם "1.5" ו-"1e3" נחשבים "int". לבדיקה אמיתית השתמשו ב-filter_var($x, FILTER_VALIDATE_INT) !== false.

MISC::value_in()#

public static function value_in($search_in, $search_for)

בודקת אם ערך נמצא ברשימה.

פרמטרמשמעות
$search_inהערך לחיפוש (שם הפרמטר מבלבל: זה הערך, לא הרשימה)
$search_forמערך, או מחרוזת שמפוצלת לפי פסיקים

החזרה: bool. ההשוואה היא == רופפת, ולכן MISC::value_in(0, "a,b") נותנת true ב-PHP מתחת ל-8 ו-false מ-8 ומעלה, והשוואות כמו "1" == "01" נותנות true.

MISC::value_in("b", "a,b,c");   // true

MISC::IsSerialized()#

public static function IsSerialized($str)

בודקת אם מחרוזת היא פלט של serialize, על ידי ניסיון unserialize בשקט.

פרמטרמשמעות
$strמחרוזת

החזרה: bool. מכיוון שהיא מריצה unserialize על הקלט, אל תקראו לה על קלט של גולש.

רשימות ומערכים#

MISC::array_reverse()#

public static function array_reverse($a)

הופכת סדר של מערך ושומרת את המפתחות (גם מספריים), בניגוד ל-array_reverse של PHP ללא preserve_keys.

פרמטרמשמעות
$aמערך

החזרה: מערך חדש.

MISC::array_unshift()#

public static function array_unshift(&$array, $key, $val)

מוסיפה איבר בתחילת מערך אסוציאטיבי עם מפתח לבחירה (ה-array_unshift של PHP מאפס מפתחות).

פרמטרמשמעות
&$arrayהמערך, מועבר by reference ומשתנה במקום
$keyהמפתח החדש
$valהערך החדש

החזרה: המערך אחרי השינוי (גם הוא נכתב ל-$array). אם המפתח כבר קיים הוא יידרס במקומו ולא יעבור לתחילה.

$opts = ["b" => "Two"];
MISC::array_unshift($opts, "a", "One");   // ["a" => "One", "b" => "Two"]

MISC::bgl_implode()#

public static function bgl_implode($arr)

מאחדת מערך למחרוזת בפורמט |a||b||c|, שמאפשר חיפוש LIKE '%|b|%' בעמודה אחת בלי התאמה חלקית. ערכים ריקים (falsy, כולל 0 ו-"0") מסוננים.

פרמטרמשמעות
$arrמערך. כל דבר אחר, או מערך ריק, מחזיר ""

החזרה: string.

MISC::bgl_implode([3, 7, 12]);   // "|3||7||12|"

MISC::bgl_explode()#

public static function bgl_explode($str)

ההפך של bgl_implode.

פרמטרמשמעות
$strמחרוזת בפורמט `

החזרה: מערך (ריק עבור מחרוזת ריקה). המפתחות לא מאופסים אחרי array_filter, אז אם הוסר איבר יתכנו חורים במפתחות. הערכים חוזרים כמחרוזות.

MISC::bgl_explode("|3||7||12|");   // ["3", "7", "12"]

מודולים ותרגום#

MISC::get_modules_tbl()#

public static function get_modules_tbl()

מחזירה את כל שורות טבלת CRM_modules, מאונדקסות לפי moduleName, עם מטמון של 24*7 (לפי יחידות cache_engine, ראו רפרנס מטמון) לכל שפה (modules_lang + CONFIG::$lang).

פרמטריםאין
החזרהמערך אסוציאטיבי moduleName => שורה

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

רשת, IP ותהליכים#

MISC::get_ip()#

static function get_ip()

מחזירה את כתובת ה-IP של הגולש: קודם HTTP_CLIENT_IP, אחר כך HTTP_X_FORWARDED_FOR, ואחרת REMOTE_ADDR.

פרמטריםאין
החזרהstring, כפי שהגיעה, בלי אימות
אפשר לזייף

שתי הכותרות הראשונות נשלחות על ידי הלקוח, ולכן גולש יכול לקבוע כל כתובת שירצה ולעקוף הגבלות קצב, חסימות לפי IP ולוגים. X-Forwarded-For יכולה גם להכיל רשימה מופרדת בפסיקים. אם האתר אחרי Cloudflare או proxy מהימן, קראו את הכותרת של אותו proxy בלבד (HTTP_CF_CONNECTING_IP), ואחרת השתמשו ב-$_SERVER['REMOTE_ADDR']. אל תשתמשו בערך הזה להחלטות אבטחה.

MISC::ip_info()#

static function ip_info($ip = NULL, $purpose = "location", $deep_detect = TRUE)

מחזירה מיקום גיאוגרפי של כתובת IP.

פרמטרמשמעות
$ipכתובת לבדיקה. ללא ערך (או ערך לא תקין): נלקחת כתובת הגולש
$purposelocation (ברירת מחדל), address, city, state, region, country, countrycode. האות גדולה/קטנה, רווחים, -, _ והמילה name מתעלמים מהם
$deep_detecttrue: כשאין $ip נלקחת כתובת מ-HTTP_CF_CONNECTING_IP, אחר כך HTTP_X_FORWARDED_FOR, אחר כך HTTP_CLIENT_IP, ורק אחריהן REMOTE_ADDR

החזרה:

$purposeהערך
locationמערך עם city, state, country, country_code, continent, continent_code
addressמחרוזת עיר, אזור, מדינה
city, state, region, country, countrycodeמחרוזת
ערך אחר, או כשל הבדיקהNULL

סדר המקורות: כותרת Cloudflare CF-IPCountry (מחזירה קוד מדינה בלבד, בלי עיר או אזור), ואחריה geoplugin.net, ואחריו ip-api.com, כשהמקורות החיצוניים נקראים ב-HTTP פשוט, עם זמן מרבי של 2 שניות לכל שירות ו-3 שניות בסך הכל. תוצאה מוצלחת נשמרת ב-session (ip_check) ונעשה בה שימוש חוזר עד שה-IP משתנה. כשל לא נשמר.

שירותים חיצוניים, בלי הצפנה, ועם תוצאה ריקה

בלי Cloudflare, הבקשה הראשונה של כל גולש (שלא נשמר לו ערך ב-session) תלויה בשירות חיצוני וחוסמת עד 3 שניות. ה-IP של הגולש נשלח ב-HTTP גלוי. כשכל המקורות נכשלים מקבלים NULL, ולכן תמיד טפלו בו ($c = MISC::ip_info(null, "countrycode"); if (!$c) { ... }). אל תחסמו גולשים לפי תוצאה ריקה. ראו גם את אזהרת get_ip על זיוף כותרות.

$country = MISC::ip_info(null, "countrycode");   // "IL", או NULL כשלא ידוע

MISC::get_ip_data_with_fallback()#

static function get_ip_data_with_fallback($ip)

הפונקציה הפנימית של ip_info: בודקת את כותרת Cloudflare (CF-IPCountry), ואם אין, מנסה את geoplugin.net ואז את ip-api.com (שניהם ב-HTTP), עד לתשובה תקינה.

פרמטרמשמעות
$ipכתובת IP תקינה

החזרה: אובייקט בצורת geoplugin (geoplugin_city, geoplugin_regionName, geoplugin_countryName, geoplugin_countryCode, geoplugin_continentCode), או אובייקט ריק (object)[] כשכל המקורות נכשלו. קראו ל-ip_info במקום לקרוא לה ישירות.

MISC::validate_ip_data() ו-MISC::normalize_ip_data()#

static function validate_ip_data($data, $parser)
static function normalize_ip_data($data, $parser)

עזרים פנימיים של get_ip_data_with_fallback. $parser הוא אחד מ-geoplugin, ipapi, ipapi_com, ipinfo. validate_ip_data מחזירה bool האם בתשובה יש קוד מדינה של שני תווים. normalize_ip_data ממירה את התשובה של כל ספק לצורה של geoplugin. אין סיבה לקרוא להן מקוד אתר.

MISC::ping_url()#

public static function ping_url($url)

שולחת בקשת GET בלי גוף (CURLOPT_NOBODY) עם timeout של 2 שניות, בעיקר כדי "להעיר" כתובת בלי לחכות לתשובה מלאה.

פרמטרמשמעות
$urlכתובת מלאה

החזרה: לא מוגדרת למשתמש: אל תסתמכו על ערך ההחזרה. הפונקציה עוקבת אחרי הפניות, ולכן גם היא פתוחה ל-SSRF כשהכתובת מגיעה מגולש. אחרי timeout הבקשה כבר נשלחה, אבל אין ודאות שהשרת הספיק לסיים אותה.

MISC::ping()#

public static function ping($url, $params = [])

מריצה כתובת פנימית של האתר ברקע, בתהליך PHP נפרד, בלי לחכות. כך מפעילים עבודה ארוכה (למשל system/ai_agent_runner/run מ-Form.php) בלי לתקוע את בקשת הגולש.

פרמטרמשמעות
$urlנתיב של האתר (בלי דומיין), למשל "system/cron/send_queue". מחרוזת לא ריקה
$paramsמערך key => value שמועבר כמו פרמטרי $_GET

החזרה: true כשהפקודה הופעלה (לא אומר שהיא הצליחה), false כש-$url ריק או לא מחרוזת.

איך זה עובד: נבנית פקודה <php> index.php url='<url>' key='<value>' ..., שמורצת עם nohup ו-exec ברקע. הפלט נזרק, ושגיאות נכתבות ל-/tmp/ping_errors.log. בתהליך החדש MISC::cli_check() הופכת את url= ל-REQUEST_URI. את בינארי ה-PHP מוצאת detect_cli_php().

MISC::ping("system/ai_agent_runner/run", ["job" => $jobId]);
מה צריך כדי שזה יעבוד

הפונקציה דורשת ש-exec יהיה פעיל (ב-disable_functions מושבת באחסון משותף רבים). ב-$params רק הערכים עוברים escapeshellarg, והמפתחות מוצמדים לפקודה כמו שהם: תנו רק מפתחות קבועים בקוד, לעולם לא מפתחות שמקורם בגולש. כל האתרים כותבים לאותו /tmp/ping_errors.log, והקובץ נדרס בכל קריאה. אין שום בדיקה שהתהליך הסתיים או הצליח, ולכן עבודה שחשוב לדעת על כשלה צריכה לרשום את עצמה (לוג או שורה בטבלה). ראו Cron.

MISC::detect_cli_php()#

public static function detect_cli_php(): string

מוצאת את קובץ ה-PHP שמתאים להרצה משורת פקודה. היא בודקת לפי הסדר /usr/local/bin/php, /usr/bin/php, ואחר כך נתיבי cPanel מ-ea-php85 עד ea-php74, ואז את PHP_BINARY (כשאינו lsphp או FPM). אם לא נמצא כלום מוחזר /usr/local/bin/php. התוצאה נשמרת בזיכרון לשאר הבקשה.

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

עורכי טקסט (CKEditor)#

שלוש הפונקציות הבאות כמעט זהות: כל אחת מחזירה HTML של textarea שהופך לעורך CKEditor 4, וקוראת ל-PAGE::add_asset("assets/ckeditor/ckeditor.js"). כל אחת מגדירה גם פונקציית JavaScript בשם check_<name>() שמעדכנת את ה-textarea לפני שליחה. הן נבדלות בסרגל הכלים.

מתודהסרגל הכלים
create_editorFull
create_editor_miniBasic
create_editor_newnew_editor
public static function create_editor($name, $text='', $height='450', $width='800')
public static function create_editor_mini($name, $text='', $height = "300", $width = "100%")
public static function create_editor_new($name, $text='', $height='450', $width='800')
פרמטרמשמעות
$nameשם השדה. משמש גם כ-name של ה-textarea וגם בשם פונקציית ה-JS ובבורר
$textהתוכן ההתחלתי
$height, $widthמידות העורך. ברירות המחדל שונות בין הפונקציות, כמו בחתימה

החזרה: string של HTML ו-JavaScript להדפסה בטופס.

$name ו-$text אינם מסוננים
$name נכנס כמו שהוא לשם פונקציית JavaScript ולבורר, ולכן שם שמגיע מגולש מאפשר הזרקת קוד. השתמשו בשמות קבועים ([a-z0-9_]) בלבד. CKEditor 4 עצמו כבר אינו מתוחזק, וקיימת בו סיכון של XSS בתוכן שנשמר. נקו את ה-HTML שנשמר מהעורך לפני הצגתו לגולשים.

echo MISC::create_editor_mini("description", $row["description"], "250", "100%");

עזרים ישנים או שבורים#

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

מתודהמצב
ae_detect_ie()עובדת: true כש-User-Agent מכיל Trident או MSIE. מיותרת כיום
ieversion()Fatal: קוראת ל-ae_detect_ie() כפונקציה גלובלית ללא MISC::, ופונקציה גלובלית כזו לא קיימת, ולכן כל קריאה זורקת שגיאה על פונקציה לא מוגדרת
heb2utf($s)שבורה: משתמשת ב-$t לפני שהוא מוגדר (notice, ובמצבים מסוימים שגיאה). ממירה windows-1255 ל-UTF-8
IsHebrew($string)לא אמינה: ה-pattern "[א-ת]" משתמש ב-[ ו-] כתוחמים ולא כמחלקת תווים, ובלי דגל u. אל תסתמכו על התוצאה. השתמשו ב-preg_match('/\p{Hebrew}/u', $s)
MySubstr($string, $start, $length = null)חיתוך מחרוזת עברית דרך windows-1255 ו-iconv. תווים שאינם עברית ואנגלית (אימוג'י, למשל) אובדים. העדיפו mb_substr
LogFile($filename, $dump)לא static (אותה בעיה כמו MakeEmailAttach), כותבת print_r($dump) לקובץ עם 'w' (דורסת) ו-die אם הקובץ לא נפתח. השתמשו ב-error_log
sanitize_output($buffer)מחזירה את הקלט כמו שהוא: השורה הראשונה היא return $buffer;, והקוד שאחריה לא מורץ
encode/decodeראו פרק ההצפנה: הפרמטר $key שמופיע בהערות הקוד אינו בשימוש

MISC::ieversion()#

public static function ieversion()
Fatal

קריאה אליה מפילה את הבקשה. אל תקראו לה. החליפו ב-MISC::ae_detect_ie() אם אתם באמת צריכים לזהות IE.

MISC::ae_detect_ie()#

public static function ae_detect_ie()

החזרה: bool: האם ה-User-Agent מכיל Trident או MSIE.

MISC::heb2utf()#

public static function heb2utf($s)
פרמטרמשמעות
$sמחרוזת בקידוד windows-1255

החזרה: string ב-UTF-8, אבל $t אינו מאותחל. השתמשו ב-iconv("windows-1255", "UTF-8", $s).

MISC::IsHebrew()#

public static function IsHebrew($string)
פרמטרמשמעות
$stringמחרוזת

החזרה: תוצאת preg_match (1, 0 או false), לא bool. ראו את הטבלה למעלה: לא אמינה.

MISC::MySubstr()#

public static function MySubstr($string, $start, $length = null)
פרמטרמשמעות
$stringמחרוזת UTF-8
$startמיקום התחלה (בבתים של windows-1255, כלומר בתווים)
$lengthאורך. אם null או 0, עד סוף המחרוזת

החזרה: string.

MISC::LogFile()#

function LogFile($filename, $dump)

החזרה: אין. כותבת את print_r($dump, true) ל-$filename ומוחקת את תוכנו הקודם. אינה static, ולכן הקריאה MISC::LogFile() נכשלת ב-PHP 8.

MISC::sanitize_output()#

public static function sanitize_output($buffer)

החזרה: $buffer ללא שינוי. אל תפעילו אותה כ-ob_start callback בציפייה לכיווץ HTML: לכך משמשת PAGE::minify_html (PAGE).

ראו גם#

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