getWidgetContent()
Die statische Methode getWidgetContent() auf der Widget-Klasse ist der Ort, an dem Widgets dynamische Daten laden — z. B. Blog-Artikel, Produkte, Menu-Strukturen oder user-spezifische Inhalte. Reine Formular-Widgets geben einfach null zurueck.
Signatur
class myWidget_PageBuilderPlugin
{
public static function getWidgetContent($widget_array, $backend = false)
{
// Rueckgabe: null | array
}
}| Parameter | Typ | Zweck |
|---|---|---|
$widget_array | array | Formular-Werte + Meta (IDs, Ordner-Pfad, Klasse) |
$backend | bool | true beim Rendern im Pagebuilder-Preview-Modus, false im oeffentlichen Frontend |
Der Rueckgabewert landet im Vue-Template unter widget.content. null bedeutet: keine zusaetzlichen Daten, Vue sieht nur widget.data.
Wann implementieren?
| Situation | getWidgetContent() |
|---|---|
| Widget zeigt nur Formular-Werte (Titel, Text, Bild, Link) | return null; |
| Widget lädt Inhalte aus DB (Blog, Shop, Menu) | Query + Array zurueckgeben |
| Widget zeigt Login-Status oder user-spezifischen Inhalt | Session lesen, Array zurueckgeben |
| Widget rendert Sub-Items (Repeater) | Items aus page_widget_item laden |
| Widget zeigt andere Frontend-Verhalten zwischen Preview + Live | if ($backend)-Branch |
$widget_array-Schluessel
Formular-Werte werden mit widget_-Prefix zur Verfuegung gestellt — nicht unter dem row-Namen direkt.
row im JSON | Key in $widget_array |
|---|---|
title | widget_title |
subtitle | widget_subtitle |
text | widget_html (Legacy!) |
link | widget_link |
image | widget_image |
gallery | widget_gallery |
logo | widget_logo |
color | widget_color |
price | widget_price |
video | widget_video |
location | widget_location |
vcard | widget_vcard |
short_text | widget_short_text |
map | widget_map |
accordion | widget_accordion |
pagebuilder | widget_pagebuilder |
form | widget_form |
row: "text" hat Key widget_html
Das einzige Mapping, das nicht dem Schema widget_{row} folgt: "row": "text" wird zu $widget_array['widget_html']. Wer $widget_array['widget_text'] liest, bekommt null und wundert sich. Das Remapping passiert in PageService.php — die DB-Spalte heisst text, der Key-Name widget_html macht die HTML-Content-Semantik explizit.
Zusaetzlich enthaelt $widget_array System-Keys:
| Key | Zweck |
|---|---|
item_id / base_id | Widget-Base-ID — fuer Item-Queries in page_widget_item |
widget_id | Konkrete Widget-Record-ID (eine pro Platzierung auf einer Seite) |
widget_folder | Absoluter Pfad zum Widget-Ordner |
widget_class | Widget-Template-Name (myWidget) |
sale_status und sliderrevolution existieren als DB-Spalten, sind aber nicht im $widget_array gemappt — falls gebraucht, direkt via $widget_id aus page_row_col_widget laden.
Pattern: null zurueckgeben (reines Formular-Widget)
Fuer rein statische Widgets, die nur Formular-Werte darstellen:
class image_PageBuilderPlugin
{
public static function getVersion() { return "1.0.0"; }
public static function getName() { return "Image"; }
public static function getWidgetContent($widget_array, $backend = false)
{
return null;
}
}Das reale image-Widget aus widgetsBase/widgets/image/bootstrap.php — kuerzer geht es nicht. Im Vue-Template steht dann ausschliesslich widget.data.* zur Verfuegung.
Pattern: DB-Daten laden
Typisches Muster: Widget hat einen select-Feld (z. B. Produkt-ID oder Kategorie-ID), daraus werden dynamisch Daten geladen.
class productTeaser_PageBuilderPlugin
{
public static function getVersion() { return "1.0.0"; }
public static function getName() { return "Product Teaser"; }
public static function getWidgetContent($widget_array, $backend = false)
{
$productId = (int) ($widget_array['widget_subtitle'] ?? 0);
if ($productId === 0) return null;
$currentLanguage = $GLOBALS['current_language'] ?? 'de';
$lang = real_escape_string($currentLanguage);
$q = query("SELECT id, title, price, image, url_rewrite
FROM s_products
WHERE id = $productId AND language_short LIKE '$lang'
LIMIT 1");
if (num_rows($q) === 0) return null;
$row = fetch_assoc($q);
$row['image'] = files::loadURL($row['image']);
return ['product' => $row];
}
}Im Vue-Template:
<template>
<div v-if="widget.content?.product" class="product-teaser">
<NuxtImg :src="widget.content.product.image" :alt="widget.content.product.title" />
<h3>{{ widget.content.product.title }}</h3>
<span>{{ widget.content.product.price }} EUR</span>
</div>
</template>Pattern: Items laden (Repeater)
Wenn das Widget ein items-Feld hat, liegen die Sub-Daten in page_widget_item. base_id kommt aus $widget_array['item_id']:
public static function getWidgetContent($widget_array, $backend = false)
{
$widgetBaseId = (int) ($widget_array['item_id'] ?? 0);
$language = real_escape_string($GLOBALS['current_language'] ?? 'de');
$q = query("SELECT * FROM page_widget_item
WHERE widget_id = $widgetBaseId
AND language_short LIKE '$language'
ORDER BY position ASC");
$items = [];
while ($row = fetch_assoc($q)) {
$row['image'] = files::loadURL($row['image']);
$items[] = $row;
}
return ['items' => $items];
}Details zum Items-System: Widget-Items / Repeater.
Pattern: Frontend- vs. Backend-Rendering
Manche Widgets laden im Live-Frontend Daten, wollen im Pagebuilder-Preview aber nur Platzhalter zeigen (z. B. um DB-Calls zu sparen oder Preview-Seitenrendering nicht zu stoeren):
public static function getWidgetContent($widget_array, $backend = false)
{
if ($backend) {
return ['items' => []]; // Platzhalter fuer Live-Preview
}
// Nur im echten Frontend: aufwendige Query
$items = [];
$q = query("SELECT * FROM blog WHERE visible = 1 ORDER BY postdate DESC LIMIT 10");
while ($row = fetch_assoc($q)) {
$items[] = $row;
}
return ['items' => $items];
}Das reale blog-Widget nutzt genau dieses Pattern: if (!$backend) { ...DB-Queries... }. Im Preview-Mode laedt es gar nichts, weil Blog-Eintraege erst im Live-Kontext mit URL-Parametern (?p=2, ?c=news, ?s=query) Sinn ergeben.
Pattern: Session-abhaengige Daten
Fuer User-spezifische Widgets (User-Center, My-Courses, personalisierte Empfehlungen):
public static function getWidgetContent($widget_array, $backend = false)
{
$userId = (int) ($_SESSION['userid'] ?? 0);
if ($userId === 0) {
return ['loggedIn' => false];
}
$q = query("SELECT id, title FROM elearning_purchases p
JOIN elearning_courses c ON c.id = p.course_id
WHERE p.user_id = $userId");
return [
'loggedIn' => true,
'courses' => fetch_all($q),
];
}Session-abhaengige Widgets cachen nicht
Der Response-Cache (APCu) greift nur fuer anonyme GETs. Widgets, die $_SESSION lesen, werden pro Request neu gerendert — das ist korrekt so, aber gut zu wissen fuer Performance-Tuning.
Pattern: Multi-Language mit Fallback
Bei mehrsprachigen Inhalten: aktuelle Sprache zuerst, bei leerem Resultat Fallback auf Basissprache.
public static function getWidgetContent($widget_array, $backend = false)
{
$currentLang = real_escape_string($GLOBALS['current_language'] ?? 'de');
$baseLang = real_escape_string($GLOBALS['baseLanguage'] ?? 'de');
$q = query("SELECT * FROM blog
WHERE visible = 1 AND language_short LIKE '$currentLang'
ORDER BY postdate DESC LIMIT 10");
if (num_rows($q) === 0 && $currentLang !== $baseLang) {
$q = query("SELECT * FROM blog
WHERE visible = 1 AND language_short LIKE '$baseLang'
ORDER BY postdate DESC LIMIT 10");
}
return ['items' => fetch_all($q)];
}Rueckgabe-Shape
Der zurueckgegebene Array ist komplett frei — das Vue-Template bestimmt, welche Keys es erwartet. Konventionell:
| Key | Inhalt |
|---|---|
items | Array fuer Listen-Widgets (Blog, Shop, Galerie) |
product / post / course | Einzel-Objekt fuer Teaser/Detail-Widgets |
loggedIn + user | Auth-Status fuer personalisierte Widgets |
config | Plugin-Config fuer Einstellungs-Widgets |
pages | Pagination-Daten |
Fehler-Handling
getWidgetContent() wird waehrend des normalen Page-Renderings aufgerufen. Fehler sollten nicht zu HTTP-500 eskalieren — sie verhindern das Rendering der ganzen Seite.
public static function getWidgetContent($widget_array, $backend = false)
{
try {
// DB-Operationen
return ['items' => $items];
} catch (Throwable $e) {
error_log('Widget xyz: ' . $e->getMessage());
return ['items' => []]; // stilles Fallback
}
}Die Vue-Komponente zeigt dann einen leeren Zustand — besser als kaputte Seite.
Performance
- Keine schweren Queries ohne Index — Widgets laufen bei jedem Seitenaufruf. Indizes auf Filter-Spalten setzen.
LIMITimmer setzen — auch wenn die Datenmenge "klein" ist, kann sie wachsen.$backend-Guard nutzen — Pagebuilder-Preview braucht keine echten Daten.- Response-Cache greift fuer anonyme GETs — aber nur wenn das Widget keine Session liest.
Haeufige Fehler
$widget_array['widget_text'] bei row: "text"
Wie oben erwaehnt: Key ist widget_html, nicht widget_text. Ergibt null statt des eingegebenen Textes.
Nicht-statische Methode
getWidgetContent() muss public static sein. Der Core ruft sie via ClassName::getWidgetContent($widget_array, $backend) auf. Eine Instanzmethode wird nicht gefunden und nicht ausgefuehrt.
real_escape_string vergessen
String-Parameter aus $widget_array (z. B. Kategorie-Namen, Slugs) in SQL-Queries: immer mit real_escape_string() escapen. Numerische Werte mit (int). Widgets laufen im Frontend-Kontext mit User-Eingaben aus URL-Parametern — SQL-Injection-relevant.
Sprach-Globals
Die aktive Sprache steht unter $GLOBALS['current_language'] (Snake-Case), die Hauptsprache unter $GLOBALS['baseLanguage'] (CamelCase — historisch inkonsistent). Beide Variablen haengen vom Aufruf-Kontext ab; bei Unsicherheit null coalesce mit Fallback: $GLOBALS['current_language'] ?? 'de'.
Keine URL-Aufloesung fuer Bilder
Formular-Wert widget_image ist ein JSON mit Media-ID, keine URL. Ohne files::loadURL() oder aequivalente Aufloesung bekommt das Vue-Template die rohe ID und kann das Bild nicht anzeigen.
Siehe auch
- Widget-Anatomie — Signatur der
_PageBuilderPlugin-Klasse - widget_menu-Feldtypen — welche
row-Werte es gibt - Widget-Items / Repeater —
page_widget_itemladen - Custom-CSS-Konvention — CSS-Wrapper pro Widget
- User-Center-Items — Vue-Level-Pattern fuer User-Widgets