בניית פאנל חדש

מדריך שלב אחר שלב ליצירת פאנל ניהול: מחלקת ADMINMODULE, מתודות index ו-insert, רישום בטבלת adminPanel_panels, ניקוי מטמון, הרשאות ובדיקה.

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

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

{admin} הוא CONFIG::$admin_url, ו-system/ הוא תיקיית הליבה (api/core בריפו). קוראים לפאנלים של האתר בשמות שלא קיימים ב-system/admin/ (ראו פאנלים מובנים).

המבנה של מחלקת פאנל#

הקובץ נקרא application/admin/<name>.php והמחלקה ADMINMODULE_<name> מרחיבה את bgl_controller:

class ADMINMODULE_articles extends bgl_controller
{
    public function index() { /* ... */ }
}

כללים שהקוד אוכף או מניח:

  • שם המחלקה תלוי רישיות ושם הקובץ חייב להתאים לשם הפאנל ב-URL.
  • index() היא ברירת המחדל: {admin}/articles מפעילה אותה.
  • כל מתודה ציבורית היא כתובת. {admin}/articles/insert מפעילה את insert(). מתודת עזר חייבת להיות private או protected, אחרת כל מנהל עם גישה לפאנל יכול לקרוא לה.
  • מתודות מחזירות HTML (return, לא echo). נקודות קצה של JSON קובעות $_GET["pmode"] = "empg" וכותרת Content-Type.
  • $this->view($tpl, $vars) מציגה תבנית Smarty מ-application/views/<tpl>.tpl; $this->system_view(...) מציגה תבנית מהליבה. $this->load->library("name") טוענת ספריית ליבה.
  • wrapper($pageContent) ב-wz_controller מחזירה את התוכן כמות שהוא. אפשר לדרוס אותה כדי לעטוף את הפלט של כל מתודות הפאנל (למשל בכותרת קבועה).
שימו לב
Smarty בפאנלים לא עושה escape אוטומטי. ערך שמקורו במשתמש או בבסיס הנתונים ושמודפס בתבנית חייב לעבור |escape (או htmlspecialchars ב-PHP).

הכתובת {admin}/articles/insert/5 מעבירה $_GET["id"] = "5". הכתובת {admin}/articles/5 היא index עם id = 5.

שלב 1: טבלה#

טבלאות האתר בקידומת CRM_:

CREATE TABLE CRM_articles (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL DEFAULT '',
    body TEXT NULL,
    published TINYINT(1) NOT NULL DEFAULT 0
) DEFAULT CHARSET=utf8mb4;

עמודת id ב-panel_table היא חובה (הטבלה מזהה שורות לפיה). אם הפאנל רב-לשוני ראו תוכן רב-לשוני.

שלב 2: הקובץ application/admin/articles.php#

<?php
class ADMINMODULE_articles extends bgl_controller
{
    // רשימת המאמרים
    public function index()
    {
        MISC::breadcrumbs([["name" => "מאמרים"]]);

        $tbl = new panel_table("articles");      // מזהה קבוע וייחודי
        $tbl->title = "מאמרים";
        $tbl->query = DB::query("articles", null, "id DESC");
        $tbl->order_manager = false;             // ראו panel_table: ברירת מחדל true
        $tbl->btn_add_url = CONFIG::$admin_url . "/articles/insert";
        $tbl->delete = function ($id) {
            DB::delete("articles", ["id" => $id]);
        };
        $tbl->titles = [
            ["title" => "ID",    "field" => "id"],
            ["title" => "כותרת", "field" => "title"],
        ];
        $tbl->actions = function ($rows) {
            return [[
                "title" => "עריכה",
                "link"  => "EDIT",
                "icon"  => '<i class="fas fa-edit"></i>',
            ]];
        };

        return $tbl->get_html();
    }

    // טופס הוספה ועריכה
    public function insert()
    {
        $frm = new AJAXForm("frmArticles");
        $frm->table = "articles";
        $frm->successFunc = "adminMoveToLastPanel()";

        if (isset($_GET["id"])) {
            $frm->update_mode((int)$_GET["id"]);
        }

        $title = new FormInput_Text("title");
        $title->title = "כותרת";
        $title->Must();
        $frm->push($title);

        $published = new FormInput_Switch("published");
        $published->title = "מפורסם";
        $frm->push($published);

        if (!$frm->IsFormSent()) {
            return $frm->get_html();
        }
        return $frm->save();
    }
}

מה קורה כאן:

  • כפתור ההוספה מוביל ל-btn_add_url, והפעולה "link" => "EDIT" מובילה לאותה כתובת עם מזהה השורה (/id בסוף). ה-id מגיע ל-insert() כ-$_GET["id"]. פרטים ב-panel_table.
  • הטופס שולח את עצמו ב-AJAX ל-?pmode=empg. IsFormSent() בודקת אם הגיע השדה isFormSent_<formName>; בבקשה הראשונה הוא מחזיר את ה-HTML, ובשליחה שומר דרך save(). פרטים ב-טפסים.
  • successFunc היא מחרוזת JavaScript שמורצת אחרי שמירה מוצלחת. adminMoveToLastPanel() חוזרת לרשימה.
  • MISC::breadcrumbs($list) שולחת את פירורי הלחם בכותרת תגובה, וה-SPA מציג אותם.
אזהרה
DB::insert לא קיים (שגיאה קטלנית). הכנסה ידנית נעשית ב-DB::update("table")->set_var("col", $val)->insert(). אל תעתיקו קוד שקורא ל-DB::insert מקבצים ישנים: למשל install_db_manager.php בליבה הוא קוד מת שעושה זאת.

שלב 3: רישום הפאנל בטבלה#

קובץ בלי שורה ב-CRM_adminPanel_panels הוא 404 ולא מופיע בתפריט (חריג: skip_permission_check, ראו הרשאות). קטגוריות התפריט שונות בכל אתר, לכן קודם קראו אותן:

SELECT id, name FROM CRM_adminPanel_panels_categories ORDER BY id;

דרך א: קוד התקנה (מומלץ לפרויקט)#

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

$panel = "articles";
$exists = DB::get_row("SELECT id FROM CRM_adminPanel_panels WHERE panel_name = '" . DB::escape($panel) . "'");

if (!$exists) {
    $p = DB::update("adminPanel_panels");
    $p->set_var("panel_name", $panel);
    $p->set_var("text", "מאמרים");
    $p->set_var("module_link", "");
    $p->set_var("category", 1);           // מזהה קטגוריה מהשאילתה למעלה
    $p->set_var("deletable", 0);
    $p->set_var("ord", 0);
    $p->set_var("icon", "<i class='fa fa-table-cells'></i>");
    $p->set_var("perm_developer", 0);
    $p->insert();
}

דרך ב: ה-API panel_registry#

הפאנל המובנה panel_registry חושף נקודות קצה למי שמחובר:

בקשהמה עושה
GET {admin}/panel_registry/listכל הפאנלים לפי קטגוריות (JSON)
GET {admin}/panel_registry/exists?panel_name=articlesהאם קיים
POST {admin}/panel_registry/registerרושם פאנל. פרמטרים: panel_name ו-text (חובה), category (ברירת מחדל 7), icon, perm_developer (0 או 1), ord (ברירת מחדל 100)

התשובה היא JSON, למשל {"success": true, "panel_id": 41, "panel_name": "articles", "admin_url": "admin/articles"}. אם הפאנל קיים: {"success": false, "error": "...", "existing_id": 41}.

דרך ג: ממשק PanelList#

בפאנל {admin}/PanelList מנהל מפתח מוסיף פאנל בטופס: שם תצוגה (text), panel_name, קטגוריה, אייקון (בוחר FontAwesome), הרשאות נוספות (extra_perms) ו-perm_developer (מוצג למפתחים בלבד). אותו מסך מנהל גם את תת-הפעולות (panels_options) ואת הקטגוריות.

הערה
panel_name הוא שם הקובץ בלי .php. module_link נשאר ריק לפאנל שמגובה בקוד.

שלב 4: הרשאה וניקוי מטמון#

  1. הענקת הרשאה: מנהל על (perms = -1) רואה את הפאנל מיד. קבוצות אחרות צריכות סימון ב-{admin}/Admins ← הרשאות הקבוצה (ראו הרשאות). פעולות משנה (למשל insert) נרשמות כשורות ב-panels_options רק אם רוצים לשלוט בהן בנפרד; בלי שורה הן פתוחות לכל מי שיש לו גישה לפאנל.
  2. מטמון: תפריט האדמין נקרא בדרך כלל חי מהטבלה. אם הפאנל לא מופיע אחרי הרישום, נקו מטמון בפאנל clear_cache (או cache_engine::remove בקוד, ראו ממשק המטמון).
שימו לב

לפני {admin}/articles/delete או כל מתודה שמשנה נתונים: בדקו ADMIN::has_perms("edit") או "delete" בעצמכם. הליבה לא בודקת הרשאת עריכה בשמירת טופס, ופעולה בלי שורה ב-panels_options מחזירה תמיד true.

שלב 5: בדיקה#

  1. היכנסו ל-{admin}/articles: אמורה להופיע רשימה ריקה עם כפתור הוספה.
  2. הוסיפו מאמר, וודאו שהטופס חוזר לרשימה אחרי השמירה.
  3. עריכה: לחצו על הפעולה, בדקו שה-URL הוא {admin}/articles/insert/<id>.
  4. התחברו כמנהל מקבוצה בלי הרשאה וודאו שהפאנל לא מופיע וש-{admin}/articles מפנה ל-404.
  5. אם משהו נכשל, הפאנל error_log מציג את יומן השגיאות של האתר.

רישום אוטומטי מקוד#

אפשר לרשום את הפאנל בקוד שרץ בכל בקשת אדמין, כך שהפריסה היא קבצים בלבד. הקוד חייב להיות ב-application/admin/includes/ (נטען בכל בקשת אדמין לפני שהפאנל נוצר) ולא בתוך מחלקת הפאנל, שנוצרת רק אחרי שיש לה שורה בטבלה. חובה בדיקת קיום (כמו בדרך א) כדי לא ליצור כפילויות.

ראו גם#

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