ROUTER ו-MODULE

רפרנס מלא למחלקות ROUTER ו-MODULE ולבסיס הקונטרולרים wz_controller: כל מתודה ציבורית, סדר ההתאמה של כתובות, משתני $_GET שהניתוב מציב, וערכי ההחזרה המיוחדים של קונטרולר.

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

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 עושה בסדר הזה:

  1. ROUTER::parse_friendly_url($url): ממלא $_GET ומציב CONFIG::$system_type ("client" או "admin").
  2. בדיקת דומיין (רק ב-client, אלא אם CONFIG::$ignore_domain_check === true): SERVER_NAME חייב להיות platform_data["domain"] או אחד מ-allowed_domains, אחרת die("URL ERROR"). הקונטרולרים minify ו-Tools פטורים מהבדיקה.
  3. ROUTER::redirections(): הפניות 301 (client בלבד).
  4. טעינת application/includes ואז LANGS::init_words().
  5. client: MODULE::get_html($_GET["sys_controller"], $_GET["pname"], true) כשיש sys_controller, אחרת MODULE::get_html($_GET["module"], $_GET["pname"]). admin: AdminModule (ראו סקירת פאנל הניהול).

ROUTER#

משתנים סטטיים#

משתנהטיפוסברירת מחדלמשמעות
ROUTER::$tracearray[]רשימת שלבי ההחלטה של הבקשה הנוכחית. כל איבר: 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 לפני כן מועתקים פנימה
1MASK::in($url)במצב הסוואה מחזיר את הנתיב האמיתי (ראו מצב הסוואה). קובץ סטטי מוסווה מוגש ומסיים את הבקשה
2ריק (דף הבית)system_type="client", module=CONFIG::$default_module, pname=""
3<32 תווי hex>.txtקובץ המפתח של IndexNow. מוגש רק אם השם הוא המפתח של האתר (SeoIndexNow::serve_engine_key); אחרת הניתוב ממשיך
4poosh-sw.jsService Worker של POOSH מוגש מ-assets/wizzo_poosh/poosh-sw.js ו-die
5tjs.jsה-SDK של TARGET מוגש מ-assets/wizzo_target/tjs.js ו-die
6pjs.jsווידג'ט Pilot: פרוקסי עם מטמון של 5 דקות. כשל בשליפה מחזיר הערת JS ריקה, לא נשמר במטמון
7p/<slug>דף NEWPAGE: sys_controller=newpage_public, np_slug=rawurldecode(slug)
8pg.js / pg.cssנכסי NEWPAGE: פרוקסי עם מטמון לפי ?v=
9tag_manager.jssys_controller=minify, pname=tag_manager
10minify/<id>.js או .csssys_controller=minify, pname="", id=<id>
11assets/<x>.scss.csssys_controller=minify, pname=scss, file=<core_path>/assets/<x>
12<x>.scss.csssys_controller=minify, pname=scss, file=<x>
13assets/<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).

נתיבmodulepnameid
{admin}""""לא מוגדר
{admin}/PagesPages""לא מוגדר
{admin}/Pages/12Pages""12 (קטע מספרי בלבד)
{admin}/Pages/insertPagesinsertלא מוגדר
{admin}/Pages/insert/12Pagesinsert12 (כל מה שאחרי הקטע השני, כולל /)

אחרי ההתאמה, אם מוגדרת רשימת מדינות לאדמין (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#

  1. seoUrl מדויק: שורה ב-CRM_seoUrl עם regularexp = "0" ו-sysname שווה לנתיב (או לנתיב אחרי urldecode). התוצאה היא pageurl של השורה.
  2. seoUrl רגולרי: כל שורה עם regularexp = "1" לפי סדר ord. sysname הוא ביטוי PCRE מלא עם תוחמים (למשל #^blog/(\d+)$#), ו-pageurl הוא ההחלפה, כשהתו $ מומר ל-\ (כלומר $1 הופך ל-\1). ההתאמה נבדקת על הנתיב ואז על urldecode שלו. השורה הראשונה שמתאימה מנצחת. ביטוי לא תקין מושתק ב-@ ופשוט נכשל.
  3. אין התאמה: pageUrl = $url, כלומר הנתיב עצמו משמש כנתיב קונטרולר.

אם pageUrl מכיל ?, הפרמטרים שאחריו נכנסים ל-$_GET עם array_merge (הם גוברים על קיימים). אז הנתיב מותאם לפי הטבלה, מהמעלה למטה, וההתאמה הראשונה מנצחת:

תבניתsys_controllermodulepnameid
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>
קטע מספרי הוא תמיד id
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_systemtrue לקונטרולר מערכת (core_path/controllers), false לקונטרולר אתר

מחזיר את התוכן (מחרוזת) שהמתודה החזירה אחרי $controller->wrapper($content). התהליך:

קונטרולר אתר ($is_system = false)

  1. MISC::get_modules_tbl() טוען את CRM_modules ממפתח לפי moduleName (מטמון modules_lang<lang>, 168 שעות).
  2. אם $modules_tbl[$module] קיים ו-active == "1": MODULE::get($module) טוען את application/controllers/<module>.php. חסר קובץ או מחלקה: ERRORPAGE.
  3. אם המתודה is_callable (ציבורית ומוגדרת, כולל מתודות שירשה המחלקה) היא נקראת; אחרת ERRORPAGE.
  4. מודול שלא רשום או לא פעיל: אם קיים קובץ הקונטרולר והמבקר הוא אדמין, מודפס טופס "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" היא הדרך). הסדר:

  1. אם DB::had_error() (שאילתה נכשלה בבקשה הזו): MISC::service_unavailable("routing tables unavailable: ..."), כלומר HTTP 503 עם Retry-After: 120. הסיבה: טבלאות הניתוב (seoUrl, modules) מחזירות ריקות כשה-DB נחנק, וכל עמוד תקין היה מקבל 404 ונמחק מהאינדקס של גוגל.
  2. ROUTER::parse_friendly_url("404"): שולח את כותרת 404, מציב PAGE::$is_404, ומציב מחדש את $_GET לפי הכתובת 404.
  3. PAGE::$cache_this_page = false: דף שגיאה לא נכנס למטמון העמודים.
  4. אם module שהתקבל פעיל ויש בו המתודה המבוקשת, היא נקראת (כך דף 404 מעוצב שנמצא ב-module).
  5. אחרת מוחזר <h1>404 - Page not found</h1>.

סיכום קודי תגובה של הניתוב#

מצבתגובה
module או מתודה לא קיימים404 (דרך errorpage)
בעיית DB בזמן ניתוב503 + Retry-After: 120
הפניה מתאימה ב-CRM_redirections301
נתיב ללא תבנית (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->loadwz_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; }
}
קונטרולר חדש הוא 404 עד שיש לו שורה פעילה

קובץ ב-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() בדיוק כמו קונטרולר. ראו ערכות נושא.

ראו גם#

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