ROUTER הופך כתובת בקשה לשלושה משתנים ב-$_GET (module או sys_controller, pname, id) ו-MODULE טוען את הקונטרולר המתאים וקורא למתודה. שתי המחלקות סטטיות, נטענות אוטומטית עם שאר ה-collections, והן ב-system/collections/ROUTER.php וב-system/collections/MODULE.php (system/ הוא api/core בריפו). הדף הזה הוא הרפרנס; הסיפור המלא של בקשה מופיע ב-מחזור חיי בקשה, והמדריכים ב-ניתוב, כתובות ידידותיות ו-קונטרולרים של אתר.
{admin} בדף הזה הוא CONFIG::$admin_url. הערכים CONFIG::$admin_url, CONFIG::$default_module, CONFIG::$site_url ו-CONFIG::$models_folder מוגדרים בקונפיגורציית האתר (ראו CONFIG_USER).
איך הבקשה מגיעה לכאן#
core.php קורא ל-PAGE::load($_SERVER['REQUEST_URI']), ו-PAGE::load עושה בסדר הזה:
ROUTER::parse_friendly_url($url): ממלא$_GETומציבCONFIG::$system_type("client"או"admin").- בדיקת דומיין (רק ב-client, אלא אם
CONFIG::$ignore_domain_check === true):SERVER_NAMEחייב להיותplatform_data["domain"]או אחד מ-allowed_domains, אחרתdie("URL ERROR"). הקונטרולריםminifyו-Toolsפטורים מהבדיקה. ROUTER::redirections(): הפניות 301 (client בלבד).- טעינת
application/includesואזLANGS::init_words(). - client:
MODULE::get_html($_GET["sys_controller"], $_GET["pname"], true)כשישsys_controller, אחרתMODULE::get_html($_GET["module"], $_GET["pname"]). admin:AdminModule(ראו סקירת פאנל הניהול).
ROUTER#
משתנים סטטיים#
| משתנה | טיפוס | ברירת מחדל | משמעות |
|---|---|---|---|
ROUTER::$trace | array | [] | רשימת שלבי ההחלטה של הבקשה הנוכחית. כל איבר: stage ועוד מפתחות url, match, note. מתמלא תמיד, בלי קשר לתצוגה |
ROUTER::parse_friendly_url()#
public static function parse_friendly_url(string $url)
הלב של הניתוב. מקבל נתיב עם query string אופציונלי (בדרך כלל REQUEST_URI), לא מחזיר ערך, ומשפיע על $_GET ועל CONFIG::$system_type. הסדר, מהראשון שמתאים:
| # | תנאי על הנתיב (אחרי trim($url, "/")) | מה קורה |
|---|---|---|
| 0 | יש ? | $_GET מוחלף ב-parse_str של ה-query. פרמטרי מעקב (cache_engine::$tracking_params: utm_*, gclid, fbclid ועוד) שהיו ב-$_GET לפני כן מועתקים פנימה |
| 1 | MASK::in($url) | במצב הסוואה מחזיר את הנתיב האמיתי (ראו מצב הסוואה). קובץ סטטי מוסווה מוגש ומסיים את הבקשה |
| 2 | ריק (דף הבית) | system_type="client", module=CONFIG::$default_module, pname="" |
| 3 | <32 תווי hex>.txt | קובץ המפתח של IndexNow. מוגש רק אם השם הוא המפתח של האתר (SeoIndexNow::serve_engine_key); אחרת הניתוב ממשיך |
| 4 | poosh-sw.js | Service Worker של POOSH מוגש מ-assets/wizzo_poosh/poosh-sw.js ו-die |
| 5 | tjs.js | ה-SDK של TARGET מוגש מ-assets/wizzo_target/tjs.js ו-die |
| 6 | pjs.js | ווידג'ט Pilot: פרוקסי עם מטמון של 5 דקות. כשל בשליפה מחזיר הערת JS ריקה, לא נשמר במטמון |
| 7 | p/<slug> | דף NEWPAGE: sys_controller=newpage_public, np_slug=rawurldecode(slug) |
| 8 | pg.js / pg.css | נכסי NEWPAGE: פרוקסי עם מטמון לפי ?v= |
| 9 | tag_manager.js | sys_controller=minify, pname=tag_manager |
| 10 | minify/<id>.js או .css | sys_controller=minify, pname="", id=<id> |
| 11 | assets/<x>.scss.css | sys_controller=minify, pname=scss, file=<core_path>/assets/<x> |
| 12 | <x>.scss.css | sys_controller=minify, pname=scss, file=<x> |
| 13 | assets/<x>.<ext> כש-ext מהרשימה למטה והקובץ קיים ב-core_path/assets | הקובץ מוגש עם Content-Type לפי הסיומת, Access-Control-Allow-Origin: *, ו-die. אם הקובץ לא קיים הניתוב ממשיך |
| 14 | {admin} ואחריו 0 עד 3 קטעים | system_type="admin" (ראו למטה) |
| 15 | כל השאר | system_type="client": seoUrl, ואז system controller או module (ראו למטה) |
הסיומות בסעיף 13: js css jpg jpeg png gif ico svg dic txt woff2 woff otf pdf doc docx xls.
הביטוי של סעיף 13 אינו מעוגן בסוף הנתיב והקטע (.+) מקבל גם נקודות. אל תשימו קבצים רגישים מתחת ל-system/assets/ בתקווה שהם "לא מקושרים": כל קובץ בסיומת מהרשימה שם נגיש ציבורית. הנכסים מוגשים דרך PHP (כל ה-bootstrap רץ) בלי Cache-Control (פרט ל-wzeditor/plugins/), כך שכדאי להגיש אותם ישירות מהשרת דרך .htaccess.
נתיבי אדמין#
CONFIG::$admin_url מוכנס לביטוי רגולרי כמו שהוא, בלי preg_quote. השתמשו בשם של אותיות ומספרים בלבד (למשל wizzocms).
| נתיב | module | pname | id |
|---|---|---|---|
{admin} | "" | "" | לא מוגדר |
{admin}/Pages | Pages | "" | לא מוגדר |
{admin}/Pages/12 | Pages | "" | 12 (קטע מספרי בלבד) |
{admin}/Pages/insert | Pages | insert | לא מוגדר |
{admin}/Pages/insert/12 | Pages | insert | 12 (כל מה שאחרי הקטע השני, כולל /) |
אחרי ההתאמה, אם מוגדרת רשימת מדינות לאדמין (admin_countries::get_active() שונה מ-false), הבקשה נבדקת מול המדינה של ה-IP. בקשה שנדחתה מופנית ל-/ עם exit. פטורות: בקשה פנימית שבה REMOTE_ADDR == SERVER_ADDR, וכתובות ש-WIZZO_NET::is_wizzo() מזהה. ראו התחברות, סשנים ו-WIZZO ID.
WIZZO_NET::is_wizzo() מקבל גם את הכותרות CF-Connecting-IP ו-X-Forwarded-For. באתר שאין לפניו proxy מהימן הן ניתנות לזיוף, ולכן שער המדינות אינו גבול אבטחה אמיתי. ראו מודל האבטחה.נתיבי client#
- seoUrl מדויק: שורה ב-
CRM_seoUrlעםregularexp = "0"ו-sysnameשווה לנתיב (או לנתיב אחריurldecode). התוצאה היאpageurlשל השורה. - seoUrl רגולרי: כל שורה עם
regularexp = "1"לפי סדרord.sysnameהוא ביטוי PCRE מלא עם תוחמים (למשל#^blog/(\d+)$#), ו-pageurlהוא ההחלפה, כשהתו$מומר ל-\(כלומר$1הופך ל-\1). ההתאמה נבדקת על הנתיב ואז עלurldecodeשלו. השורה הראשונה שמתאימה מנצחת. ביטוי לא תקין מושתק ב-@ופשוט נכשל. - אין התאמה:
pageUrl = $url, כלומר הנתיב עצמו משמש כנתיב קונטרולר.
אם pageUrl מכיל ?, הפרמטרים שאחריו נכנסים ל-$_GET עם array_merge (הם גוברים על קיימים). אז הנתיב מותאם לפי הטבלה, מהמעלה למטה, וההתאמה הראשונה מנצחת:
| תבנית | sys_controller | module | pname | id |
|---|---|---|---|---|
system/<c> | <c> | "" | ||
system/<c>/<n> (n מספרי) | <c> | "" | <n> | |
system/<c>/<p> | <c> | <p> | ||
system/<c>/<p>/<rest> | <c> | <p> | <rest> | |
<m> | <m> | "" | ||
<m>/<n> (n מספרי) | <m> | "" | <n> | |
<m>/<p> | <m> | <p> | ||
<m>/<p>/<rest> | <m> | <p> | <rest> |
news/5 נקרא כ-module=news, id=5 ולא כמתודה בשם 5. כדי לקרוא למתודה item עם id השתמשו ב-news/item/5. מתודה לא יכולה להיות מספר.router error ו-/a//b
נתיב שלא מתאים לאף תבנית (למשל רצף // באמצע) מסתיים ב-die("router error") עם HTTP 200 וטקסט חופשי, לא בדף 404. חסמו או נרמלו כפילויות / ב-.htaccess.$_GET מוחלף כולו
כשיש query string, $_GET נבנה מחדש מה-query. ערך שקוד ב-application/includes/init.php הציב ב-$_GET לפני כן נעלם (פרמטרי המעקב שרדו מאז 5.0.114). explode("?") מתבצע בלי הגבלה, כך שמה שאחרי ? שני נחתך.הכתובת 404#
כשהנתיב הוא בדיוק 404, הניתוב שולח HTTP/1.1 404 Not Found ומציב PAGE::$is_404 = true, ואז ממשיך כרגיל. כלומר דף 404 הוא module בשם 404 (או שורת seoUrl עם sysname = "404"). אם אין כזה, התוצאה היא <h1>404 - Page not found</h1>. ראו 404, 503 ושגיאות.
ROUTER::redirections()#
public static function redirections()
מריץ הפניות 301 מהטבלה CRM_redirections (עמודות url_from, url_to; ניהול בפאנל redirections, ראו הפניות 301). נקרא מ-PAGE::load רק ל-client.
| נושא | התנהגות |
|---|---|
| מטמון | מפתח redirections, 168 שעות (24*7), לא תלוי פלטפורמה. הפאנל מוחק אותו בשמירה ובמחיקה של שורה; שינוי ישיר ב-DB דורש cache_engine::remove("redirections") |
| מפתח ההתאמה | נתיב הבקשה בלי / מוביל וסוגר ובלי query string, כמות שהוא או אחרי urldecode. התאמה מדויקת בלבד, ללא תווים כלליים |
| יעד | אם url_to לא מכיל את המחרוזת http מוסיפים לו / בתחילתו. הכתובת יכולה להיות חיצונית |
| query string | אם הבקשה הכילה query, הוא מתווסף ליעד (? או & לפי הצורך) |
| פלט | HTTP/1.1 301 Moved Permanently, Location: <יעד>, exit |
// דוגמה: רשומה url_from="old-page", url_to="new-page"
// GET /old-page?x=1 -> 301 Location: /new-page?x=1
http
כתובת יחסית שמכילה את הרצף http (למשל blog/http-basics) תיחשב אבסולוטית ולא תקבל / בתחילתה. כתבו יעדים יחסיים עם / מוביל מפורש.ROUTER::get_seo_tbl()#
static function get_seo_tbl()
מחזיר את כל שורות CRM_seoUrl ממוינות לפי ord ASC, מתוך מטמון (cache_engine::get("seoUrl_tbl", ...), ברירת מחדל 24 שעות, פר פלטפורמה). הפאנלים Seo, seo_center והשדות FormInput_SEO מוחקים את המפתח בשמירה. קוד שכותב ל-CRM_seoUrl ישירות צריך לקרוא cache_engine::remove("seoUrl_tbl").
ROUTER::get_by_url()#
public static function get_by_url($url)
כתובת פנימית לכתובת ידידותית. מסיר את CONFIG::$site_url מההתחלה, מחפש שורה שבה pageurl שווה למחרוזת, ומחזיר את ה-sysname שלה. אין התאמה: מחזיר את הקלט.
$friendly = ROUTER::get_by_url("pages/12"); // "about-us" אם קיימת שורה כזו, אחרת "pages/12"
ROUTER::get_by_friendly()#
public static function get_by_friendly($url)
ההפך: מקבל sysname ומחזיר pageurl. אין התאמה: מחזיר את הקלט.
ROUTER::remove_by_url()#
public static function remove_by_url($url)
מוחק מ-CRM_seoUrl את השורות שבהן pageurl שווה למחרוזת (DB::delete("seoUrl", ["pageurl" => $url])), בלי ערך החזרה. המתודה לא מנקה את מטמון seoUrl_tbl; קראו cache_engine::remove("seoUrl_tbl") אחריה.
חלק מהקוד כותב pageUrl/sysName וחלק pageurl/sysname. ב-MySQL זה זהה, אבל במפתחות של שורה שחזרה מהמטמון (get_seo_tbl) השמות הם באותיות קטנות כפי שמוגדרים בטבלה. כתבו pageurl ו-sysname.
ROUTER::is_trace_on(), trace(), trace_render()#
public static function is_trace_on()
public static function trace($stage, $detail = [])
public static function trace_render()
| מתודה | פעולה |
|---|---|
is_trace_on() | true כשה-URL מכיל __router_trace וגם הצופה הוא אדמין מחובר, 127.0.0.1/::1, או REMOTE_ADDR == SERVER_ADDR |
trace($stage, $detail) | מוסיף איבר ל-ROUTER::$trace: array_merge(["stage" => $stage], $detail) (מפתחות מקובלים url, match, note). בהפעלה הראשונה כשהתצוגה מותרת רושם register_shutdown_function שמרנדר את הפאנל. אפשר לקרוא לה מקוד האתר כדי להוסיף שלבים משלכם |
trace_render() | מדפיס <div id="router-trace"> קבוע בתחתית העמוד עם טבלת השלבים, system_type, is_404 ו-$_GET הסופי. נרשם אוטומטית, אין סיבה לקרוא לה ישירות |
הפעלה: הוסיפו ?__router_trace=1 לכל כתובת. שימוש מעשי ב-דיבוג ניתוב.
MODULE#
MODULE::get_html()#
public static function get_html($module, $page = "index", $is_system = false)
| פרמטר | משמעות |
|---|---|
$module | שם הקונטרולר ($_GET["module"] או $_GET["sys_controller"]). false נחשב כשגיאה |
$page | שם המתודה. מחרוזת ריקה הופכת ל-index |
$is_system | true לקונטרולר מערכת (core_path/controllers), false לקונטרולר אתר |
מחזיר את התוכן (מחרוזת) שהמתודה החזירה אחרי $controller->wrapper($content). התהליך:
קונטרולר אתר ($is_system = false)
MISC::get_modules_tbl()טוען אתCRM_modulesממפתח לפיmoduleName(מטמוןmodules_lang<lang>, 168 שעות).- אם
$modules_tbl[$module]קיים ו-active == "1":MODULE::get($module)טוען אתapplication/controllers/<module>.php. חסר קובץ או מחלקה:ERRORPAGE. - אם המתודה
is_callable(ציבורית ומוגדרת, כולל מתודות שירשה המחלקה) היא נקראת; אחרתERRORPAGE. - מודול שלא רשום או לא פעיל: אם קיים קובץ הקונטרולר והמבקר הוא אדמין, מודפס טופס "Click here to activate this module" שמפנה ל-
/{admin}/Modules/insertוההרצה נעצרת (die). בכל מקרה אחרERRORPAGE.
קונטרולר מערכת ($is_system = true)
טוען core_path/controllers/<module>.php בלי בדיקת CRM_modules. הקובץ צריך להתקיים והמחלקה להיקרא בדיוק כמו הקובץ; אחרת ERRORPAGE.
אין רשימה לבנה של מתודות וגם לא בדיקת הרשאה מרכזית. כל מתודה ציבורית בקונטרולר אתר פעיל, בקונטרולר מערכת, ובמחלקות שהוא יורש (view, system_view, wrapper), ניתנת לקריאה בכתובת. כל בדיקת הרשאה (ADMIN::is_admin(), טוקן, IP) היא באחריות המתודה. מתודות עזר: private או protected. כל קונטרולר מערכת נגיש ב-/system/<name>/<method>; הרשימה ב-כל ה-URLs של המערכת.
הקובץ news.php חייב להגדיר class news. ב-Linux News ו-news הם קבצים שונים, אבל class_exists אינו תלוי רישיות, ולכן שם עם אותיות גדולות עלול להחזיר 404 מבלבל. הימנעו משמות שכבר תפוסים (user, storage, Tools). אין תמיכה ב-namespace.
ערכי ההחזרה המיוחדים של מתודת קונטרולר#
| ערך | התוצאה |
|---|---|
| מחרוזת רגילה | התוכן של העמוד (אחרי wrapper) |
"ERRORPAGE" | MODULE::errorpage(): תהליך 404 (למטה) |
"REQUIRE_LOGIN" | מוחזר סקריפט $(function(){ $('#page').trigger('REQUIRE_LOGIN'); }); שה-JS של האתר מאזין לו. נדרש jQuery |
| אחר | מוחזר כמות שהוא. מתודה שמדפיסה בעצמה (echo) ויוצאת (exit/die) עוקפת את כל המערכת |
פרמטר ה-pmode ב-$_GET: empg או inner גורמים ל-PAGE::output() להחזיר את התוכן בלי ה-theme (שימושי ל-AJAX ולנקודות קצה). קונטרולרי מערכת רבים מציבים $_GET["pmode"] = "empg" בעצמם בתחילת המתודה.
MODULE::get()#
public static function get($module, $is_system = false)
טוען את קובץ הקונטרולר (require_once) ומחזיר new $module(). מחזיר false כשהקובץ חסר או שאין מחלקה בשם הזה. שימושי לטעינת קונטרולר אחר מתוך קוד:
$mdl = MODULE::get("news");
if ($mdl) $html = $mdl->latest();
MODULE::get_controller_file_src()#
public static function get_controller_file_src($controller, $is_system = false)
מחזיר את הנתיב לקובץ: CONFIG::$core_path . "/controllers/<name>.php" כש-$is_system, אחרת CONFIG::$base_path . "/application/controllers/<name>.php". מחזיר false אם הקובץ לא קיים (is_file).
MODULE::errorpage()#
static function errorpage()
נקראת כשקונטרולר מחזיר "ERRORPAGE". לא להפעיל ישירות (ההחזרה "ERRORPAGE" היא הדרך). הסדר:
- אם
DB::had_error()(שאילתה נכשלה בבקשה הזו):MISC::service_unavailable("routing tables unavailable: ..."), כלומר HTTP 503 עםRetry-After: 120. הסיבה: טבלאות הניתוב (seoUrl,modules) מחזירות ריקות כשה-DB נחנק, וכל עמוד תקין היה מקבל 404 ונמחק מהאינדקס של גוגל. ROUTER::parse_friendly_url("404"): שולח את כותרת 404, מציבPAGE::$is_404, ומציב מחדש את$_GETלפי הכתובת404.PAGE::$cache_this_page = false: דף שגיאה לא נכנס למטמון העמודים.- אם
moduleשהתקבל פעיל ויש בו המתודה המבוקשת, היא נקראת (כך דף 404 מעוצב שנמצא ב-module). - אחרת מוחזר
<h1>404 - Page not found</h1>.
סיכום קודי תגובה של הניתוב#
| מצב | תגובה |
|---|---|
| module או מתודה לא קיימים | 404 (דרך errorpage) |
| בעיית DB בזמן ניתוב | 503 + Retry-After: 120 |
הפניה מתאימה ב-CRM_redirections | 301 |
נתיב ללא תבנית (router error) | 200 עם טקסט |
| דומיין לא מורשה | 200 עם הטקסט URL ERROR |
| בקשת אדמין ממדינה חסומה | 302 ל-/ |
פירוט נוסף ב-הודעות שגיאה.
wz_controller והמחלקות הקשורות#
הקובץ MODULE.php מגדיר גם את מחלקות הבסיס. bgl_controller, bgl_model ו-bgl_loader הם כינויים ריקים לשמות הקודמים (class bgl_controller extends wz_controller {}); bgl_theme (ב-collections/theme.php) יורש מ-bgl_controller. קוד חדש יכול להשתמש בכל אחד מהשמות.
wz_controller#
class wz_controller
{
var $load; // bgl_loader
static $loader; // bgl_loader (המופע האחרון שנוצר)
function __construct()
function wrapper($pageContent)
function view($tpl, $vars = array())
function system_view($tpl, $vars = array())
public function __get($name)
public function __set($name, $value)
}
| מתודה / מאפיין | משמעות |
|---|---|
__construct() | יוצר $this->load = new bgl_loader($this) וגם wz_controller::$loader. קונטרולר שמגדיר בנאי משלו חייב לקרוא ל-parent::__construct() |
$this->load | wz_loader לטעינת מודלים וספריות (ראו למטה) |
wrapper($pageContent) | נקרא על תוצאת כל מתודה של הקונטרולר לפני ההחזרה. ברירת מחדל: מחזיר את התוכן כמות שהוא. דרסו כדי לעטוף או להמיר (למשל להחזיר JSON מכל המתודות) |
view($tpl, $vars) | מרנדר את application/views/<tpl>.tpl דרך FILES::html_template ומחזיר מחרוזת. ה-engine (Smarty או PHP גולמי) נקבע ב-CONFIG::$use_smarty |
system_view($tpl, $vars) | אותו דבר מתוך core_path/views/<tpl>.tpl (תבניות הליבה) |
__get / __set | מאפיינים דינמיים נשמרים במערך פנימי; קריאה למאפיין שלא הוגדר מחזירה null ולא מייצרת אזהרה. כך load->model() מחבר מודל לקונטרולר |
// application/controllers/news.php
class news extends wz_controller
{
function index()
{
$rows = DB::query("news", ["is_active" => 1], "id DESC")->get_all();
return $this->view("news/list", ["rows" => $rows]); // application/views/news/list.tpl
}
// לא נגיש כתובת: private
private function format($row) { return $row; }
}
קובץ ב-application/controllers/ לא מספיק. צריך שורה ב-CRM_modules עם moduleName ו-active = 1, ואז ניקוי מטמון modules_lang<lang>. אדמין שמבקר בכתובת רואה את טופס ההפעלה (ראו למעלה). פירוט ב-קונטרולרים של אתר.
wz_loader ($this->load)#
class wz_loader
{
var $parent;
function __construct(&$parent = null)
function model($path)
function library($path)
}
| מתודה | פעולה |
|---|---|
model($path) | דורש CONFIG::$models_folder . "/" . $path . ".php" (require_once). הקובץ חסר: die("ERROR: model $path is not exists"). שם המחלקה הוא basename($path); נוצר מופע וחובר לקונטרולר: $this->parent->{basename($path)} = new <class> |
library($path) | אם core_path/libraries/<path> הוא תיקייה, כל קבצי ה-PHP בה נטענים (FILES::include_dir); אחרת require_once core_path/libraries/<path>.php. לא יוצר מופע |
class news extends wz_controller
{
function index()
{
$this->load->model("m_news"); // application/models/m_news.php, class m_news
$rows = $this->m_news->latest(10);
return $this->view("news/list", ["rows" => $rows]);
}
}
wz_model#
class wz_model extends wz_controller
{
function __construct()
static function merge(&$settings, $m_settings)
}
wz_model יורש את כל wz_controller (כולל view).
merge(&$settings, $m_settings) ממזג הגדרות לתוך מערך ברירות מחדל: רק מפתחות שכבר קיימים ב-$settings נדרסים; מחרוזות עוברות trim; ערך ריק ("", שאינו bool) נדלג, כך שלא דורס ברירת מחדל; מערכים ואובייקטים מועתקים כמות שהם.
$settings = ["limit" => 10, "order" => "id DESC"];
wz_model::merge($settings, ["limit" => 25, "order" => "", "unknown" => 1]);
// $settings == ["limit" => 25, "order" => "id DESC"]
bgl_theme#
המחלקה bgl_theme (ב-system/collections/theme.php) יורשת מ-bgl_controller ולכן יש לה view, load ו-wrapper. כך ערכת נושא משתמשת ב-$this->view() בדיוק כמו קונטרולר. ראו ערכות נושא.