Initial plugin commit

This commit is contained in:
2026-04-12 22:50:16 +02:00
commit d36023a4a3
6 changed files with 491 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.DS_Store
Thumbs.db
*.zip

22
assets/admin.css Executable file
View File

@@ -0,0 +1,22 @@
.post-type-kgv_faq #post-body-content .editor-post-title,
.post-type-kgv_faq #titlediv #title {
font-weight: 600;
}
.post-type-kgv_faq .misc-pub-section.misc-pub-post-status,
.post-type-kgv_faq .misc-pub-section.curtime {
display: block;
}
.post-type-kgv_faq .column-kgv_faq_order {
width: 90px;
}
.post-type-kgv_faq .column-kgv_faq_answer {
width: 35%;
}
.taxonomy-kgv_faq_cat .wrap h1,
.post-type-kgv_faq .wrap h1 {
margin-bottom: 12px;
}

91
assets/front.css Executable file
View File

@@ -0,0 +1,91 @@
.kgv-faq-wrapper {
margin: 30px 0;
}
.kgv-faq-filter {
margin-bottom: 20px;
}
.kgv-faq-filter select,
.kgv-faq-search {
min-width: 240px;
max-width: 100%;
padding: 10px 12px;
border: 1px solid #d0d7de;
border-radius: 8px;
background: #fff;
}
.kgv-faq-search-wrap {
margin-bottom: 20px;
}
.kgv-faq-list {
display: grid;
gap: 14px;
}
.kgv-faq-item {
border: 1px solid #e5e7eb;
border-radius: 12px;
background: #fff;
overflow: hidden;
}
.kgv-faq-question {
margin: 0;
}
.kgv-faq-toggle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px 18px;
background: #fff;
border: 0;
cursor: pointer;
text-align: left;
font-size: 18px;
font-weight: 600;
color: #111827;
}
.kgv-faq-toggle:hover,
.kgv-faq-toggle:focus {
background: #f9fafb;
outline: none;
}
.kgv-faq-icon {
flex: 0 0 auto;
font-size: 22px;
line-height: 1;
color: #6b7280;
}
.kgv-faq-toggle.is-open .kgv-faq-icon {
transform: rotate(45deg);
}
.kgv-faq-answer {
border-top: 1px solid #eef2f7;
}
.kgv-faq-answer__inner {
padding: 16px 18px 18px;
color: #374151;
}
.kgv-faq-answer__inner > *:first-child {
margin-top: 0;
}
.kgv-faq-answer__inner > *:last-child {
margin-bottom: 0;
}
.kgv-faq-item.is-hidden {
display: none;
}

44
assets/front.js Executable file
View File

@@ -0,0 +1,44 @@
document.addEventListener('click', function (event) {
const button = event.target.closest('[data-kgv-faq-toggle]');
if (!button) {
return;
}
const panelId = button.getAttribute('aria-controls');
const panel = document.getElementById(panelId);
if (!panel) {
return;
}
const isOpen = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
button.classList.toggle('is-open', !isOpen);
panel.classList.toggle('is-open', !isOpen);
if (isOpen) {
panel.setAttribute('hidden', '');
} else {
panel.removeAttribute('hidden');
}
});
document.addEventListener('input', function (event) {
const input = event.target.closest('[data-kgv-faq-search]');
if (!input) {
return;
}
const wrapper = input.closest('[data-kgv-faq]');
if (!wrapper) {
return;
}
const value = input.value.trim().toLowerCase();
const items = wrapper.querySelectorAll('[data-kgv-faq-item]');
items.forEach(function (item) {
const text = item.textContent.toLowerCase();
item.classList.toggle('is-hidden', value && !text.includes(value));
});
});

303
kgv-faq.php Executable file
View File

@@ -0,0 +1,303 @@
<?php
/**
* Plugin Name: KGV FAQ
* Description: FAQ-Verwaltung mit Fragen, Antworten, Kategorien, sauberem Backend und Shortcode-Ausgabe.
* Version: 1.0.0
* Author: Ronny Grobel
* Update URI: https://git.apex-project.de/RonnyG/KGV-FAQ
* GitHub Plugin URI: https://git.apex-project.de/RonnyG/KGV-FAQ
*/
if (!defined('ABSPATH')) {
exit;
}
final class KGV_FAQ_Plugin {
const VERSION = '1.0.0';
const POST_TYPE = 'kgv_faq';
const TAXONOMY = 'kgv_faq_cat';
public function __construct() {
add_action('init', [$this, 'register_post_type']);
add_action('init', [$this, 'register_taxonomy']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
add_action('wp_enqueue_scripts', [$this, 'enqueue_front_assets']);
add_filter('manage_' . self::POST_TYPE . '_posts_columns', [$this, 'admin_columns']);
add_action('manage_' . self::POST_TYPE . '_posts_custom_column', [$this, 'admin_column_content'], 10, 2);
add_filter('manage_edit-' . self::POST_TYPE . '_sortable_columns', [$this, 'sortable_columns']);
add_shortcode('kgv_faq', [$this, 'faq_shortcode']);
register_activation_hook(__FILE__, [$this, 'activate']);
register_deactivation_hook(__FILE__, [$this, 'deactivate']);
}
public function activate() {
$this->register_post_type();
$this->register_taxonomy();
flush_rewrite_rules();
}
public function deactivate() {
flush_rewrite_rules();
}
public function register_post_type() {
register_post_type(self::POST_TYPE, [
'labels' => [
'name' => 'FAQ',
'singular_name' => 'FAQ',
'add_new' => 'Neu hinzufügen',
'add_new_item' => 'Neue Frage hinzufügen',
'edit_item' => 'Frage bearbeiten',
'new_item' => 'Neue Frage',
'view_item' => 'Frage ansehen',
'search_items' => 'Fragen durchsuchen',
'not_found' => 'Keine Fragen gefunden',
'not_found_in_trash' => 'Keine Fragen im Papierkorb gefunden',
'menu_name' => 'FAQ',
],
'public' => false,
'publicly_queryable' => false,
'exclude_from_search' => true,
'has_archive' => false,
'rewrite' => false,
'query_var' => false,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => false,
'show_in_admin_bar' => true,
'show_in_rest' => true,
'menu_icon' => 'dashicons-editor-help',
'supports' => ['title', 'editor', 'excerpt', 'page-attributes'],
]);
}
public function register_taxonomy() {
register_taxonomy(self::TAXONOMY, [self::POST_TYPE], [
'labels' => [
'name' => 'FAQ-Kategorien',
'singular_name' => 'FAQ-Kategorie',
'search_items' => 'Kategorien durchsuchen',
'all_items' => 'Alle Kategorien',
'edit_item' => 'Kategorie bearbeiten',
'update_item' => 'Kategorie aktualisieren',
'add_new_item' => 'Neue Kategorie hinzufügen',
'new_item_name' => 'Neuer Kategoriename',
'menu_name' => 'Kategorien',
],
'public' => false,
'publicly_queryable' => false,
'hierarchical' => true,
'show_ui' => true,
'show_admin_column' => true,
'rewrite' => false,
'query_var' => false,
'show_in_rest' => true,
]);
}
public function enqueue_admin_assets($hook) {
global $post_type;
$allowed = ['post.php', 'post-new.php', 'edit.php', 'term.php', 'edit-tags.php'];
if (!in_array($hook, $allowed, true)) {
return;
}
if ($post_type !== self::POST_TYPE && (!isset($_GET['taxonomy']) || sanitize_key(wp_unslash($_GET['taxonomy'])) !== self::TAXONOMY)) {
return;
}
wp_enqueue_style(
'kgv-faq-admin',
plugin_dir_url(__FILE__) . 'assets/admin.css',
[],
self::VERSION
);
}
public function enqueue_front_assets() {
wp_enqueue_style(
'kgv-faq-front',
plugin_dir_url(__FILE__) . 'assets/front.css',
[],
self::VERSION
);
wp_enqueue_script(
'kgv-faq-front',
plugin_dir_url(__FILE__) . 'assets/front.js',
[],
self::VERSION,
true
);
}
public function admin_columns($columns) {
$new_columns = [];
foreach ($columns as $key => $label) {
if ($key === 'date') {
continue;
}
$new_columns[$key] = $label;
if ($key === 'title') {
$new_columns['kgv_faq_order'] = 'Reihenfolge';
$new_columns['kgv_faq_answer'] = 'Antwort';
}
}
$new_columns['date'] = 'Datum';
return $new_columns;
}
public function admin_column_content($column, $post_id) {
if ($column === 'kgv_faq_order') {
echo (int) get_post_field('menu_order', $post_id);
return;
}
if ($column === 'kgv_faq_answer') {
$content = get_post_field('post_content', $post_id);
$text = wp_strip_all_tags($content);
$text = mb_substr($text, 0, 120);
echo esc_html($text ? $text . '…' : '—');
}
}
public function sortable_columns($columns) {
$columns['kgv_faq_order'] = 'menu_order';
return $columns;
}
public function faq_shortcode($atts) {
$atts = shortcode_atts([
'category' => '',
'limit' => 100,
'show_filter' => '1',
'open_first' => '0',
'search' => '0',
], $atts, 'kgv_faq');
$selected_category = '';
if (!empty($_GET['faq_cat'])) {
$selected_category = sanitize_title(wp_unslash($_GET['faq_cat']));
} elseif (!empty($atts['category'])) {
$selected_category = sanitize_title($atts['category']);
}
$query_args = [
'post_type' => self::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => max(1, (int) $atts['limit']),
'orderby' => ['menu_order' => 'ASC', 'title' => 'ASC'],
'order' => 'ASC',
];
if ($selected_category) {
$query_args['tax_query'] = [[
'taxonomy' => self::TAXONOMY,
'field' => 'slug',
'terms' => $selected_category,
]];
}
$faq_query = new WP_Query($query_args);
$terms = get_terms([
'taxonomy' => self::TAXONOMY,
'hide_empty' => true,
]);
$uid = wp_unique_id('kgv-faq-');
$open_first = $atts['open_first'] === '1';
$show_search = $atts['search'] === '1';
ob_start();
echo '<div class="kgv-faq-wrapper" data-kgv-faq>';
if ($atts['show_filter'] === '1' && !is_wp_error($terms) && !empty($terms)) {
echo '<form class="kgv-faq-filter" method="get">';
foreach ($_GET as $key => $value) {
if ($key === 'faq_cat' || is_array($value)) {
continue;
}
echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr(wp_unslash($value)) . '">';
}
echo '<label for="' . esc_attr($uid . '-cat') . '"><strong>Kategorie:</strong></label> ';
echo '<select id="' . esc_attr($uid . '-cat') . '" name="faq_cat" onchange="this.form.submit()">';
echo '<option value="">Alle Fragen</option>';
foreach ($terms as $term) {
echo '<option value="' . esc_attr($term->slug) . '" ' . selected($selected_category, $term->slug, false) . '>';
echo esc_html($term->name);
echo '</option>';
}
echo '</select>';
echo '</form>';
}
if ($show_search) {
echo '<div class="kgv-faq-search-wrap">';
echo '<input type="search" class="kgv-faq-search" placeholder="Frage suchen …" aria-label="Frage suchen" data-kgv-faq-search>';
echo '</div>';
}
if ($faq_query->have_posts()) {
echo '<div class="kgv-faq-list">';
$index = 0;
while ($faq_query->have_posts()) {
$faq_query->the_post();
$faq_id = get_the_ID();
$button_id = $uid . '-button-' . $faq_id;
$panel_id = $uid . '-panel-' . $faq_id;
$is_open = $open_first && $index === 0;
echo '<article class="kgv-faq-item" data-kgv-faq-item>';
echo '<h3 class="kgv-faq-question">';
echo '<button type="button" class="kgv-faq-toggle' . ($is_open ? ' is-open' : '') . '" id="' . esc_attr($button_id) . '" aria-expanded="' . ($is_open ? 'true' : 'false') . '" aria-controls="' . esc_attr($panel_id) . '" data-kgv-faq-toggle>';
echo '<span>' . esc_html(get_the_title()) . '</span>';
echo '<span class="kgv-faq-icon" aria-hidden="true">+</span>';
echo '</button>';
echo '</h3>';
echo '<div class="kgv-faq-answer' . ($is_open ? ' is-open' : '') . '" id="' . esc_attr($panel_id) . '" role="region" aria-labelledby="' . esc_attr($button_id) . '"' . ($is_open ? '' : ' hidden') . '>';
echo '<div class="kgv-faq-answer__inner">';
echo wp_kses_post(wpautop(get_the_content()));
echo '</div>';
echo '</div>';
echo '</article>';
$index++;
}
echo '</div>';
wp_reset_postdata();
} else {
echo '<p>Keine Fragen gefunden.</p>';
}
echo '</div>';
return ob_get_clean();
}
}
new KGV_FAQ_Plugin();

28
readme.txt Executable file
View File

@@ -0,0 +1,28 @@
KGV FAQ WordPress Plugin
Beschreibung:
KGV FAQ ist ein WordPress-Plugin für einen strukturierten FAQ-Bereich im Backend und die Ausgabe im Frontend per Shortcode.
Installation:
1. ZIP in WordPress unter `Plugins > Installieren > Plugin hochladen` hochladen
2. Plugin aktivieren
3. Danach einmal `Einstellungen > Permalinks > Speichern` ausführen
Funktionen:
- eigener FAQ-Bereich im WordPress-Backend
- Fragen als Titel
- Antworten über den Editor
- FAQ-Kategorien
- Reihenfolge über Seitenattribute / Reihenfolge
- Ausgabe im Frontend per Shortcode
Shortcodes:
- `[kgv_faq]`
- `[kgv_faq category="allgemein"]`
- `[kgv_faq show_filter="0"]`
- `[kgv_faq open_first="1"]`
- `[kgv_faq search="1"]`
Empfohlene Nutzung:
- eine normale WordPress-Seite `FAQ` anlegen
- dort den Shortcode `[kgv_faq]` einfügen