פאנל ניהול הוא קובץ 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: הרשאה וניקוי מטמון#
- הענקת הרשאה: מנהל על (
perms = -1) רואה את הפאנל מיד. קבוצות אחרות צריכות סימון ב-{admin}/Admins← הרשאות הקבוצה (ראו הרשאות). פעולות משנה (למשלinsert) נרשמות כשורות ב-panels_optionsרק אם רוצים לשלוט בהן בנפרד; בלי שורה הן פתוחות לכל מי שיש לו גישה לפאנל. - מטמון: תפריט האדמין נקרא בדרך כלל חי מהטבלה. אם הפאנל לא מופיע אחרי הרישום, נקו מטמון בפאנל
clear_cache(אוcache_engine::removeבקוד, ראו ממשק המטמון).
לפני {admin}/articles/delete או כל מתודה שמשנה נתונים: בדקו ADMIN::has_perms("edit") או "delete" בעצמכם. הליבה לא בודקת הרשאת עריכה בשמירת טופס, ופעולה בלי שורה ב-panels_options מחזירה תמיד true.
שלב 5: בדיקה#
- היכנסו ל-
{admin}/articles: אמורה להופיע רשימה ריקה עם כפתור הוספה. - הוסיפו מאמר, וודאו שהטופס חוזר לרשימה אחרי השמירה.
- עריכה: לחצו על הפעולה, בדקו שה-URL הוא
{admin}/articles/insert/<id>. - התחברו כמנהל מקבוצה בלי הרשאה וודאו שהפאנל לא מופיע וש-
{admin}/articlesמפנה ל-404. - אם משהו נכשל, הפאנל
error_logמציג את יומן השגיאות של האתר.
רישום אוטומטי מקוד#
אפשר לרשום את הפאנל בקוד שרץ בכל בקשת אדמין, כך שהפריסה היא קבצים בלבד. הקוד חייב להיות ב-application/admin/includes/ (נטען בכל בקשת אדמין לפני שהפאנל נוצר) ולא בתוך מחלקת הפאנל, שנוצרת רק אחרי שיש לה שורה בטבלה. חובה בדיקת קיום (כמו בדרך א) כדי לא ליצור כפילויות.
ראו גם#
- panel_table ו-רפרנס panel_table
- טפסים ו-שדות טופס
- הרשאות
- פאנלי Vue לפאנל עם ממשק משלכם
- סקירת הפאנל