טפסים בפאנל הניהול

איך מגדירים טופס עריכה בפאנל: Form ו-AJAXForm, דחיפת שדות, מצב עדכון, שמירה, ולידציה, טפסים רב-לשוניים וכפתורים נוספים, והקשר לטבלת panel_table.

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

טופס בפאנל הניהול הוא אובייקט AJAXForm שאתם ממלאים בשדות (FormInput_*) ומחזירים מהמתודה של הפאנל. בבקשה הראשונה הוא מחזיר את ה-HTML, ובשליחה הוא שומר לטבלה ומחזיר הודעה. בדף הזה מוסבר המחזור המלא: הגדרה, מצב עדכון, שמירה, ולידציה, רב-לשוניות, ואיך הרשימה (panel_table) והטופס מדברים זה עם זה. קטלוג השדות נמצא ב-שדות טופס.

{admin} הוא CONFIG::$admin_url, ו-system/ הוא תיקיית הליבה (api/core בריפו). הקוד ב-system/libraries/forms/ (Form.php, Form_AJAXForm.php, FormInputs*.php).

הדפוס הבסיסי#

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();                              // לפני push
    $frm->push($title);

    $body = new FormInput_Textarea("body");
    $body->title = "תוכן";
    $frm->push($body);

    if (!$frm->IsFormSent()) {
        return $frm->get_html();                 // הצגה
    }
    return $frm->save();                         // שמירה
}

הסדר חשוב:

  1. update_mode($id) לפני שמירה והצגה: היא טוענת את השורה (db_row) ומציבה את db_id. בלעדיה כל שמירה היא INSERT.
  2. Must() על השדה לפני push(). push() רושם את בדיקת השדה החובה רק אם must כבר נקבע.
  3. IsFormSent() בודקת isset($_POST["isFormSent_<formName>"]). השדה הנסתר הזה מתווסף לטופס אוטומטית.

מאפיינים של הטופס#

מאפייןברירת מחדלמשמעות
tableהטבלה לשמירה (בלי CRM_)
successFunc""מחרוזת JavaScript שמורצת אחרי שמירה מוצלחת. למשל adminMoveToLastPanel() (חזרה לרשימה)
tpl"frmUL"תבנית הפריסה. "groups" מציגה קבוצות לפי groups; ערך אחר הוא נתיב לקובץ Smarty (FILES::html_template) שמקבל את form, inputs, את כל המשתנים של tpl_vars ואת כל שדה לפי שמו
groups[]קבוצות שדות: [["key" => "", "name" => "פרטים"], ...]. שדה משויך לקבוצה בעזרת $input->group = "key"
className, styleמחלקת CSS וסגנון לעטיפה
msgAddedStr, msgNotAddedStrהודעות ברירת מחדלמה חוזר אחרי שמירה (הצלחה / כישלון)
sendStr"שלח"טקסט כפתור השמירה
submit_class""מחלקה לכפתור השמירה
hide_save_buttonfalseהסתרת כפתור השמירה
general_buttons[]כפתורים נוספים (ראו להלן)
autosavefalseמספר שניות בין שמירות אוטומטיות (למשל 30)
ReloadAfterSubmittrueהתשובה מחליפה את תוכן הטופס; כשהוא false, מורץ successFunc
allowMultyLangtrue בפאנלהצגת בורר שפות עריכה
copyToAllLangsfalseהעתקת הערכים לכל השפות בשמירה
contentTableName, queryKey"parentId"טבלת תוכן רב-לשוני מותאמת (ChangeContentTable)
mustCharכוכביתהסימון שמוצג ליד שדה חובה
realtime_enabledtrueעריכה משותפת בזמן אמת, כשהשירות wizzonow פעיל
ai_automations[]אוטומציות AI שמוצגות בטופס

מתודות#

מתודהמשמעות
new AJAXForm($name)יוצרת טופס (Form היא האב ללא AJAX; בפאנל משתמשים ב-AJAXForm)
push($input)מוסיפה שדה. מדלגת על שדה שה-perms שלו לא עובר ADMIN::has_perms, ועל שדות conf כשעורכים שפה משנית
RemoveInput($name)מסירה שדה
GetInputByName($name)מחזירה את אובייקט השדה
update_mode($id)טוענת שורה לעריכה (כולל השפה הנוכחית)
IsFormSent()האם הטופס נשלח
get_html()HTML מלא, כולל JavaScript של שליחה בלי רענון
save()שומרת ומחזירה הודעה
MakeSave()השמירה עצמה, מחזירה bool; save() עוטפת אותה בהודעות
GetNewID()מזהה השורה שנוצרה (אחרי save)
ChangeContentTable($tbl, $queryKey)טבלת תוכן רב-לשוני ידנית
UnSend()מבטלת את דגל השליחה, כדי להציג את הטופס שוב

מה קורה בשמירה#

save() קוראת ל-MakeSave(), ש:

  1. יוצרת DB::update($table).
  2. לכל שדה שנדחף: קוראת ל-get_results($new_id). התוצאה "ERROR" עוצרת הכול (הטופס חוזר עם הודעה). תוצאה שהיא מערך ([שם_עמודה => ערך]) מועברת ל-set_var של השאילתה, לטבלת conf או content לפי שדה table של השדה.
  3. עושה INSERT (כשאין db_id) או UPDATE.
  4. שדות עם requireID = true (גלריות, SeoUrl ועוד) רצים אחרי ה-INSERT, כשהמזהה כבר ידוע. אם אחד מהם נכשל, השורה החדשה נמחקת.

רק שדות שנדחפו לטופס נשמרים, ולכן שולח לא יכול לכתוב עמודה שלא הגדרתם (מניעת mass assignment). הערך המחרוזתי "NULL" נשמר כ-SQL NULL, ו-"NOW()" כביטוי.

בהצלחה save() שולחת את successFunc כ-<script> ומחזירה את msgAddedStr. בכישלון היא מחזירה את msgNotAddedStr. אחרי שמירה מוצלחת מופעלים גם סוכני AI שהוגדרו לאירוע form_save:<table> (ראו סוכני AI).

אחרי השמירה: להריץ קוד משלכם#

$result = $frm->save();
if ($frm->GetNewID()) {
    cache_engine::remove("articles");   // למשל ניקוי מטמון
}
return $result;

מזהה השורה אחרי INSERT זמין ב-$frm->GetNewID(), ואחרי עדכון הוא ה-db_id.

ולידציה#

  • שדה חובה: $input->Must() (או Must(true) כדי לאפשר "להתעלם"). הדפדפן בודק שהשדה לא ריק ומציג הודעה, והשרת מחזיר "ERROR" מ-get_results כשהשדה ריק.
  • בדיקה בצד הלקוח: $input->add_validate_function("myJsFunc") מוסיפה שם של פונקציית JavaScript (שחייבת להחזיר true או false) ל-submitFormCheakErrors<formName>(). הפונקציות מורצות לפני השליחה. validate_url מאפשר בדיקה מול כתובת (AJAX סינכרוני).
  • בדיקה בשרת: בפועל הליבה בודקת בשרת רק את must. כל בדיקה אחרת (פורמט, ייחודיות, טווח) כתבו בעצמכם לפני save(), והציגו שגיאה במקום לשמור:
if ($frm->IsFormSent()) {
    $slug = DB::escape($_POST["slug"] ?? "");
    $dup = DB::get_row("SELECT id FROM CRM_articles WHERE slug = '" . $slug . "'");
    if ($dup && (int)$dup["id"] !== (int)($_GET["id"] ?? 0)) {
        $frm->UnSend();    // מבטל את דגל השליחה: הטופס יוצג שוב
        return "<div class='error'>הכתובת כבר בשימוש</div>" . $frm->get_html();
    }
}
שימו לב

בדיקות JavaScript נעקפות בקלות. כל כלל שחשוב לשלמות הנתונים (ייחודיות, טווחים, הרשאות) צריך להיבדק גם ב-PHP לפני save().

שימו לב

טפסי הניהול ופעולות הטבלה לא נושאים טוקן CSRF משלהם: ההגנה היא עוגיית הסשן (SameSite=Lax). פעולה מסוכנת במיוחד (מחיקה, שליחה) שווה טוקן נוסף כמו בפאנל file_manager (ראו ממשק JSON).

כפתורים נוספים: general_buttons#

$frm->general_buttons = [
    [
        "title" => "שכפל",
        "icon"  => '<i class="fas fa-copy"></i>',
        "class" => "btn",
        "save"  => true,                  // true: לבדוק ולשמור לפני; "force": לשמור בלי בדיקת ולידציה
        "alert" => "לשכפל את המאמר?",
        "perms" => "edit",
        "func"  => function ($db_row) {
            // רץ בשרת, מקבל את שורת הטופס
            $up = DB::update("articles");
            $up->set_var("title", $db_row["title"] . " (עותק)");
            $up->insert();
        },
    ],
];

מפתחות נוספים: link (במקום func: ניווט), target. הפעולה נשלחת כ-POST form_action=<token>, והשרת מריץ רק כפתורים שהוגדרו (הטוקן הוא MISC::encode של ההגדרה). בכל טופס מתווסף אוטומטית כפתור "ביטול" שחוזר לפאנל הקודם.

טפסים רב-לשוניים#

בפאנל הטופס רב-לשוני כברירת מחדל. הנתונים נשמרים בשתי טבלאות: articles (שדות משותפים, table = "conf" בשדה, ברירת המחדל) ו-articles_content (שדות לפי שפה, parentId + langId), כשהשדה מוגדר $input->table = "content":

$title = new FormInput_Text("title");
$title->table = "content";        // נשמר בטבלת התוכן, לפי שפה
$title->title = "כותרת";
$frm->push($title);

מעבר שפה נעשה עם ?editLangID=<מזהה פלטפורמה>; update_mode טוענת את השורה בשפה זו, ושדות conf מוסתרים כשעורכים שפה משנית. ראו רב-לשוניות ו-פלטפורמות.

הטופס והרשימה#

הקשר בין panel_table לטופס הוא כתובת בלבד:

  1. btn_add_url ב-panel_table מצביע על מתודת הטופס ({admin}/articles/insert).
  2. פעולת "link" => "EDIT" מוסיפה את מזהה השורה: {admin}/articles/insert/12, ו-$_GET["id"] מגיע ל-update_mode.
  3. אחרי שמירה successFunc מחזיר לרשימה: adminMoveToLastPanel() בעמוד מלא, או סגירת חלון קופץ ורענון רשימה כשהטופס נפתח עם btn_add_url_target = "popup" (ראו דוגמת redirections ב-בניית פאנל חדש).

גילוי לסוכני AI#

בקשה עם הכותרת AI-Form-Discovery למתודת הטופס מחזירה תיאור JSON של הטופס (שדות, סוגים, חובה, אפשרויות, פעולות) במקום HTML. בקשת שמירה עם AI-Form-Insert מנרמלת ערכים דרך השדות, ובכישלון מחזירה את הכותרת AI-Form-Error עם שגיאות לפי שדה. פרטים ב-AI בטפסים.

ראו גם#

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