Wizzo Market הוא החנות של Wizzo: משם מפעילים לאתר שירותים (WizzoAI, POOLSE, POOSH, KAMA, SEOK, אולג ועוד), ושם נמצאים החיוב והזהות. הליבה של האתר לא מכילה את המוצרים עצמם. היא מחזיקה מפתח אחד שמזהה את האתר מול המרקט, מושכת משם קובץ הגדרות, וחושפת דלת אחת שדרכה המרקט פונה אל האתר. בדף הזה תבינו את שלושת המרכיבים האלה ואת המעגל ביניהם.
המודל בחמישה צעדים#
- מפתח המרקט של האתר. הערך
wizzo_market_api_keyבטבלתCRM_params. הוא נשמר דרך{admin}/wizzo_market(המתודהset_api_key), והוא הסוד שמזהה את האתר מול המרקט. אותו מפתח משמש גם לאימות קריאות נכנסות מהמרקט. - הפעלת שירות. הפאנל
admin/wizzo_marketטוען אפליקציית Vue של המרקט (assets/wizzo_market/index.html) שמדברת עם המרקט דרך פרוקסי בצד השרת:system/wizzo_market_api/<action>. - סנכרון הגדרות. אחרי כל פעולה דרך הפרוקסי, ובכל קריאת
syncמהמרקט, הליבה מושכתGET https://market.wizzo.media/api/get_settingsוכותבת אתsystem/market_settings.json. - קריאת מפתחות. כל ספרייה בליבה שצריכה מפתח של שירות שואלת את
market_service. מפתחות השירותים נקראים מהקובץ ולא מבסיס הנתונים. - פאנלים. מאז ליבה 5.0.62 אין בליבה פאנלי מוצר. התפריט מציג "כלים" שהמרקט מגדיר, וכל כלי נפתח כ-
{admin}/wizzo_market?tool=<tool>.
מנהל בפאנל --(wizzo_market_api, פרוקסי)--> market.wizzo.media
|
get_settings (אחרי כל פעולה / sync)
v
system/market_settings.json
|
market_service::api_key("kama") וחברותיה, בתוך ספריות הליבה
מבנה system/market_settings.json#
הקובץ הוא שדה data מתשובת get_settings. הליבה קוראת ממנו את השדות הבאים בלבד:
{
"services": [
{
"service": "poosh",
"additional_data": {
"api_key": "<public key of the site in that product>",
"secret_key": "<secret, used by SEOK and Oleg>",
"dashboard_url": "https://seok.wizzo.market",
"settings": { "enabled": true, "lang": "he" }
},
"panels": [
{ "file": "services_poosh_stats", "name": "Poosh", "tool": "poosh", "perms": ["stats"] }
],
"scripts": [ "<script src=\"https://example.invalid/sdk.js\"></script>" ]
}
]
}
| שדה | מי קורא | הערות |
|---|---|---|
services[].service | market_service::settings, is_active, get_all_api_keys | שם השירות. ראו את רשימת השמות ב-קטלוג השירותים |
additional_data.api_key / secret_key | api_key(), secret_key() ובספריות כמו wizzo_ai, SeoHosted, OlegHosted, poolse_client | מפתחות האתר בתוך המוצר |
additional_data.dashboard_url | SeoHosted::base, OlegHosted::base | מיקום המנוע. חייב להיות https://, אחרת חוזרים לכתובת ברירת המחדל |
additional_data.settings | market_service::enable_config | רק לשירות enable: נכתב ל-system/accessibility_config.json |
panels[] | settings_tools, panel_by_tool, panel_tool | tool ריק נגזר מהטבלה LEGACY_TOOLS לפי file |
scripts[] | generate_system_tags | נכתב ל-system/system_tags.json ומוזרק ל-body_end של כל עמוד |
market_settings.json מכיל api_key ו-secret_key של כל שירות, והוא יושב בתוך ה-webroot. ההגנה היחידה היא system/.htaccess ש-market_service::protect_settings_dir() שותל אוטומטית (כלל FilesMatch), והוא עובד ב-Apache וב-LiteSpeed בלבד. על nginx וכל שרת אחר חובה להוסיף חסימה ידנית של /system/market_settings.json ושל /system/system_tags.json בהגדרות השרת. אחרי כל התקנה, בדקו שגישה ישירה לקובץ מחזירה 403. ראו הקשחה.market_service: הממשק לקובץ#
הקובץ: libraries/market_service.php. כל המתודות סטטיות, והקובץ נקרא פעם אחת לבקשה (init() טוען אותו ל-market_service::$market_settings).
| מתודה | ערך החזרה | הערה |
|---|---|---|
market_service::init() | void | מוודא את ה-.htaccess וטוען את הקובץ. נקראת אוטומטית מכל שאר המתודות |
market_service::settings($service) | object או false | רשומת השירות כאובייקט (->additional_data->api_key). מקבלת גם את השם הישן של שירות בשינוי שם (poosh / wizzopoosh, kama / wizzoradar) |
market_service::api_key($service) | string או false | additional_data.api_key, מחרוזת ריקה אם אין, false אם השירות לא פעיל |
market_service::secret_key($service) | string או false | additional_data.secret_key |
market_service::get_all_api_keys() | array | [service => api_key] לכל השירותים עם מפתח |
market_service::is_active($service) | bool | האם השירות מופיע בקובץ |
market_service::settings_tools() | array | מפת הכלים שהמרקט מגדיר בתפריט ([tool => true]) |
market_service::panel_by_tool($tool) | array או null | הפאנל שהכלי שייך לו, עם file (מפתח ההרשאה) ו-perms |
market_service::panel_tool($panel) | string | שם הכלי של פאנל, כולל נפילה ל-LEGACY_TOOLS |
market_service::enable_config() | array או null | הגדרות ווידג'ט הנגישות (שירות enable) |
market_service::sync_enable_config() | void | כותב את system/accessibility_config.json, נקרא בכל סנכרון |
market_service::migrate_legacy_enable_settings() | void | מעביר פעם אחת הגדרות נגישות ישנות מ-PARAMS למרקט |
market_service::write_enable_config($cfg) | void | כותב רק את המפתחות המותרים |
market_service::generate_system_tags() | array | בונה את system/system_tags.json מכל scripts[] |
market_service::get_system_tags() | array | קורא את הקובץ הזה |
market_service::protect_settings_dir($force = false) | void | שותל את בלוק ה-.htaccess של /system/, ובלוקים שהאתר הוסיף לידו נשמרים |
market_service::guard_block() | string | הטקסט של הבלוק |
$this->load->library("market_service");
if (market_service::is_active("spamguard"))
{
$key = market_service::api_key("spamguard"); // never echo it into a page
}
market_service::secret_key() קורא additional_data->secret_key ישירות, ולכן קריאה לשירות שרשום בלי שדה כזה נותנת אזהרת PHP. בדקו קודם is_active(). ו-api_key() של שירות לא פעיל מחזירה false, לא מחרוזת ריקה, אז השוו עם ! ולא עם === "".הפרוקסי system/wizzo_market_api/<action>#
הפרוקסי הוא בקר ליבה (controllers/wizzo_market_api.php) שמעביר בקשות מהפאנל אל https://market.wizzo.media/api/<action>, ובכך שומר על המפתח בצד השרת. הוא דורש ADMIN::is_admin(), והבקשה נשלחת בכותרות הבאות:
| כותרת | תוכן |
|---|---|
Authorization | מפתח המרקט של האתר (wizzo_market_api_key) |
X-Core-Version | התוכן של system/core/version.txt. לפי הגרסה המרקט בוחר שמות שירות חדשים או ישנים |
X-Cms-Admin | <id>|<base64url של השם> של המנהל המחובר, כדי שהמרקט יפתח את המוצר בשמו |
X-Cms-Perms | מה הקבוצה של המנהל מורשית לעשות בכלי הנדרש: *, או רשימת מפתחות הרשאה |
X-Cms-Token | רק בפעולות mcp...: אסימון שמאשר שמנהל מחובר באמת מבקש (ראו MCP חיצוני) |
אחרי כל קריאה הפרוקסי מריץ get_settings(), כלומר סנכרון מלא של market_settings.json, ואז system_tags.json, accessibility_config.json וניקוי מטמון העמוד הראשי. סנכרון שנכשל (שגיאת רשת, קוד HTTP שאינו 2xx, או תשובה בלי services) לא דורס את הקובץ הקיים: נרשמת שורה ב-error_log והקובץ הישן נשאר. הכתיבה אטומית: קובץ זמני ואז rename.
הדלת ההפוכה: sync#
כשהמרקט רוצה שהאתר ימשוך הגדרות מיד (חשבון חובר, מפתח הוחלף) הוא קורא ל-system/wizzo_market_api/sync. אין כאן סשן מנהל: האימות הוא WIZZO_KEY::matches(PARAMS::get("wizzo_market_api_key")), ובכישלון חוזר 403 עם {"ok":false}.
נתיב הפעולה בפרוקסי נגזר מ-REQUEST_URI ומצורף לכתובת המרקט כמו שהוא, וכל מנהל מחובר מגיע אליו, לא רק מפתח (developer). בנוסף, admin/wizzo_market::set_api_key כותב את מפתח המרקט בלי לוודא שהמנהל הוא מפתח ובלי אסימון CSRF. בקוד שלכם אל תבנו על הפרוקסי כדלת כללית: קראו לשירותים דרך הספריות. זה ממצאים ידועים בביקורת (SVC-19) ופתרונם בתהליך.
WIZZO_KEY: קריאת המפתח מבקשה נכנסת#
כל דלת בליבה שנקראת מצד שרת של Wizzo (ה-connector, sync, ריצות SEOK ואולג, ריצות הסוכנים) קוראת את המפתח דרך collections/WIZZO_KEY.php, ולא מ-$_SERVER["HTTP_AUTHORIZATION"]. הסיבה: מארחי cgi-fcgi לא מעבירים את הכותרת Authorization ל-PHP.
| מתודה | הערה |
|---|---|
WIZZO_KEY::incoming($body = null): string | מחפשת בסדר: HTTP_AUTHORIZATION, REDIRECT_HTTP_AUTHORIZATION, apache_request_headers(), הכותרת X-Wizzo-Key, ובגוף ה-POST אחד מהשדות _key, api_key, _token. מסירה קידומת Bearer |
WIZZO_KEY::matches($expected, $body = null): bool | השוואה ב-hash_equals. ציפייה ריקה לעולם לא תואמת, כך שאתר בלי מפתח לא נפתח בבקשה בלי מפתח |
if (!WIZZO_KEY::matches(PARAMS::get("wizzo_market_api_key"), $body))
{
http_response_code(401);
die(json_encode(["ok" => false, "error" => "unauthorized"]));
}
מפתח לא מתקבל ב-query string, כדי שלא יישמר בלוגי הגישה וב-Referer.
system/connector: הדלת של המרקט לתוך האתר#
controllers/connector.php הוא הדלת היחידה שדרכה המרקט (ובאמצעותו כל מוצר) פונה לאתר. האימות הוא WIZZO_KEY::matches מול מפתח המרקט, והתשובה תמיד JSON עם ok.
| פעולה | תוצאה |
|---|---|
GET /system/connector (או ?do=discovery) | גרסאות, זהות האתר, היכולות והנתיבים שלהן |
POST /system/connector/mcp_connect (גוף: rotate אופציונלי) | רישום דלת ה-MCP של האתר ב-gateway. נכשל עם 502 |
POST /system/connector/agents_ready | רישום האתר בבקרת הסוכנים. 409 אם WizzoAI לא פעיל |
POST /system/connector/seo_ready | העברת ה-SEO של האתר למנוע המאוחסן (רישום, ייצוא היסטוריה, החלפת מצב) |
POST /system/connector/oleg_ready | הצגת האתר למנוע של אולג. 409 אם אולג לא פעיל |
GET /system/connector/status | מצב חי של כל יכולת |
פירוט ה-MCP ב-MCP חיצוני, והסוכנים ב-סוכני AI.
כלי מרקט בפאנל: איך זה מופיע#
מוצר חדש לא דורש קוד בליבה:
- בצד המרקט מוסיפים לשירות רשומת
panels[]עםfile,name,toolו-perms. השדהfileהוא מפתח ההרשאה שקבוצות מנהלים קיבלו (ADMIN::has_perms($key, $file)). - באתר, לחיצה על סנכרון (או כל פעולה בפאנל) מושכת את ה-JSON.
settings_tools()מונע כפילות עם שורות ישנות של אותו פאנל בטבלתadminPanel_panels. - הכלי נפתח כ-
{admin}/wizzo_market?tool=<tool>. הפאנל מעביר ל-Vue של המרקט אתroute: '/tool/<tool>'ואתperms:allאם לקבוצה ישshow_allעל הפאנל, אחרתlimited. - אם הכלי צריך נקודת קצה בצד האתר (העלאת קבצים, webhook), כותבים בקר או קובץ
admin/<svc>_media.phpעםstatic $skip_permission_check = true, ובתוכו בודקיםADMIN::is_admin()ו-market_service::is_active("<svc>"). בלי שתי הבדיקות האלה הדלת פתוחה.
רשומות adminPanel_panels של פאנלים שנמחקו מהליבה (למשל ai_dashboard, seo_advisor, services_*) עדיין עשויות להיות בבסיס הנתונים של אתרים ותיקים. הטבלה market_service::LEGACY_TOOLS ממפה אותן לכלי המרקט המתאים.
סקריפטים שהמרקט מזריק#
שירותים עם scripts[] (ווידג'ט נגישות, ווידג'ט צ'אט, מדידה) מוזרקים לסוף ה-body של כל עמוד באתר דרך system/system_tags.json.
אין באתר רשימת דומיינים מותרים, אין SRI, ואין מסך שמראה למנהל מה מוזרק. כלומר המרקט הוא ספק עם גישת סקריפט מלאה לדפי האתר. בדקו מדי פעם את התוכן של system/system_tags.json (ראו SVC-20 בביקורת).
ראו גם#
- קטלוג השירותים, לכל שירות: מה הוא עושה ואיפה הליבה נוגעת בו
- מארחים חיצוניים, לאן יוצאות קריאות מהאתר ומה עובר בהן
- MCP חיצוני, הכלים של מוצרי המרקט בתוך הסוכנים
- עדכון עצמי, הגרסה שהפרוקסי שולח ב-
X-Core-Version - הקשחה ו-מודל האבטחה