תקן קוד

מוסכמות הכתיבה של ליבת WIZZO CMS: שמות, סגנון PHP, כללי DB, אבטחה, JS ו-CSS, והפער בין התקן לקוד הישן

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

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

מידע

אין כיום כלי שאוכף את התקן: אין phpcs, אין .editorconfig ואין CI (ראו בדיקות). האכיפה היא סקירת קוד ב-Pull Request. במפת הדרכים מתוארים הכלים שיאכפו אותו אוטומטית.

שמות וקבצים#

מההכללדוגמה
קבצי PHPsnake_case, שם הקובץ זהה לשם המחלקה (כולל האותיות)core_ping.php מגדיר core_ping
קונטרולר של אתר או קונטרולר מערכתמחלקה שיורשת wz_controller (או bgl_controller, שם נרדף ישן)class core_ping extends wz_controller
פאנל ניהולADMINMODULE_<שם_הקובץ> שיורש bgl_controllerADMINMODULE_minify ב-admin/minify.php
ערכת נושאTEMPLATE_<שם_התיקייה> שיורש bgl_theme, בקובץ themes/<שם>/index.phpTEMPLATE_admin_panel
מודלמחלקה שיורשת wz_model (או bgl_model), בתיקייה CONFIG::$models_folder, נטענת עם $this->load->model('path')m_orders
טבלאותCRM_ ואחריו שם באותיות קטנות עם _CRM_adminPanel_admins
פעולות (actions) בפאנלמתודה ציבורית אחת לכל פעולה, index היא ברירת המחדלgroups_insert() ב-ADMINMODULE_minify
הערה

כל הקבצים תחת api/core/controllers/ נגישים מהרשת בלי הרשאה אוטומטית. מי שמוסיף קובץ שם מוסיף נקודת כניסה ציבורית. פירוט במבנה הריפו ובקונטרולרי מערכת.

סגנון PHP#

  • הזחה בטאבים (זה הרוב בקבצים המרכזיים, DB.php, MISC.php, controllers/*). קובץ קיים שמוזח ברווחים: משמרים את ההזחה שלו ולא מעצבים אותו מחדש, כדי שה-diff יישאר קריא.
  • בלי ?> סוגר בסוף קובץ, כדי שלא ייפלט רווח או שורה ריקה לפני ה-headers.
  • קידוד UTF-8 בלי BOM, סיומות שורה LF (אין CRLF בקבצי הליבה).
  • אין namespaces ואין autoload של Composer לקוד הליבה. הקבצים של collections/ נטענים בעזרת glob בעליית המערכת, ולכן שם המחלקה חייב להיות ייחודי גלובלית.
  • בלי display_errors, error_reporting או ini_set של שגיאות בתוך קוד: הם נקבעים ב-CONFIG_USER או בהגדרות השרת (ראו קונפיגורציה).
  • בלי קוד מוער שנשאר "ליתר ביטחון". Git שומר היסטוריה.
  • טקסט שמוצג למשתמש בממשק ניהול עובר דרך LANGS::get_word('KEY') כשקיים מפתח מתאים (ראו פרמטרים ושפות). פאנלים ותיקים מכילים עברית ישירות בקוד, וזה מקובל שם, אבל מפתחות חדשים שייכים למילון.
<?php

class ADMINMODULE_orders extends bgl_controller
{
	public function index()
	{
		$list = new panel_table("orders");
		$list->query = DB::query("orders");
		$list->titles = array(
			array("title" => "id", "field" => "id"),
			array("title" => "name", "field" => "name")
		);

		return $list->get_html();
	}
}

כללי DB#

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

כלללמה
אין DB::insert. הכנסה: DB::update("table")->set("col", $val)->insert(). עדכון: ->update($id). שתיהן מחזירות boolDB::insert לא קיים וגורם ל-fatal error. קוד ישן שקורא לו הוא קוד מת
ה-builder (DB::query, DB::update, DB::delete, DB::get_val) מקבל שמות טבלה בלי CRM_הקידומת מתווספת אוטומטית
SQL גולמי (DB::get_all, DB::get_row, DB::sql) מחייב את השם המלא CRM_...אין שם הוספה אוטומטית
כל ערך חיצוני ב-SQL גולמי עובר DB::escape או הופך ל-(int)אין prepared statements בשכבה
DB::escape מחזיר מחרוזת בלי גרשיים מקיפים, ולכן חייבים לעטוף אותה בעצמכם: "... WHERE name = '" . DB::escape($v) . "'"escape מסיר את הגרשיים ש-PDO מוסיף. ערך ריק או NULL חוזר כמות שהוא
לא להעביר מחרוזת של משתמש כארגומנט שני של DB::get_valמחרוזת (שאינה מספר) מתפרשת כתנאי SQL גולמי ($filter), לא כערך להשוואה
מזהים ב-backtick, קריאות FUNC() ומפתחות order, limit, group נחשבים גולמייםאותם אסור להרכיב מקלט משתמש בלי רשימה לבנה
id מספרי נכנס ל-SQL כ-(int)ההמרה סוגרת הזרקה בלי תלות ב-escape
// נכון: builder, בלי CRM_, מערך תנאים
$ok = DB::update("orders")
	->set("name", $name)
	->set("status", 1)
	->insert();

$row = DB::get_row("SELECT * FROM CRM_orders WHERE id = " . (int)$id);

// נכון: ערך מחרוזת ב-SQL גולמי
$rows = DB::get_all("SELECT id FROM CRM_orders WHERE email = '" . DB::escape($email) . "'");
אזהרה

בקשות GET ו-POST, כותרות HTTP ושדות מ-JSON הם קלט חיצוני. ב-$_GET["id"] שנדבק לשאילתה בלי המרה נמצא מקור מרכזי לפרצות בליבה. אין חריגים "כי זה פאנל ניהול".

אבטחה בקוד חדש#

  • בדיקת הרשאה בכל נקודת כניסה שמשנה מצב או חושפת מידע. בפאנל: ADMIN::is_admin() בודק שמחובר מנהל, ו-ADMIN::has_perms($perm, $page, $action) בודק הרשאה לפעולה (ראו הרשאות). קונטרולר מערכת שלא בודק כלום פתוח לכל העולם.
  • פלט ל-HTML עובר MISC::special_chars($text) (עוטף htmlspecialchars ומחליף גם ' ב-&#39;). פלט שנכנס לתוך מאפיין או ל-JS מקבל קידוד מתאים להקשר.
  • פעולות שמשנות מצב (מחיקה, שמירה, שינוי הגדרות) לא מתבצעות על GET. ראו CSRF, XSS ו-SQLi למצב ההגנות בפועל.
  • קבצים: מעלים רק דרך STORAGE ובודקים סיומת ותוכן לפי כללי העלאת קבצים. לא סומכים על $_FILES[..]['type'].
  • מייל, SMS ו-push: לא קוראים ל-MISC::email ולא לשירותי השליחה מתוך קוד שרץ בבדיקות, ב-cron לא מתוזמן או בלולאה. כל שליחה אמיתית היא החלטה מפורשת.
  • סודות (סיסמאות, מפתחות API, טוקנים) לא נכתבים בקוד, בקובץ בריפו או ב-commit. הם חיים ב-CONFIG_USER של האתר או בטבלת ההגדרות. ראו הקשחה.
  • תשובות לשגיאות: לא die("...") עם טקסט חופשי שמכיל נתיבים או שגיאות SQL. מחזירים קוד סטטוס ו-JSON קצר, וכותבים פרטים ללוג (ראו קודי שגיאה).
  • unserialize על קלט שמגיע מבחוץ, eval ו-include של נתיב שנבנה מפרמטר אסורים. לנתונים מבניים משתמשים ב-json_decode.

JavaScript ו-Vue#

  • Vue 3 ב-Options API בלבד. לא Composition API ולא <script setup>.
  • קריאות שרת דרך this.api(), ניווט דרך this.goto(), הודעות דרך this.toast().
  • כל פאנל Vue חייב להיות מחובר לטעינת AJAX של האדמין: פונקציות mountApp ו-unmountApp, ו-MutationObserver שמפעיל אותן כשהפאנל נכנס ויוצא מה-DOM. פירוט בפאנלי Vue.
  • FontAwesome באדמין הוא גרסה 5. שמות אייקונים של גרסה 6 לא יוצגו, והשדה יישאר ריק.
  • לעולם לא v-if, v-else או v-for על אלמנט <i> של FontAwesome. הסקריפט של FA מחליף את ה-<i> ב-<svg>, וה-patch של Vue נשבר עד שהרכיב כולו מפסיק להתרנדר. עוטפים:
<span v-if="done" class="wz_if"><i class="fas fa-check"></i></span>
<span v-else class="wz_if"><i class="fas fa-clock"></i></span>
  • ספינר לא נבנה מ-FA. משתמשים ב-<span v-if="loading" class="wz_spin"></span>.

CSS ו-SCSS#

  • עורכים .scss בלבד. הקובץ ה-.css נוצר בצד השרת ולא נערך ידנית (ראו צינור הנכסים).
  • מאפיינים פיזיים של כיוון אסורים: לא left, right, margin-left, padding-right, text-align: left, float: right. משתמשים באלו הלוגיים: inset-inline-start / inset-inline-end, margin-inline-*, padding-inline-*, text-align: start / end.
  • inline-end ב-RTL הוא הצד השמאלי. "end" אינו "right".
  • הלוגיים עוקבים אחרי כיוון האלמנט עצמו: על תא עם direction: ltr, padding-inline-end ינחת בצד ימין פיזית.
  • באדמין הכיוון נקבע ב-class של ה-body (body.rtl) ולא ב-[dir=rtl]. כלל שתלוי בכיוון נכתב גם כ-.rtl &.

גרסה, commit ותיעוד#

  • שינוי בקוד שנשלח ללקוחות מעלה את api/core/version.txt באותו PR (ראו שליחת שינויים וגרסאות).
  • הודעת commit קצרה ומתארת מה השתנה ולמה. פורמט מלא בשליחת שינויים.
  • שינוי ב-API ציבורי של מחלקה (חתימה, מפתח הגדרה, שם טבלה) מעדכן באותו PR את דף הרפרנס המתאים תחת רפרנס ליבה.

הקוד הקיים והתקן#

חלק גדול מהקוד נכתב לפני שהתקן הזה נוסח, ולכן תמצאו בו דברים שהתקן אוסר: $_GET בתוך SQL, die עם טקסט, בדיקות הרשאה חסרות, הזחה מעורבת, קוד מוער. הנחיות לעבודה בקוד כזה:

  1. לא מעתיקים דפוס מקוד ישן רק כי הוא קיים. מחפשים את הדוגמה בדפי התיעוד.
  2. לא מתקנים בהזדמנות קבצים שלא קשורים לשינוי. PR ממוקד קל לסקירה ולביטול.
  3. כשנוגעים בפונקציה, מתקנים בה מה שהתקן אוסר ושייך לשורות שאתם משנים (למשל המרת $id ל-(int)).
  4. ממצא אבטחה בקוד שלא נגעתם בו: מדווחים בצנרת הפנימית, לא פותחים issue ציבורי ולא מתארים אותו בדף תיעוד.

ראו גם#

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