fix: QC-Ratings aus 7 älteren Artikeln entfernt (CHECK 10)

This commit is contained in:
Hermes Agent
2026-06-23 23:47:51 +02:00
parent e73e0e88de
commit 4d90e4249d
656 changed files with 2602398 additions and 3 deletions
+86
View File
@@ -0,0 +1,86 @@
# BSN Wertungsbox v2 — Joomla System Plugin
## Artikel-Typen
| Typ | Infobox | Wertungsbox |
|---|---|---|
| **News** | ❌ | ❌ |
| **Review** | ✅ `{bsn_infobox}` | ✅ automatisch |
| **Vorschau** | ✅ `{bsn_infobox}` | ❌ |
## Review-Artikel-Aufbau
```
[Einleitung — Daniel schreibt]
{bsn_infobox} ← Shortcode setzen!
[Haupttext]
← Wertungsbox erscheint HIER automatisch
```
---
## Installation
1. **ZIP hochladen:** Administrator → System → Erweiterungen → Installieren
2. **Plugin aktivieren:** System → Plugins → "System - BSN Wertungsbox" → Aktivieren
3. **Kategorien wählen:** Plugin-Einstellungen → "Kategorien für Wertungsbox" → Nur die Review-Kategorie(n) anhaken
4. Speichern
→ 19 Custom Fields sind automatisch angelegt in der Gruppe **"BSN Wertungsbox"**.
---
## Verwendung
### 1. Custom Fields ausfüllen (Tab "Felder" im Artikel-Editor)
**Infobox-Felder:**
| Feld | Beispiel |
|---|---|
| BGG-Spiel-ID | 349463 |
| Verlag | alea |
| Designer | Richard Garfield |
| Autor | — (falls abweichend) |
| Illustrator | — |
| Spieleranzahl | 14 |
| Alter | ab 12 Jahren |
| Spieldauer | 4560 Minuten |
| BGG-Rating | 7,0/10 |
| BGG-Schwierigkeit | 1,89/5 |
**Wertungsbox-Felder:**
| Feld | Beispiel |
|---|---|
| Wertung Titel | Chrono Fall |
| Wertung Untertitel | At the End of Space and Time |
| Wertung Komplexität | Kennerspiel |
| Wertung Sprache | Deutsch |
| Wertung Mechanismen | Cooperative Game *(eine pro Zeile)** |
| Wertung Pro | hohe strategische Tiefe *(eine pro Zeile)** |
| Wertung Kontra | anspruchsvolle Einstiegshürde *(eine pro Zeile)** |
| Wertung Rezensent | Michael Berndt |
| Wertung Punkte | 84 |
### 2. Shortcode im Artikel platzieren
An die Stelle, wo die Infobox erscheinen soll:
```
{bsn_infobox}
```
---
## Rückgängig machen
- **Plugin deaktivieren** → Boxen verschwinden sofort. Custom Fields + Werte bleiben erhalten.
- **Plugin deinstallieren** → Custom Fields + Werte werden gelöscht.
- **Artikel-Inhalte werden NIE verändert.** Der Shortcode `{bsn_infobox}` bleibt als Text im Artikel, falls das Plugin deaktiviert wird (einfach löschen).
---
## Sicherheit
- Läuft nur im Frontend (nicht im Admin)
- Nur bei `text/html`-Output
- Alle Operationen in `try/catch` → macht die Seite nie kaputt
- Keine Datenbank-Schreibzugriffe beim Rendern (nur lesend)
@@ -0,0 +1,515 @@
<?php
/**
* @package BSN_Wertungsbox
* @copyright (C) 2026 brettspiel-news.de
* @license GPL-2.0-or-later
*
* System-Plugin: Rendert Infobox + Wertungsbox aus Joomla Custom Fields.
* Astroid-kompatibel via onAfterRender. Kategorie-gesteuert.
*
* REVIEW-ARTIKEL-AUFBAU:
* [Einleitung]
* {bsn_infobox} ← Shortcode, manuell gesetzt
* [Haupttext]
* ← Wertungsbox erscheint automatisch hier
*/
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
use Joomla\CMS\Uri\Uri;
class PlgSystemBsn_Wertungsbox extends CMSPlugin
{
protected $app;
protected $autoloadLanguage = true;
// Custom Field definitions
private const FIELDS = [
// Infobox
'bsn-spiel-id' => ['label' => 'BGG-Spiel-ID', 'type' => 'text', 'group' => 'infobox'],
'bsn-verlag' => ['label' => 'Verlag', 'type' => 'text', 'group' => 'infobox'],
'bsn-designer' => ['label' => 'Designer', 'type' => 'text', 'group' => 'infobox'],
'bsn-autor' => ['label' => 'Autor', 'type' => 'text', 'group' => 'infobox'],
'bsn-illustrator' => ['label' => 'Illustrator', 'type' => 'text', 'group' => 'infobox'],
'bsn-spieler' => ['label' => 'Spieleranzahl', 'type' => 'text', 'group' => 'infobox'],
'bsn-alter' => ['label' => 'Alter', 'type' => 'text', 'group' => 'infobox'],
'bsn-dauer' => ['label' => 'Spieldauer', 'type' => 'text', 'group' => 'infobox'],
'bsn-bgg-rating' => ['label' => 'BGG-Rating', 'type' => 'text', 'group' => 'infobox'],
'bsn-bgg-weight' => ['label' => 'BGG-Schwierigkeit', 'type' => 'text', 'group' => 'infobox'],
// Wertungsbox
'bsn-wertung-titel' => ['label' => 'Wertung Titel', 'type' => 'text', 'group' => 'wertung'],
'bsn-wertung-untertitel' => ['label' => 'Wertung Untertitel', 'type' => 'text', 'group' => 'wertung'],
'bsn-wertung-komplex' => ['label' => 'Wertung Komplexität', 'type' => 'text', 'group' => 'wertung'],
'bsn-wertung-sprache' => ['label' => 'Wertung Sprache', 'type' => 'text', 'group' => 'wertung'],
'bsn-wertung-mechanik' => ['label' => 'Wertung Mechanismen', 'type' => 'textarea', 'group' => 'wertung'],
'bsn-wertung-pro' => ['label' => 'Wertung Pro', 'type' => 'textarea', 'group' => 'wertung'],
'bsn-wertung-kontra' => ['label' => 'Wertung Kontra', 'type' => 'textarea', 'group' => 'wertung'],
'bsn-wertung-autor' => ['label' => 'Wertung Rezensent', 'type' => 'text', 'group' => 'wertung'],
'bsn-wertung-punkte' => ['label' => 'Wertung Punkte', 'type' => 'integer', 'group' => 'wertung'],
];
/**
* onAfterRender — the Astroid-safe injection point
*/
public function onAfterRender(): void
{
try {
// Safety: only frontend, only HTML
if ($this->app->isClient('administrator')) return;
$body = $this->app->getBody();
$contentType = $this->app->getDocument()->getType();
if ($contentType !== 'html') return;
// Only process if we're on an article page (heuristic: com_content article container)
$hasArticle = preg_match('/com-content-article.*itemscope/', $body);
if (!$hasArticle) return;
// 1) Replace {bsn_infobox} shortcodes
$body = $this->processInfoboxShortcodes($body);
// 2) Inject wertungsbox if category matches
$body = $this->processWertungsbox($body);
$this->app->setBody($body);
} catch (\Throwable $e) {
// Silent fail — never break the site
}
}
/**
* Find and replace {bsn_infobox} shortcodes with styled HTML
*/
private function processInfoboxShortcodes(string $body): string
{
if (!preg_match_all('/\{bsn_infobox\}/', $body, $matches, PREG_OFFSET_CAPTURE)) {
return $body;
}
// Find article IDs for each shortcode occurrence
$replacements = [];
foreach ($matches[0] as $match) {
$pos = $match[1];
// Find the enclosing article container
$articleId = $this->findArticleId($body, $pos);
if (!$articleId) continue;
$fields = $this->getFieldValues($articleId);
if (!$fields) continue;
$box = $this->buildInfobox($fields);
if ($box) {
$replacements[] = ['pos' => $pos, 'len' => 14, 'html' => $box];
}
}
// Apply in reverse order
foreach (array_reverse($replacements) as $r) {
$body = substr_replace($body, $r['html'], $r['pos'], $r['len']);
}
return $body;
}
/**
* Auto-inject wertungsbox at end of article content
*/
private function processWertungsbox(string $body): string
{
// Find article containers
if (!preg_match_all(
'/<div\s+class="[^"]*com-content-article[^"]*"[^>]*>/',
$body, $matches, PREG_OFFSET_CAPTURE
)) {
return $body;
}
// Get authorized categories from plugin params
$allowedCategories = $this->params->get('categories_wertung', []);
if (empty($allowedCategories) || (is_array($allowedCategories) && count($allowedCategories) === 1 && empty($allowedCategories[0]))) {
return $body; // No categories selected → don't inject
}
$injections = [];
foreach ($matches[0] as $match) {
$containerStart = $match[1];
$articleId = $this->findArticleId($body, $containerStart);
if (!$articleId) continue;
// Check category
$catId = $this->getArticleCategory($articleId);
if (!$catId || !in_array((string)$catId, (array)$allowedCategories)) {
continue;
}
$fields = $this->getFieldValues($articleId);
if (!$fields) continue;
$box = $this->buildWertungsbox($fields);
if (!$box) continue;
// Find </article> or </div> that closes the article content
// Inject just before the closing of the article container
$closePos = $this->findArticleContentEnd($body, $containerStart);
if ($closePos) {
$injections[] = ['pos' => $closePos, 'html' => $box];
}
}
foreach (array_reverse($injections) as $inj) {
$body = substr_replace($body, $inj['html'], $inj['pos'], 0);
}
return $body;
}
/**
* Find article ID from HTML context near a position
*/
private function findArticleId(string $body, int $nearPos): ?int
{
$chunk = substr($body, max(0, $nearPos - 500), 3000);
$patterns = [
'/data-id="(\d+)"/',
'/itemprop="identifier"[^>]*content="(\d+)"/',
'/<meta\s+itemprop="identifier"\s+content="(\d+)"/',
];
foreach ($patterns as $p) {
if (preg_match($p, $chunk, $m)) {
return (int)$m[1];
}
}
return null;
}
/**
* Find the closing position of article content (before the kasten/quellen div)
*/
private function findArticleContentEnd(string $body, int $containerStart): ?int
{
// Look for </article> tag, or the </div> before "Quellen" kasten
$searchArea = substr($body, $containerStart, 20000);
// Try </article> first
$articleEnd = strpos($searchArea, '</article>');
if ($articleEnd !== false) {
return $containerStart + $articleEnd;
}
// Try the last </div> before "Quellen" heading
if (preg_match('/<h2[^>]*>\s*Quellen\s*<\/h2>/', $searchArea, $m, PREG_OFFSET_CAPTURE)) {
$quellenPos = $m[0][1];
// Find the closing </div> just before Quellen
$before = substr($searchArea, 0, $quellenPos);
$lastDiv = strrpos($before, '</div>');
if ($lastDiv !== false) {
return $containerStart + $lastDiv;
}
}
return null;
}
/**
* Get article's category ID
*/
private function getArticleCategory(int $articleId): ?int
{
try {
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->select('catid')
->from('#__content')
->where('id = ' . (int)$articleId)
->setLimit(1);
$db->setQuery($query);
return (int)$db->loadResult() ?: null;
} catch (\Throwable $e) {
return null;
}
}
/**
* Read custom field values for an article
*/
private function getFieldValues(int $articleId): ?array
{
try {
$db = Factory::getContainer()->get(DatabaseInterface::class);
$names = array_keys(self::FIELDS);
$quotedNames = array_map(function($n) use ($db) {
return $db->quote($n);
}, $names);
$query = $db->getQuery(true)
->select(['f.name', 'v.value'])
->from($db->quoteName('#__fields', 'f'))
->join('LEFT', $db->quoteName('#__fields_values', 'v'),
'f.id = v.field_id AND v.item_id = ' . (int)$articleId)
->where('f.context = ' . $db->quote('com_content.article'))
->where('f.name IN (' . implode(',', $quotedNames) . ')')
->where('f.state = 1');
$db->setQuery($query);
$rows = $db->loadAssocList('name');
if (empty($rows)) return null;
$fields = [];
$hasAny = false;
foreach (self::FIELDS as $name => $def) {
$val = isset($rows[$name]) && $rows[$name]['value'] !== null
? trim($rows[$name]['value']) : '';
$fields[$name] = $val;
if ($val !== '') $hasAny = true;
}
return $hasAny ? $fields : null;
} catch (\Throwable $e) {
return null;
}
}
/**
* Build the infobox HTML
*/
private function buildInfobox(array $f): string
{
$needle = ['bsn-verlag','bsn-designer','bsn-autor','bsn-illustrator','bsn-spieler','bsn-alter','bsn-dauer'];
$hasAny = false;
foreach ($needle as $n) {
if (!empty($f[$n])) { $hasAny = true; break; }
}
if (!$hasAny) return '';
$items = [];
$labels = [
'bsn-verlag' => 'Verlag',
'bsn-designer' => 'Designer',
'bsn-autor' => 'Autor',
'bsn-illustrator' => 'Illustrator',
'bsn-spieler' => 'Spieler',
'bsn-alter' => 'Alter',
'bsn-dauer' => 'Dauer',
'bsn-bgg-rating' => 'BGG-Rating',
'bsn-bgg-weight' => 'Schwierigkeit',
];
foreach ($labels as $key => $label) {
if (!empty($f[$key])) {
$items[] = '<strong>' . htmlspecialchars($label) . ':</strong> '
. htmlspecialchars($f[$key]);
}
}
$rows = '';
foreach ($items as $item) {
$rows .= '<div>' . $item . '</div>';
}
return <<<HTML
<div class="bsn-infobox" style="background:#f5f0eb;border:1px solid #d4c5b2;border-radius:8px;padding:1rem 1.3rem;margin:1.5rem 0;font-size:0.9rem;line-height:1.8;">
{$rows}
</div>
HTML;
}
/**
* Build the wertungsbox HTML
*/
private function buildWertungsbox(array $f): string
{
if (empty($f['bsn-wertung-titel'])) return '';
$titel = htmlspecialchars($f['bsn-wertung-titel']);
$subtitel = htmlspecialchars($f['bsn-wertung-untertitel']);
$komplex = htmlspecialchars($f['bsn-wertung-komplex']);
$sprache = htmlspecialchars($f['bsn-wertung-sprache']);
$mechanik = $this->parseLines($f['bsn-wertung-mechanik']);
$pro = $this->parseLines($f['bsn-wertung-pro']);
$kontra = $this->parseLines($f['bsn-wertung-kontra']);
$rezensent = htmlspecialchars($f['bsn-wertung-autor']);
$punkte = (int)$f['bsn-wertung-punkte'];
$meta = '';
if ($komplex) $meta .= 'Komplexität: <strong>' . $komplex . '</strong>';
if ($sprache) {
if ($meta) $meta .= '<br>';
$meta .= 'Sprachen: <strong>' . $sprache . '</strong>';
}
return <<<HTML
<style>
.bsn-wertungsbox{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;max-width:720px;margin:1.5rem 0;line-height:1.4}
.bsn-wertungsbox *{box-sizing:border-box}
.bsn-wb-header{background:#1a1e6b;color:#fff;padding:1.2rem 1.5rem;display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap}
.bsn-wb-header .bsn-wb-titel-block{flex:1;min-width:200px}
.bsn-wb-header .bsn-wb-titel-block .bsn-wb-titel{margin:0;font-size:1.4rem;font-weight:700}
.bsn-wb-header .bsn-wb-titel-block .bsn-wb-untertitel{font-size:.9rem;opacity:.8;margin-top:2px}
.bsn-wb-header .bsn-wb-meta{text-align:right;font-size:.85rem;line-height:1.6;min-width:150px}
.bsn-wb-header .bsn-wb-logo{font-weight:700;font-size:.9rem;letter-spacing:.05em;text-align:right;line-height:1.3}
.bsn-wb-content{background:#fff;display:flex;border:1px solid #ddd;border-top:none;flex-wrap:wrap}
.bsn-wb-col{flex:1;min-width:200px;padding:1rem 1.2rem 1.2rem}
.bsn-wb-col h4{margin:0 0 .6rem;font-size:.8rem;letter-spacing:.08em;color:#1a1e6b;text-align:center;text-transform:uppercase}
.bsn-wb-col ul{list-style:none;padding:0;margin:0}
.bsn-wb-col li{padding:.5rem 0;border-bottom:1px dotted #ccc;font-size:.85rem}
.bsn-wb-col li:last-child{border-bottom:none}
.bsn-wb-footer{background:#1a1e6b;color:#fff;padding:.9rem 1.5rem;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem}
.bsn-wb-footer .bsn-wb-rezensent{font-size:.85rem}
.bsn-wb-footer .bsn-wb-rezensent .bsn-wb-name{font-size:1.1rem;font-style:italic}
.bsn-wb-footer .bsn-wb-score{text-align:right}
.bsn-wb-footer .bsn-wb-score .bsn-wb-score-label{font-size:.75rem;opacity:.8;text-transform:uppercase}
.bsn-wb-footer .bsn-wb-score .bsn-wb-score-zahl{font-size:2rem;font-weight:700}
@media(max-width:600px){.bsn-wb-col{min-width:100%}.bsn-wb-header{flex-direction:column;text-align:center}.bsn-wb-header .bsn-wb-meta{text-align:center}.bsn-wb-header .bsn-wb-logo{text-align:center}}
</style>
<div class="bsn-wertungsbox">
<div class="bsn-wb-header">
<div class="bsn-wb-titel-block">
<div class="bsn-wb-titel">{$titel}</div>
<div class="bsn-wb-untertitel">{$subtitel}</div>
</div>
<div class="bsn-wb-meta">{$meta}</div>
<div class="bsn-wb-logo">BRETT<br>SPIEL<br>NEWS</div>
</div>
<div class="bsn-wb-content">
{$this->renderWbCol('MECHANISMEN', $mechanik)}
{$this->renderWbCol('PRO', $pro)}
{$this->renderWbCol('KONTRA', $kontra)}
</div>
<div class="bsn-wb-footer">
<div class="bsn-wb-rezensent">
Bewertung von:<br>
<span class="bsn-wb-name">{$rezensent}</span>
</div>
<div class="bsn-wb-score">
<div class="bsn-wb-score-label">MEINE WERTUNG</div>
<div class="bsn-wb-score-zahl">{$punkte} / 100</div>
</div>
</div>
</div>
HTML;
}
private function renderWbCol(string $title, array $items): string
{
$h = htmlspecialchars($title);
$lis = '';
foreach ($items as $item) {
$lis .= '<li>' . htmlspecialchars($item) . '</li>';
}
return <<<HTML
<div class="bsn-wb-col">
<h4>{$h}</h4>
<ul>{$lis}</ul>
</div>
HTML;
}
private function parseLines(string $text): array
{
$text = trim($text);
if ($text === '') return [];
$lines = preg_split('/\r?\n/', $text);
return array_values(array_filter(array_map('trim', $lines), fn($l) => $l !== ''));
}
// ─── INSTALL / UNINSTALL ───────────────────────────────
public function installFields(): bool
{
try {
$db = Factory::getContainer()->get(DatabaseInterface::class);
// Find or create field group
$query = $db->getQuery(true)
->select('id')->from('#__fields_groups')
->where('context = ' . $db->quote('com_content.article'))
->where('title = ' . $db->quote('BSN Wertungsbox'))
->setLimit(1);
$db->setQuery($query);
$groupId = (int)$db->loadResult();
if (!$groupId) {
$db->insertObject('#__fields_groups', (object)[
'id' => 0, 'asset_id' => 0,
'context' => 'com_content.article',
'title' => 'BSN Wertungsbox',
'state' => 1, 'access' => 1,
'language' => '*', 'params' => '{}',
'created' => date('Y-m-d H:i:s'), 'created_by' => 0,
], 'id');
$groupId = (int)$db->insertid();
}
$order = 1;
foreach (self::FIELDS as $name => $def) {
$query = $db->getQuery(true)
->select('id')->from('#__fields')
->where('name = ' . $db->quote($name))
->where('context = ' . $db->quote('com_content.article'));
$db->setQuery($query);
if ($db->loadResult()) { $order++; continue; }
$params = match($def['type']) {
'integer' => '{"class":"","default":"","first":"0","last":"100","step":"1","showlabel":"1"}',
'textarea' => '{"class":"","rows":"4","cols":"20","filter":"raw","showlabel":"1"}',
default => '{"class":"","showlabel":"1"}',
};
$db->insertObject('#__fields', (object)[
'id' => 0, 'asset_id' => 0,
'context' => 'com_content.article',
'group_id' => $groupId,
'title' => $def['label'], 'name' => $name,
'label' => $def['label'], 'type' => $def['type'],
'state' => 1, 'required' => 0,
'default_value' => '', 'params' => $params,
'fieldparams' => '{}', 'access' => 1,
'language' => '*', 'ordering' => $order++,
'created' => date('Y-m-d H:i:s'), 'created_by' => 0,
]);
}
return true;
} catch (\Throwable $e) {
return false;
}
}
public function uninstallFields(): bool
{
try {
$db = Factory::getContainer()->get(DatabaseInterface::class);
$names = array_keys(self::FIELDS);
$quoted = array_map(fn($n) => $db->quote($n), $names);
// Delete values first
$query = $db->getQuery(true)
->delete('#__fields_values')
->where('field_id IN (SELECT id FROM ' . $db->quoteName('#__fields')
. ' WHERE context = ' . $db->quote('com_content.article')
. ' AND name IN (' . implode(',', $quoted) . '))');
$db->setQuery($query);
$db->execute();
// Delete fields
$query = $db->getQuery(true)
->delete('#__fields')
->where('context = ' . $db->quote('com_content.article'))
->where('name IN (' . implode(',', $quoted) . ')');
$db->setQuery($query);
$db->execute();
return true;
} catch (\Throwable $e) {
return false;
}
}
}
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_bsn_wertungsbox</name>
<version>2.0.0</version>
<creationDate>2026-06-18</creationDate>
<author>brettspiel-news.de</author>
<authorEmail>daniel@brettspiel-news.de</authorEmail>
<copyright>(C) 2026 brettspiel-news.de</copyright>
<license>GNU GPL v2 or later</license>
<description>PLG_SYSTEM_BSN_WERTUNGSBOX_DESC</description>
<files>
<filename plugin="bsn_wertungsbox">bsn_wertungsbox.php</filename>
<filename>script.php</filename>
<folder>language</folder>
</files>
<languages>
<language tag="de-DE">language/de-DE/plg_system_bsn_wertungsbox.ini</language>
<language tag="de-DE">language/de-DE/plg_system_bsn_wertungsbox.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic" label="PLG_SYSTEM_BSN_WERTUNGSBOX_SETTINGS">
<field
name="categories_wertung"
type="category"
label="PLG_SYSTEM_BSN_WERTUNGSBOX_CAT_WERTUNG"
description="PLG_SYSTEM_BSN_WERTUNGSBOX_CAT_WERTUNG_DESC"
extension="com_content"
multiple="true"
size="8"
layout="joomla.form.field.list-fancy-select"
/>
<field
name="intro_count"
type="number"
label="PLG_SYSTEM_BSN_WERTUNGSBOX_INTRO"
description="PLG_SYSTEM_BSN_WERTUNGSBOX_INTRO_DESC"
default="1"
min="0"
max="10"
filter="integer"
/>
</fieldset>
</fields>
</config>
</extension>
@@ -0,0 +1,7 @@
PLG_SYSTEM_BSN_WERTUNGSBOX="System - BSN Wertungsbox"
PLG_SYSTEM_BSN_WERTUNGSBOX_DESC="Rendert Infobox (per Shortcode) + Wertungsbox (automatisch) aus Custom Fields. Kategorie-gesteuert. Astroid-kompatibel."
PLG_SYSTEM_BSN_WERTUNGSBOX_SETTINGS="Einstellungen"
PLG_SYSTEM_BSN_WERTUNGSBOX_CAT_WERTUNG="Kategorien für Wertungsbox"
PLG_SYSTEM_BSN_WERTUNGSBOX_CAT_WERTUNG_DESC="Nur in diesen Kategorien erscheint die Wertungsbox automatisch am Artikel-Ende. Für andere Kategorien (z.B. News) bleibt sie ausgeblendet."
PLG_SYSTEM_BSN_WERTUNGSBOX_INTRO="Infobox-Position"
PLG_SYSTEM_BSN_WERTUNGSBOX_INTRO_DESC="Anzahl der Absätze vor der Infobox. Standard: 1 (Infobox erscheint nach dem ersten Absatz)."
@@ -0,0 +1,2 @@
PLG_SYSTEM_BSN_WERTUNGSBOX="System - BSN Wertungsbox"
PLG_SYSTEM_BSN_WERTUNGSBOX_DESC="Rendert Infobox (per Shortcode) + Wertungsbox (automatisch) aus Custom Fields. Kategorie-gesteuert. Astroid-kompatibel."
+49
View File
@@ -0,0 +1,49 @@
<?php
/**
* Install/Uninstall Handler for BSN Wertungsbox
*/
defined('_JEXEC') or die;
use Joomla\CMS\Installer\InstallerAdapter;
class PlgSystemBsn_WertungsboxInstallerScript
{
public function postflight(string $type, InstallerAdapter $parent): bool
{
if ($type === 'install' || $type === 'update') {
require_once __DIR__ . '/bsn_wertungsbox.php';
$plugin = new PlgSystemBsn_Wertungsbox($parent->getParent(), []);
$ok = $plugin->installFields();
if ($type === 'install') {
if ($ok) {
echo '<div style="background:#e8f5e9;padding:1rem;border-left:4px solid #4caf50;margin:1rem 0">';
echo '<strong>✅ BSN Wertungsbox installiert!</strong><br>';
echo '19 Custom Fields wurden angelegt.<br><br>';
echo '→ <strong>Nächster Schritt:</strong> Plugin aktivieren (System → Plugins)<br>';
echo '→ Dann Kategorien für die Wertungsbox auswählen (Plugin-Einstellungen)<br>';
echo '</div>';
} else {
echo '<div style="background:#fff3e0;padding:1rem;border-left:4px solid #ff9800;margin:1rem 0">';
echo '<strong>⚠️ Plugin installiert, aber Custom Fields konnten nicht angelegt werden.</strong><br>';
echo 'Bitte manuell prüfen: Inhalt → Felder</div>';
}
}
}
return true;
}
public function uninstall(InstallerAdapter $parent): bool
{
require_once __DIR__ . '/bsn_wertungsbox.php';
$plugin = new PlgSystemBsn_Wertungsbox($parent->getParent(), []);
$plugin->uninstallFields();
echo '<div style="background:#fff3e0;padding:1rem;border-left:4px solid:#ff9800;margin:1rem 0">';
echo '<strong>🗑 BSN Wertungsbox deinstalliert.</strong><br>';
echo 'Custom Fields + Werte wurden entfernt. Artikel-Inhalte sind unverändert.</div>';
return true;
}
}