העמוד הראשון שלך

מדריך מעשי שבונה עמוד אמיתי באתר WIZZO CMS מאפס, קונטרולר, תבנית Smarty, כותרת SEO ורישום מודול, ומסביר מה לבדוק כשמתקבל 404.

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

במדריך הזה תבנו עמוד אחד מתחילתו ועד סופו: קונטרולר ב-application/controllers, תבנית ב-application/views, כותרת SEO, שורת רישום בטבלת CRM_modules, וגלישה אל /hello ואל /hello/card/7. בסוף יש רשימת תקלות נפוצות, ובראשן ה-404 המפורסם של "יצרתי קובץ ושום דבר לא קורה".

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

הליבה יושבת ב-api/core/ בריפו ונפרסת ל-system/core/ באתר. {admin} בכתובות הניהול הוא הערך של CONFIG::$admin_url.

איך עמוד נולד: שלוש חוליות#

כתובת רגילה באתר נקראת כך: הקטע הראשון הוא שם המודול, השני הוא שם המתודה והשלישי הוא ה-id.

כתובתמודולמתודה$_GET["id"]
/hellohelloindexלא קיים
/hello/cardhellocardלא קיים
/hello/card/7hellocard7
/hello/7helloindex7

כדי שכתובת כזו תחזיר עמוד צריכים להתקיים שלושה דברים בבת אחת. הקובץ application/controllers/hello.php עם מחלקה בשם hello, שורה פעילה בטבלת CRM_modules עם moduleName = 'hello', ומתודה ציבורית בשם המבוקש. חסר אחד מהם, והתשובה היא 404.

למה צריך גם שורה בטבלה

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

שלב 1: הקונטרולר#

צרו את הקובץ application/controllers/hello.php. שם הקובץ, שם המחלקה ושם המודול זהים, באותיות קטנות.

<?php

class hello extends wz_controller
{
    // /hello
    function index()
    {
        SEO::set("title", "שלום מ-WIZZO CMS");
        SEO::set("description", "העמוד הראשון שלי על WIZZO CMS: קונטרולר, תבנית ו-SEO.");

        return $this->view("hello/index", [
            "name"  => "עולם",
            "items" => ["קונטרולר", "תבנית", "שורת מודול"],
        ]);
    }

    // /hello/card/7
    function card()
    {
        $id = (int)($_GET["id"] ?? 0);

        $cards = [
            7 => ["title" => "כרטיס מספר שבע", "body" => "כאן יבוא התוכן מהמסד."],
            8 => ["title" => "כרטיס מספר שמונה", "body" => "עוד תוכן לדוגמה."],
        ];

        // לא קיים אצלנו: 404 אמיתי, לא עמוד ריק
        if (!isset($cards[$id])) return "ERRORPAGE";

        SEO::set("title", $cards[$id]["title"]);

        return $this->view("hello/card", ["card" => $cards[$id]]);
    }
}

כמה דברים שכדאי להבין מהקוד:

  • wz_controller היא מחלקת הבסיס של כל קונטרולר באתר (bgl_controller הוא כינוי ישן שלה). היא נותנת לכם את $this->view(), את $this->load (טעינת מודלים וספריות) ועוד.
  • מה שהמתודה מחזירה הוא תוכן העמוד. הליבה לוקחת את המחרוזת, מעבירה אותה דרך wrapper() ומשבצת אותה בתוך ה-theme. אל תעשו echo ואל תעשו die, אלא אם אתם מחזירים JSON (ראו בהמשך).
  • המחרוזת המיוחדת "ERRORPAGE" אומרת לליבה "אין כאן עמוד": היא מנתבת מחדש לעמוד ה-404 של האתר (פרטים ב-404, 503 ושגיאות).
  • SEO::set("title", ...) ממלא את תגיות הראש. קוראים לה לפני החזרת התוכן, והליבה מדפיסה את התגיות בעצמה. כל ה-API בעמוד תגיות SEO ו-Schema.
  • קוראים $_GET["id"] תמיד בהמרה. הערך מגיע מהכתובת, ולכן (int) הוא המינימום. כתובת כמו /hello/card/7/abc מגיעה אל id כמחרוזת 7/abc (הקטע השלישי אוסף את כל מה שנשאר), והמרה למספר מחזירה 7.
כל מתודה ציבורית היא כתובת

הליבה קוראת לכל מתודה שאפשר לקרוא לה (is_callable), כולל מתודות עזר שכתבתם למחלקה. מתודה שלא אמורה להיות נגישה מהרשת צריכה להיות private או protected. אותו הדבר נכון גם למתודות שהקונטרולר יורש (view, system_view, wrapper): בכתובת /hello/view הליבה תקרא ל-view() בלי פרמטרים.

בחירת שם למודול#

השם הוא גם קטע בכתובת וגם שם מחלקת PHP, ולכן:

  • אותיות לטיניות, ספרות וקו תחתון בלבד. שם עם מקף (my-page) הוא לא שם מחלקה חוקי; לכתובת עם מקף משתמשים ב-כתובות ידידותיות.
  • אותיות קטנות. הבדיקה מול CRM_modules רגישה לאותיות, ולכן /Hello לא יפתח את המודול hello.
  • לא שם של מחלקה קיימת בליבה. שמות מחלקות ב-PHP אינם רגישים לאותיות, ולכן קונטרולר בשם page או seo מתנגש במחלקות PAGE ו-SEO ונופל בשגיאה קטלנית. הרשימה המלאה ב-ה-Collections.

שלב 2: התבנית#

התבניות של האתר יושבות ב-application/views. הקריאה $this->view("hello/index", [...]) טוענת את application/views/hello/index.tpl: בלי הסיומת .tpl ובלי לוכסן בהתחלה. המשתנים במערך הופכים למשתני Smarty. צרו את application/views/hello/index.tpl:

<section class="hello">
    <h1>שלום {$name|escape}</h1>

    <ul>
        {foreach $items as $item}
            <li>{$item|escape}</li>
        {/foreach}
    </ul>

    <p><a href="/hello/card/7">לכרטיס מספר שבע</a></p>
</section>

ואת application/views/hello/card.tpl:

<article class="hello-card">
    <h1>{$card.title|escape}</h1>
    <p>{$card.body|escape}</p>
    <p><a href="/hello">חזרה</a></p>
</article>

הסינטקס המלא (תנאים, לולאות, include, פונקציות של המערכת) ב-תבניות Smarty.

תמיד escape לתוכן שהגיע מבחוץ

Smarty באתר מוגדר בלי קידוד HTML אוטומטי. כל משתנה שמקורו בטופס, בכתובת או במסד נתונים עובר דרך |escape, אחרת הוא פתח ל-XSS. פירוט ב-CSRF, XSS ו-SQLi.

שלב 3: לרשום את המודול ב-CRM_modules#

הקובץ מוכן, אבל הכתובת /hello עדיין מחזירה 404, כי המודול לא רשום. יש שתי דרכים לרשום אותו.

דרך א: פאנל הניהול#

  1. היכנסו ל-/{admin}/Modules (הפאנל צריך להיות רשום ומורשה למשתמש שלכם, ראו הרשאות וקבוצות).
  2. לחצו על כפתור ההוספה, מלאו Controller name = hello, סמנו פעיל ושמרו.

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

דרך ב: SQL#

INSERT INTO CRM_modules (moduleName, active) VALUES ('hello', 1);

הטבלה כוללת עוד עמודות (modulesDir, graphicsDir, tablesPre, adminDir, ord) שכולן אופציונליות והליבה אינה קוראת אותן בטעינת עמוד. moduleName הוא מפתח ייחודי, כך ששורה כפולה תיכשל. לשימוש חוזר בסקריפט התקנה כדאי INSERT IGNORE.

אחרי כתיבה ישירה למסד חייבים לנקות את המטמון (השלב הבא), כי רשימת המודולים נשמרת בקובץ מטמון.

חלון &quot;Click here&quot; למנהלים

אם אתם מחוברים לפאנל הניהול באותו דפדפן ופותחים כתובת של קונטרולר שקיים כקובץ אבל לא רשום, הליבה מציגה במקום 404 טופס קטן עם הכיתוב "Click here" שמציע להפעיל את המודול. הטופס שולח רק את moduleName ו-modulesDir, ללא השדה active. אחרי השימוש בו פתחו את המודול ב-/{admin}/Modules וודאו שתיבת פעיל מסומנת. הדרך המומלצת היא להוסיף את השורה ישירות בפאנל או ב-SQL.

שלב 4: ניקוי מטמון המודולים#

רשימת המודולים נשמרת לשבוע בקובץ cache/<host>/modules_lang<id>.chc.gz, ולכן שורה שנוספה ב-SQL לא נראית עד שמנקים. הדרכים:

  • בפאנל /{admin}/clear_cache (ניקוי מטמון נתונים).
  • מהקוד: cache_engine::remove("modules");
  • מחיקת הקבצים modules_lang*.chc.gz מתיקיית המטמון של האתר.

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

שלב 5: גלישה#

פתחו בדפדפן:

  • https://<האתר שלכם>/hello: רשימת הפריטים וכותרת הדף "שלום מ-WIZZO CMS". הכותרת מופיעה בלשונית ובקוד המקור.
  • https://<האתר שלכם>/hello/card/7: הכרטיס.
  • https://<האתר שלכם>/hello/card/99: עמוד ה-404 של האתר, כי המתודה החזירה "ERRORPAGE".

העיצוב שסביב התוכן (כותרת עליונה, תחתית, CSS) מגיע מה-theme של האתר, ולא מהקונטרולר. איך בונים ובוחרים theme ב-ערכות נושא.

שלב 6: כתובת ידידותית (אופציונלי)#

אם תרצו שהכרטיס יענה בכתובת כמו /cards/seven במקום /hello/card/7, מוסיפים שורה לטבלת CRM_seoUrl. העמודה sysname היא הכתובת שהגולש רואה, ו-pageurl הוא הנתיב הפנימי שאליו הליבה מנתבת:

INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('cards/seven', 'hello/card/7', 0, 0);

הטבלה נשמרת במטמון (seoUrl_tbl); שמירה דרך פאנל ה-SEO מנקה אותו, ואחרי SQL ישיר מנקים בעצמכם עם cache_engine::remove("seoUrl_tbl");. כתובות עם ביטוי רגולרי, סדר הבדיקה ושאר התכונות ב-כתובות ידידותיות.

מה הלאה: JSON במקום עמוד#

כדי שמתודה תחזיר JSON ולא עמוד עם theme, מכריזים על מצב pmode=empg בקונסטרקטור או במתודה, שולחים כותרת מתאימה ומסיימים:

<?php

class hello_api extends wz_controller
{
    function __construct()
    {
        parent::__construct();
        $_GET["pmode"] = "empg";   // בלי theme, בלי head ו-body
    }

    function ping()
    {
        header("Content-Type: application/json; charset=utf-8");
        echo json_encode(["ok" => true, "time" => date("c")]);
        die;
    }
}

גם קונטרולר כזה צריך שורה ב-CRM_modules (המודול hello_api, הכתובת /hello_api/ping). הסיבה ל-die מפורטת ב-מחזור חיי בקשה.

כשזה לא עובד#

התסמיןהסיבה הסבירהמה עושים
404אין שורה ב-CRM_modules, או active שונה מ-1SELECT * FROM CRM_modules WHERE moduleName = 'hello'; הוסיפו או תקנו את השורה
404 גם אחרי שהוספתם שורה ב-SQLמטמון רשימת המודולים (7 ימים)נקו עם cache_engine::remove("modules") או מהפאנל clear_cache
404 והשורה בסדרשם המחלקה בקובץ שונה משם המודול, או שם הקובץ באותיות אחרות (בשרת לינוקס Hello.php אינו hello.php)התאימו את שלושת השמות; המחלקה חייבת להיות מוגדרת בקובץ עצמו
404 רק בכתובת עם קטע שניהמתודה לא קיימת, או לא ציבורית/hello/card מחפש מתודה ציבורית בשם card
404 על /Helloרישיות: שם המודול רגיש לאותיותהשתמשו באותיות קטנות בכל מקום
שגיאת Smarty על תבנית שלא נמצאההנתיב ב-$this->view() שגויהנתיב יחסי ל-application/views, בלי .tpl ובלי / בהתחלה
עמוד לבן או 500שגיאת PHP בקובץקראו את לוג השגיאות (ראו אבחון ולוגים)
השינוי בקוד לא נראההדפדפן, או מטמון עמוד מלא אם הקונטרולר הפעיל אותונקו כמתואר ב-מטמון עמודים מלא
לא ברור איזה ניתוב נבחרחוק ניתוב מוקדם יותר תפס את הכתובתהתחברו לפאנל וגלשו ל-/hello?__router_trace=1 (ראו דיבוג ניתוב)
כלי הדיבוג הכי מהיר

כשמנהל מחובר, הוספת ?__router_trace=1 לכל כתובת מציגה בתחתית העמוד את כל שלבי ההחלטה של ה-ROUTER: איזה כלל נתפס, מה נקבע ב-$_GET, והאם המודול נמצא ברשימה. ברוב מקרי ה-404 התשובה כתובה שם.

מה למדנו#

  • כתובת רגילה היא /<מודול>/<מתודה>/<id>, והמודול הוא קובץ ב-application/controllers.
  • קונטרולר רגיל חייב שורה פעילה ב-CRM_modules, ואחרי כתיבה ישירה למסד צריך לנקות את המטמון.
  • המתודה מחזירה את תוכן העמוד; "ERRORPAGE" הוא 404.
  • SEO::set קובעת כותרת ותיאור, ו-$this->view מרנדרת תבנית.

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

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