Skip to content

About

REDAXO KB

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Knowledge Base

Ein REDAXO-Add-on zur Verwaltung und Ausgabe strukturierter Wissensinhalte mit Artikeln, Glossar, Suche und interaktiven Bildern.

Projektstatus

  • Status: aktive Entwicklung
  • Frontend-Unterstützung: aktuell UIKit
  • Weitere Frontend-Frameworks: geplant
  • Eine spätere Überführung zu FriendsOfREDAXO ist nicht ausgeschlossen

Funktionsumfang

  • Verwaltung mehrerer Wissensbasen
  • Artikel mit Navigation und Kapitelstruktur
  • Glossar mit Verlinkung im Artikeltext
  • Volltextsuche mit Trefferauszug
  • Interaktive Bilder mit Marker-Editor im Backend

Voraussetzungen

  • REDAXO >= 5.18
  • PHP >= 8.2
  • yform >= 5.0
  • fields >= 1.4.0
  • builder

Hinweis zu Abhängigkeiten:

Für den vollen Funktionsumfang werden Add-ons benötigt, die je nach Projektkontext nicht immer öffentlich verfügbar sind. Das betrifft insbesondere builder in einer kompatiblen Projektversion.

Installation

  1. Add-on nach redaxo/src/addons/knowledgebase kopieren.
  2. Abhängigkeiten installieren und aktivieren (yform, builder).
  3. Add-on im REDAXO-Backend installieren.
  4. Update-Skripte ausführen, damit Tabellen, Indizes und Suchdaten konsistent sind.

Hinweis für Container-Setups:

  • Nach Änderungen an PHP-Dateien ggf. den OPcache leeren.

URL-Addon-Integration: automatische URL-Profil-Generierung

Ja, das Add-on kann URL-Profile automatisch für Wissensbasen erzeugen und synchron halten. Damit entstehen saubere Frontend-URLs ohne manuelle Profilpflege pro Datensatz.

Voraussetzungen

  • url Add-on (v2) aktiv
  • Eine Seite, auf der das Knowledgebase-Modul eingebunden ist

Was automatisch passiert

  1. Im Backend-Bereich "URL-Profile & Sitemap" wird eine Wissensbasis einem REDAXO-Artikel (Modul-Seite) zugeordnet.
  2. Beim Speichern wird automatisch ein Profil mit Namespace knowledgebase_{id} angelegt oder aktualisiert.
  3. Das Profil wird auf die jeweilige Wissensbasis eingegrenzt (knowledgebase_id = {id}).
  4. URLs werden direkt neu aufgebaut.
  5. Beim Entfernen einer Zuordnung wird das zugehörige Profil wieder gelöscht.

Zusätzlich erzeugte Routen

Neben den Artikel-URLs erzeugt das Add-on auch eigene Pfade für zentrale Ansichten:

  • Glossar: /.../glossar/
  • Inhaltsverzeichnis: /.../inhaltsverzeichnis/
  • Suche: /.../suche/?q=...

Die Suche verwendet bei aktivem Profil automatisch den sauberen Suchpfad und nicht mehr die alten kb_*-Queryparameter.

Wichtige Regel

Ein REDAXO-Artikel darf nur einer Wissensbasis zugeordnet werden. Doppelte Zuordnungen werden serverseitig validiert und mit Fehlermeldung abgewiesen.

Troubleshooting

TinyMCE: automatischer Sprung zum Editor im Backend

In einzelnen REDAXO-Backend-Formularen kann es vorkommen, dass der Cursor beim Laden automatisch in den ersten TinyMCE-Editor gesetzt wird. Dadurch springt die Seite sofort nach unten.

Einordnung:

  • Das ist in der Regel kein isolierter Fehler eines einzelnen Knowledge-Base-Elements.
  • Meist ist es ein Zusammenspiel aus TinyMCE-Initialfokus, Formularaufbau und Browser-Scrollverhalten.

Lösung im Add-on:

  • Für knowledgebase/articles (Ansicht func=add) wird ein gezielter Fokus-Schutz geladen.
  • Der initiale Editor-Fokus wird abgefangen, damit der Einstieg im Formular oben bleibt.

Wichtig bei neuen Asset-Dateien:

  • Nach neuen Dateien unter redaxo/src/addons/knowledgebase/assets müssen die öffentlichen Add-on-Assets synchronisiert werden.
  • Wenn die Datei nicht unter public/assets/addons/knowledgebase verfügbar ist, kann der Fix im Backend scheinbar "nicht greifen".

Backend-Bereiche

  • Übersicht
  • Frontend-Texte
  • Wissensbasen
  • Glossar
  • Interaktive Bilder
  • Beiträge

Content Builder

Das Add-on registriert eigene Content-Builder-Elemente und ergänzt den vorhandenen Element-Pool.

Aktueller Fokus:

  • UIKit-Ausgabe im Frontend
  • Auswahl und Ausgabe wiederverwendbarer interaktiver Bilder

Elemente des Add-ons:

  • kb_chapter_nav – Kapitelmarke (Navigation und Inhaltsverzeichnis)
  • kb_text – Fließtext eines Beitrags (TinyMCE), ohne eigene Section/Container; gehört unter eine Kapitelmarke
  • kb_interactive_image, kb_recent_articles

Layout-Modus

Einstellung je Wissensbasis (Feld „Layout-Modus“):

Modus Navigation Abschnitte eines Beitrags
Klassisch Sidebar links, mitlaufend mit eigener Scrollfläche in der Sidebar unter dem Beitrag
Kompakt wie Klassisch, kleinerer Kopf in der Sidebar
Fokus ohne Sidebar; Menü-Knopf öffnet auf dem Desktop eine Schublade am Rand des Inhaltsbereichs, mobil das Off-Canvas in der Schublade
Handbuch Sidebar links nur mit den Kapiteln (Beiträgen) Übersicht „In diesem Kapitel“: ab 1200 px mitlaufende Spalte rechts, darunter aufklappbarer Kasten unter der Überschrift
Handbuch kompakt wie Handbuch immer als aufklappbares Menü „In diesem Kapitel“ unter der Überschrift (auch Desktop), keine Spalte rechts

„Handbuch“ passt für lange Handreichungen mit vielen Abschnitten je Kapitel: die Sidebar bleibt kurz, die Abschnitte stehen neben dem Text.

Abstand: Im Element kb_chapter_nav lässt sich unter „Abstand davor“ mehr Luft vor einem Kapitel einstellen (Mehr / Viel), z. B. wenn ein neuer Themenblock beginnt.

Kapitelnummern: Im Element kb_chapter_nav die Nummer als Freitext setzen (z. B. „2.1“) und „Nummer auch in der Überschrift zeigen“ aktivieren – dann steht sie auch vor dem Titel im Inhalt (Klasse .kb-chapter-nav__number).

Aussehen anpassen

Schrift und Farben der Ausgabe lassen sich über CSS-Variablen auf .kb-app an die Website angleichen, z. B.:

.kb-app {
    --kb-heading-font: Georgia, serif;   /* Standard: Schrift der Website (inherit) */
    --kb-heading-color: #222;
    --kb-accent: #418032;     /* Links, aktive Navigation, Markierungen */
    --kb-accent-2: #2f5f24;   /* dunkler Ton für Links/Hover */
    --kb-ink: #222;           /* dunkle Schrift in Navigation und Kopf */
    --kb-highlight: #418032;  /* Linie am Anreißer, Hervorhebungen */
}

Helle Hintergründe und Rahmen (--kb-accent-soft, --kb-accent-line, --kb-accent-muted, --kb-highlight-soft) werden per color-mix() daraus abgeleitet und lassen sich bei Bedarf einzeln setzen.

Links im Beitrag kennzeichnen

Unter Einstellungen → Links im Beitrag lassen sich zwei Kennzeichnungen einschalten (beide standardmäßig aus):

  • PDF-Links auszeichnen: Links auf PDF-Dateien aus dem Medienpool bekommen „(PDF, 1,2 MB)“, externe PDF-Dateien „(PDF)“. Die Größe externer Dateien wird nicht abgefragt.
  • Externe Links kennzeichnen: Symbol hinter dem Link, für Screenreader „(externer Link)“, bei target="_blank" zusätzlich „öffnet in neuem Fenster“. Externe PDF-Links bekommen „(PDF)“.

Das gilt für Anreißer und Beitragsinhalt. Nicht gekennzeichnet werden:

  • verlinkte Bilder und Grafiken (der Alt-Text beschreibt das Ziel),
  • Links ohne Text, Sprungmarken, E-Mail- und Telefon-Links,
  • Glossar-Links und Links, deren Text „PDF“ schon nennt.

Links auf andere Domains dieser Installation (YRewrite) gelten als intern. Die Texte sind unter „Frontend-Texte“ anpassbar.

Hinweis für die Redaktion: Ist die Kennzeichnung aktiv, „(PDF)“, Dateigrößen oder „externer Link“ nicht mehr selbst in den Linktext schreiben, sonst erscheint es doppelt.

Für Tests ohne Aktivierung: LinkMarker::enhance($html, true, true).

Glossar-Verlinkung

Ein Glossarbegriff wird je Beitrag nur beim ersten Vorkommen verlinkt. Alle Vorkommen verlinken: rex_config::set('knowledgebase', 'glossary_link_all', true);

Interaktive Bilder

Interaktive Bilder werden als eigene Datensätze gepflegt und anschließend in Artikeln referenziert.

Funktionen

  • Bild auswählen
  • Marker per Klick setzen
  • Marker per Drag positionieren
  • Titel und Inhalt pro Marker pflegen
  • Ausgabe als nummerierte Marker im Frontend
  • Responsives Marker-Menü, mobil standardmäßig sichtbar
  • Responsives Marker-Menü, im Desktop per Toggle einblendbar

Suche

Die Suche arbeitet zweistufig:

  1. Gewichtete Fulltext-Suche auf title, nav_title, intro und search_text (MATCH AGAINST)
  2. Fallback mit LIKE auf title, nav_title, intro, search_text und content

Treffer können optional mit dem Hinweis „Kürzlich aktualisiert“ markiert werden. Der Zeitraum ist in den AddOn-Einstellungen konfigurierbar (Standard: 14 Tage).

Aufbau von search_text

search_text wird beim Speichern oder Aktualisieren eines Artikels automatisch erzeugt aus:

  1. title
  2. nav_title
  3. intro
  4. extrahierten Textinhalten aus dem Content Builder
  5. Tags

Damit werden auch textuelle Inhalte aus Content-Builder-Feldern berücksichtigt.

Integration in AI Chat / KLXM Chat (Content-Provider)

Mit AI Chat (FriendsOfREDAXO/ai_chat) wird der Provider automatisch erkannt – keine Registrierung nötig. Der Provider implementiert die Schnittstelle des jeweils installierten Chat-AddOns (FriendsOfRedaxo\\AiChat\\… bzw. FriendsOfRedaxo\\KlxmChat\\…).

Die Knowledgebase kann Inhalte für den KLXM-Chat-Index bereitstellen. Dafür registriert sich das Addon selbst über den Extension-Point KLXMCHAT_CONTENT_PROVIDERS.

Vollständiges Registrierungsbeispiel in boot.php

if (rex_addon::exists('klxmchat') && rex_addon::get('klxmchat')->isAvailable()) {
	rex_extension::register('KLXMCHAT_CONTENT_PROVIDERS', static function (rex_extension_point $ep): array {
		$providers = $ep->getSubject();
		if (!is_array($providers)) {
			$providers = [];
		}

		if (class_exists(\FriendsOfRedaxo\KlxmChat\ContentProvider\ContentProviderInterface::class)) {
			$providers['knowledgebase'] = new \FriendsOfREDAXO\Knowledgebase\ContentProvider\KnowledgebaseContentProvider();
		}

		return $providers;
	});
}

API-Erklärung: Was der Provider liefern muss

Die Klasse muss FriendsOfRedaxo\KlxmChat\ContentProvider\ContentProviderInterface implementieren.

Pflichtmethoden und Zweck:

  • getKey()
    • technischer, stabiler Schlüssel; für Knowledgebase knowledgebase
  • getLabel()
    • Name in den KLXM-Chat-Einstellungen
  • isAvailable()
    • prüft Abhängigkeiten (z.B. eigenes Addon aktiv)
  • getSupportedSourceTypes()
    • liefert die vom Provider erzeugten Source-Types
  • getSourceTypeLabels()
    • lesbare Labels für Filter und Backend-Listen
  • collectTasks()
    • sammelt indexierbare Einträge als Task-Liste
  • prepareDocument(array $task)
    • baut aus einem Task das finale Index-Dokument
  • getPromptInstruction()
    • ergänzt provider-spezifische Anweisung für die Antwortqualität
  • getSearchIconSvg(string $sourceType)
    • optionales Icon je Source-Type

Vollständiges Provider-Beispiel

<?php

declare(strict_types=1);

namespace FriendsOfREDAXO\Knowledgebase\ContentProvider;

use FriendsOfREDAXO\Knowledgebase\KnowledgebaseUrl;
use FriendsOfREDAXO\Knowledgebase\SearchTextExtractor;
use FriendsOfRedaxo\KlxmChat\ContentProvider\ContentProviderInterface;
use rex;
use rex_addon;

final class KnowledgebaseContentProvider implements ContentProviderInterface
{
	public function getKey(): string
	{
		return 'knowledgebase';
	}

	public function getLabel(): string
	{
		return 'Knowledgebase Beiträge indexieren';
	}

	public function isAvailable(): bool
	{
		return rex_addon::exists('knowledgebase') && rex_addon::get('knowledgebase')->isAvailable();
	}

	public function getSupportedSourceTypes(): array
	{
		return ['knowledgebase_article'];
	}

	public function getSourceTypeLabels(): array
	{
		return ['knowledgebase_article' => 'Knowledgebase'];
	}

	public function getPromptInstruction(): string
	{
		return 'Knowledgebase enthält redaktionelle Hilfetexte und Anleitungen.';
	}

	public function getSearchIconSvg(string $sourceType): string
	{
		return $sourceType === 'knowledgebase_article' ? '<svg viewBox="0 0 24 24"><path d="..."/></svg>' : '';
	}

	public function collectTasks(): array
	{
		if (!$this->isAvailable()) {
			return [];
		}

		$sql = rex_sql::factory();
		$rows = $sql->getArray(
			'SELECT a.id, a.knowledgebase_id, a.title, a.nav_title, a.updatedate, a.createdate '
			. 'FROM ' . rex::getTable('knowledgebase_article') . ' a '
			. 'INNER JOIN ' . rex::getTable('knowledgebase') . ' b ON b.id = a.knowledgebase_id '
			. 'WHERE a.online = 1 AND b.online = 1'
		);

		$tasks = [];
		foreach ($rows as $row) {
			$articleId = (int) ($row['id'] ?? 0);
			$knowledgebaseId = (int) ($row['knowledgebase_id'] ?? 0);
			if ($articleId <= 0 || $knowledgebaseId <= 0) {
				continue;
			}

			$title = trim((string) ($row['nav_title'] ?? ''));
			if ($title === '') {
				$title = trim((string) ($row['title'] ?? ''));
			}

			$tasks[] = [
				'type' => 'provider_item',
				'provider' => $this->getKey(),
				'source_type' => 'knowledgebase_article',
				'source_id' => $knowledgebaseId . ':' . $articleId,
				'knowledgebase_id' => $knowledgebaseId,
				'article_id' => $articleId,
				'title' => $title !== '' ? $title : ('Knowledgebase Beitrag #' . $articleId),
				'updatedate_ts' => time(),
			];
		}

		return $tasks;
	}

	public function prepareDocument(array $task): ?array
	{
		if (!$this->isAvailable()) {
			return null;
		}

		$knowledgebaseId = (int) ($task['knowledgebase_id'] ?? 0);
		$articleId = (int) ($task['article_id'] ?? 0);
		if ($knowledgebaseId <= 0 || $articleId <= 0) {
			return null;
		}

		$article = \rex_data_knowledgebase_article::get($articleId);
		$knowledgebase = \rex_data_knowledgebase::findOnlineById($knowledgebaseId);
		if (!$article instanceof \rex_data_knowledgebase_article || !$knowledgebase instanceof \rex_data_knowledgebase) {
			return null;
		}

		$title = $article->getNavLabel();
		if ($title === '') {
			$title = trim((string) $article->getValue('title'));
		}

		$intro = trim((string) $article->getValue('intro'));
		$content = SearchTextExtractor::extractFromContentBuilder((string) $article->getValue('content'));

		$docText = trim(implode("\n", array_filter([
			'Wissensbasis: ' . trim((string) $knowledgebase->getValue('title')),
			'Titel: ' . $title,
			$intro !== '' ? 'Einleitung: ' . SearchTextExtractor::normalize($intro) : '',
			$content !== '' ? 'Inhalt: ' . $content : '',
		])));

		if ($docText === '') {
			return null;
		}

		$slug = trim((string) $article->getValue('slug'));
		$url = '';
		if ($slug !== '' && KnowledgebaseUrl::hasProfile($knowledgebaseId)) {
			$url = KnowledgebaseUrl::getArticleUrl($knowledgebaseId, $slug);
		}

		return [
			'source_type' => 'knowledgebase_article',
			'source_id' => $knowledgebaseId . ':' . $articleId,
			'title' => $title !== '' ? $title : ('Knowledgebase Beitrag #' . $articleId),
			'content' => $docText,
			'url' => $url,
			'updatedate_ts' => time(),
		];
	}
}

Wichtige Regeln für URL-Felder

  • URL nur setzen, wenn eine stabile öffentliche URL aufgelöst werden kann.
  • In Knowledgebase bedeutet das: ohne URL-Profil keine URL ausgeben.

Aktivierung in KLXM Chat

  • Nach Registrierung erscheint der Provider in KLXM Chat unter Zusätzliche Content-Provider.
  • Indexierung erfolgt erst, wenn der Provider dort aktiviert und gespeichert wurde.

Konfigurierbare Frontend-Texte

Frontend-Texte können pro Sprache im Backend-Bereich Frontend-Texte gepflegt werden.

Einschränkungen

  • Derzeit ist ausschließlich UIKit offiziell unterstützt
  • Weitere Frameworks folgen schrittweise

Perspektive

Das Add-on wird aktuell im projektspezifischen Einsatz weiterentwickelt. Eine spätere Übernahme beziehungsweise Überführung in den FriendsOfREDAXO-Kontext ist möglich.

Lizenz

MIT License

Copyright (c) 2026 KLXM Crossmedia GmbH, im Auftrag der HAGE (hage.de)

Der vollständige Lizenztext befindet sich in LICENSE.md.

About

REDAXO KB

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages