'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'wp_kses_post', 'auth_callback' => 'skrift_ratgeber_meta_auth', ) ); register_post_meta( 'post', 'faq_ueberschrift', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'sanitize_text_field', 'auth_callback' => 'skrift_ratgeber_meta_auth', ) ); register_post_meta( 'post', 'cta_ziel', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'esc_url_raw', 'auth_callback' => 'skrift_ratgeber_meta_auth', ) ); register_post_meta( 'post', 'stand_datum', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'skrift_ratgeber_datum', 'auth_callback' => 'skrift_ratgeber_meta_auth', ) ); register_post_meta( 'post', 'faq', array( 'type' => 'array', 'single' => true, 'default' => array(), 'show_in_rest' => array( 'schema' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'frage' => array( 'type' => 'string' ), 'antwort' => array( 'type' => 'string' ), ), 'additionalProperties' => false, ), ), ), 'auth_callback' => 'skrift_ratgeber_meta_auth', ) ); } ); function skrift_ratgeber_datum( $wert ) { return preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $wert ) ? $wert : ''; } /* ------------------------------------------------------------------------ * 2. Eingabebox im Beitragseditor (Gutenberg und klassisch) * --------------------------------------------------------------------- */ add_action( 'add_meta_boxes_post', function () { add_meta_box( 'skrift_ratgeber', 'Ratgeber: Kurzantwort, CTA und FAQ', 'skrift_ratgeber_metabox', 'post', 'normal', 'high' ); } ); function skrift_ratgeber_metabox( $post ) { wp_nonce_field( 'skrift_ratgeber_speichern', 'skrift_ratgeber_nonce' ); $kurz = (string) get_post_meta( $post->ID, 'kurzantwort', true ); $cta = (string) get_post_meta( $post->ID, 'cta_ziel', true ); $stand = (string) get_post_meta( $post->ID, 'stand_datum', true ); $faq_u = (string) get_post_meta( $post->ID, 'faq_ueberschrift', true ); $faq = get_post_meta( $post->ID, 'faq', true ); if ( ! is_array( $faq ) ) { $faq = array(); } ?>

Zwei bis drei Saetze, Preise nur per [skrift_preis key="..."].

$e ) : ?>
Leere Fragen werden beim Speichern entfernt.
Frage entfernen
$frage, 'antwort' => wp_kses_post( $e['antwort'] ?? '' ), ); } update_post_meta( $post_id, 'faq', $faq ); } ); /* ------------------------------------------------------------------------ * 3. Ausgabe: gemeinsamer Renderer fuer Shortcode und Elementor-Widget * --------------------------------------------------------------------- */ function skrift_faq_eintraege( $post_id = 0 ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); $faq = get_post_meta( $post_id, 'faq', true ); if ( ! is_array( $faq ) ) { return array(); } $raus = array(); foreach ( $faq as $e ) { if ( ! empty( $e['frage'] ) ) { $raus[] = $e; } } return $raus; } function skrift_faq_basis_css() { static $fertig = false; if ( $fertig ) { return ''; } $fertig = true; return ''; } function skrift_faq_schema( $faq ) { $fragen = array(); foreach ( $faq as $e ) { $fragen[] = array( '@type' => 'Question', 'name' => wp_strip_all_tags( $e['frage'] ), 'acceptedAnswer' => array( '@type' => 'Answer', 'text' => wp_strip_all_tags( do_shortcode( $e['antwort'] ?? '' ) ), ), ); } return ''; } function skrift_faq_html( $faq, $args = array() ) { $args = wp_parse_args( $args, array( 'titel' => '', 'titel_tag' => 'h2', 'erste_offen' => false, 'schema' => true, ) ); if ( empty( $faq ) ) { return ''; } $tag = in_array( $args['titel_tag'], array( 'h2', 'h3', 'h4', 'div' ), true ) ? $args['titel_tag'] : 'h2'; $html = skrift_faq_basis_css() . '
'; if ( '' !== $args['titel'] ) { $html .= sprintf( '<%1$s class="skrift-faq__titel">%2$s', $tag, esc_html( $args['titel'] ) ); } foreach ( array_values( $faq ) as $i => $e ) { $offen = ( $args['erste_offen'] && 0 === $i ) ? ' open' : ''; $html .= '
'; $html .= '' . esc_html( $e['frage'] ) . ''; $html .= '
' . wp_kses_post( wpautop( do_shortcode( $e['antwort'] ?? '' ) ) ) . '
'; $html .= '
'; } $html .= '
'; if ( $args['schema'] ) { $html .= skrift_faq_schema( $faq ); } return $html; } function skrift_kurzantwort_html( $post_id = 0 ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); $text = (string) get_post_meta( $post_id, 'kurzantwort', true ); if ( '' === trim( $text ) ) { return ''; } return '
' . wp_kses_post( wpautop( do_shortcode( $text ) ) ) . '
'; } function skrift_lesezeit_minuten( $post_id = 0, $wpm = 200 ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); $post = get_post( $post_id ); if ( ! $post ) { return 1; } $text = do_shortcode( (string) $post->post_content ) . ' ' . (string) get_post_meta( $post_id, 'kurzantwort', true ); foreach ( skrift_faq_eintraege( $post_id ) as $e ) { $text .= ' ' . $e['frage'] . ' ' . ( $e['antwort'] ?? '' ); } $text = trim( wp_strip_all_tags( do_shortcode( $text ) ) ); $woerter = '' === $text ? 0 : count( preg_split( '/\s+/u', $text ) ); $wpm = max( 100, (int) $wpm ); return max( 1, (int) ceil( $woerter / $wpm ) ); } function skrift_faq_ueberschrift( $post_id = 0, $standard = 'Häufige Fragen' ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); if ( empty( skrift_faq_eintraege( $post_id ) ) ) { return ''; } $u = trim( (string) get_post_meta( $post_id, 'faq_ueberschrift', true ) ); return '' !== $u ? $u : (string) $standard; } function skrift_stand_formatiert( $post_id = 0, $format = 'd.m.Y' ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); $s = (string) get_post_meta( $post_id, 'stand_datum', true ); if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $s ) ) { $s = get_the_modified_date( 'Y-m-d', $post_id ); } return date_i18n( $format ? $format : 'd.m.Y', strtotime( $s . ' 12:00:00' ) ); } /* Shortcodes, z. B. fuer das Elementor-Shortcode-Widget oder klassische Themes */ add_shortcode( 'skrift_faq', function ( $atts ) { $atts = shortcode_atts( array( 'titel' => '', 'erste_offen' => '0', 'schema' => '1', 'post_id' => 0, ), $atts, 'skrift_faq' ); return skrift_faq_html( skrift_faq_eintraege( $atts['post_id'] ), array( 'titel' => $atts['titel'], 'erste_offen' => '1' === (string) $atts['erste_offen'], 'schema' => '1' === (string) $atts['schema'], ) ); } ); add_shortcode( 'skrift_kurzantwort', function ( $atts ) { $atts = shortcode_atts( array( 'post_id' => 0 ), $atts, 'skrift_kurzantwort' ); return skrift_kurzantwort_html( $atts['post_id'] ); } ); /* ------------------------------------------------------------------------ * 4. Elementor-Widgets: Skrift FAQ und Skrift Kurzantwort * Einmal ins Single-Post-Template (Theme Builder) setzen, dann zeigt * jeder Beitrag automatisch seine eigenen Fragen. * --------------------------------------------------------------------- */ function skrift_elementor_bearbeitungsmodus() { if ( ! class_exists( '\Elementor\Plugin' ) ) { return false; } $e = \Elementor\Plugin::$instance; if ( isset( $e->editor ) && $e->editor->is_edit_mode() ) { return true; } return isset( $e->preview ) && $e->preview->is_preview_mode(); } add_action( 'elementor/widgets/register', function ( $widgets_manager ) { if ( ! class_exists( '\Elementor\Widget_Base' ) ) { return; } if ( ! class_exists( 'Skrift_FAQ_Widget' ) ) { class Skrift_FAQ_Widget extends \Elementor\Widget_Base { public function get_name() { return 'skrift_faq'; } public function get_title() { return 'Skrift FAQ'; } public function get_icon() { return 'eicon-accordion'; } public function get_categories() { return array( 'general' ); } public function get_keywords() { return array( 'faq', 'fragen', 'skrift', 'akkordeon', 'umschalter', 'toggle' ); } protected function register_controls() { $C = '\Elementor\Controls_Manager'; $this->start_controls_section( 'inhalt', array( 'label' => 'Inhalt' ) ); $this->add_control( 'hinweis', array( 'type' => $C::RAW_HTML, 'raw' => 'Fragen und Antworten kommen aus dem Meta-Feld faq des jeweiligen Beitrags.', ) ); $this->add_control( 'erste_offen', array( 'label' => 'Erste Frage geoeffnet', 'type' => $C::SWITCHER, 'default' => '', ) ); $this->add_control( 'schema', array( 'label' => 'FAQ-Schema ausgeben', 'type' => $C::SWITCHER, 'default' => 'yes', ) ); $this->end_controls_section(); $this->start_controls_section( 'stil_frage', array( 'label' => 'Frage', 'tab' => $C::TAB_STYLE ) ); $this->add_group_control( \Elementor\Group_Control_Typography::get_type(), array( 'name' => 'frage_typo', 'selector' => '{{WRAPPER}} .skrift-faq__frage', ) ); $this->add_control( 'frage_farbe', array( 'label' => 'Farbe', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-faq__frage' => 'color: {{VALUE}};' ), ) ); $this->add_control( 'frage_farbe_offen', array( 'label' => 'Farbe geoeffnet', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-faq__eintrag[open] .skrift-faq__frage' => 'color: {{VALUE}};' ), ) ); $this->add_control( 'frage_hintergrund', array( 'label' => 'Hintergrund', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-faq__frage' => 'background-color: {{VALUE}};' ), ) ); $this->add_responsive_control( 'frage_innenabstand', array( 'label' => 'Innenabstand', 'type' => $C::DIMENSIONS, 'size_units' => array( 'px', 'em' ), 'selectors' => array( '{{WRAPPER}} .skrift-faq__frage' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ), ) ); $this->end_controls_section(); $this->start_controls_section( 'stil_antwort', array( 'label' => 'Antwort', 'tab' => $C::TAB_STYLE ) ); $this->add_group_control( \Elementor\Group_Control_Typography::get_type(), array( 'name' => 'antwort_typo', 'selector' => '{{WRAPPER}} .skrift-faq__antwort', ) ); $this->add_control( 'antwort_farbe', array( 'label' => 'Farbe', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-faq__antwort' => 'color: {{VALUE}};' ), ) ); $this->add_responsive_control( 'antwort_innenabstand', array( 'label' => 'Innenabstand', 'type' => $C::DIMENSIONS, 'size_units' => array( 'px', 'em' ), 'selectors' => array( '{{WRAPPER}} .skrift-faq__antwort' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ), ) ); $this->end_controls_section(); $this->start_controls_section( 'stil_eintrag', array( 'label' => 'Eintrag', 'tab' => $C::TAB_STYLE ) ); $this->add_group_control( \Elementor\Group_Control_Border::get_type(), array( 'name' => 'eintrag_rahmen', 'selector' => '{{WRAPPER}} .skrift-faq__eintrag', ) ); $this->add_control( 'eintrag_radius', array( 'label' => 'Eckenradius', 'type' => $C::DIMENSIONS, 'size_units' => array( 'px' ), 'selectors' => array( '{{WRAPPER}} .skrift-faq__eintrag' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}; overflow: hidden;' ), ) ); $this->add_control( 'eintrag_hintergrund', array( 'label' => 'Hintergrund', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-faq__eintrag' => 'background-color: {{VALUE}};' ), ) ); $this->add_responsive_control( 'eintrag_abstand', array( 'label' => 'Abstand zwischen Eintraegen', 'type' => $C::SLIDER, 'size_units' => array( 'px', 'em' ), 'selectors' => array( '{{WRAPPER}} .skrift-faq__eintrag + .skrift-faq__eintrag' => 'margin-top: {{SIZE}}{{UNIT}};' ), ) ); $this->end_controls_section(); } protected function render() { $s = $this->get_settings_for_display(); $faq = skrift_faq_eintraege(); $bearbeiten = skrift_elementor_bearbeitungsmodus(); // Im Theme-Builder-Template gibt es keinen Beitrag mit FAQ, // deshalb dort Platzhalter zum Gestalten. if ( empty( $faq ) && $bearbeiten ) { $faq = array( array( 'frage' => 'Beispielfrage aus dem Feld faq', 'antwort' => 'Im Beitrag erscheinen hier die Antworten aus dem Meta-Feld faq.' ), array( 'frage' => 'Zweite Beispielfrage', 'antwort' => 'Die Gestaltung legen Sie hier im Widget fest, sie gilt fuer alle Beitraege.' ), array( 'frage' => 'Dritte Beispielfrage', 'antwort' => 'Beitraege ohne FAQ zeigen an dieser Stelle nichts an.' ), ); } echo skrift_faq_html( $faq, array( // phpcs:ignore WordPress.Security.EscapeOutput 'erste_offen' => 'yes' === ( $s['erste_offen'] ?? '' ), 'schema' => 'yes' === ( $s['schema'] ?? '' ) && ! $bearbeiten, ) ); } } } if ( ! class_exists( 'Skrift_Kurzantwort_Widget' ) ) { class Skrift_Kurzantwort_Widget extends \Elementor\Widget_Base { public function get_name() { return 'skrift_kurzantwort'; } public function get_title() { return 'Skrift Kurzantwort'; } public function get_icon() { return 'eicon-text'; } public function get_categories() { return array( 'general' ); } public function get_keywords() { return array( 'kurzantwort', 'skrift', 'zusammenfassung', 'box' ); } protected function register_controls() { $C = '\Elementor\Controls_Manager'; $this->start_controls_section( 'inhalt', array( 'label' => 'Inhalt' ) ); $this->add_control( 'hinweis', array( 'type' => $C::RAW_HTML, 'raw' => 'Der Text kommt aus dem Meta-Feld kurzantwort. Preis-Shortcodes darin werden aufgeloest.', ) ); $this->end_controls_section(); $this->start_controls_section( 'stil_box', array( 'label' => 'Box', 'tab' => $C::TAB_STYLE ) ); $this->add_group_control( \Elementor\Group_Control_Typography::get_type(), array( 'name' => 'box_typo', 'selector' => '{{WRAPPER}} .skrift-kurzantwort', ) ); $this->add_control( 'box_farbe', array( 'label' => 'Textfarbe', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-kurzantwort' => 'color: {{VALUE}};' ), ) ); $this->add_control( 'box_hintergrund', array( 'label' => 'Hintergrund', 'type' => $C::COLOR, 'selectors' => array( '{{WRAPPER}} .skrift-kurzantwort' => 'background-color: {{VALUE}};' ), ) ); $this->add_group_control( \Elementor\Group_Control_Border::get_type(), array( 'name' => 'box_rahmen', 'selector' => '{{WRAPPER}} .skrift-kurzantwort', ) ); $this->add_control( 'box_radius', array( 'label' => 'Eckenradius', 'type' => $C::DIMENSIONS, 'size_units' => array( 'px' ), 'selectors' => array( '{{WRAPPER}} .skrift-kurzantwort' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ), ) ); $this->add_responsive_control( 'box_innenabstand', array( 'label' => 'Innenabstand', 'type' => $C::DIMENSIONS, 'size_units' => array( 'px', 'em' ), 'selectors' => array( '{{WRAPPER}} .skrift-kurzantwort' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ), ) ); $this->end_controls_section(); } protected function render() { $html = skrift_kurzantwort_html(); if ( '' === $html && skrift_elementor_bearbeitungsmodus() ) { $html = '

Platzhalter: Hier erscheint die Kurzantwort des jeweiligen Beitrags.

'; } echo $html; // phpcs:ignore WordPress.Security.EscapeOutput } } } $widgets_manager->register( new Skrift_FAQ_Widget() ); $widgets_manager->register( new Skrift_Kurzantwort_Widget() ); } ); /* ------------------------------------------------------------------------ * 5. Elementor Dynamic Tags (Gruppe "Skrift") * In jedem Text- oder Ueberschrift-Widget ueber das Symbol fuer dynamische * Inhalte waehlbar. "Davor" und "Danach" stellt Elementor selbst bereit. * --------------------------------------------------------------------- */ add_action( 'elementor/dynamic_tags/register', function ( $dynamic_tags ) { if ( ! class_exists( '\Elementor\Core\DynamicTags\Tag' ) || ! method_exists( $dynamic_tags, 'register' ) ) { return; } $dynamic_tags->register_group( 'skrift', array( 'title' => 'Skrift' ) ); if ( ! class_exists( 'Skrift_Tag_Lesezeit' ) ) { class Skrift_Tag_Lesezeit extends \Elementor\Core\DynamicTags\Tag { public function get_name() { return 'skrift-lesezeit'; } public function get_title() { return 'Lesezeit'; } public function get_group() { return 'skrift'; } public function get_categories() { return array( \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY ); } protected function register_controls() { $this->add_control( 'wpm', array( 'label' => 'Woerter pro Minute', 'type' => \Elementor\Controls_Manager::NUMBER, 'default' => 200, 'min' => 100, 'max' => 400, ) ); $this->add_control( 'einheit', array( 'label' => 'Einheit', 'type' => \Elementor\Controls_Manager::TEXT, 'default' => ' Min.', ) ); } public function render() { $wpm = (int) $this->get_settings( 'wpm' ); $einheit = (string) $this->get_settings( 'einheit' ); echo esc_html( skrift_lesezeit_minuten( get_the_ID(), $wpm ? $wpm : 200 ) . $einheit ); } } } if ( ! class_exists( 'Skrift_Tag_FAQ_Ueberschrift' ) ) { class Skrift_Tag_FAQ_Ueberschrift extends \Elementor\Core\DynamicTags\Tag { public function get_name() { return 'skrift-faq-ueberschrift'; } public function get_title() { return 'FAQ-Ueberschrift'; } public function get_group() { return 'skrift'; } public function get_categories() { return array( \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY ); } protected function register_controls() { $this->add_control( 'standard', array( 'label' => 'Standardtext', 'type' => \Elementor\Controls_Manager::TEXT, 'default' => 'Häufige Fragen', 'description' => 'Gilt, wenn im Beitrag keine eigene FAQ-Ueberschrift gepflegt ist. Hat der Beitrag kein FAQ, bleibt die Ausgabe leer.', ) ); } public function render() { echo esc_html( skrift_faq_ueberschrift( get_the_ID(), (string) $this->get_settings( 'standard' ) ) ); } } } if ( ! class_exists( 'Skrift_Tag_Stand' ) ) { class Skrift_Tag_Stand extends \Elementor\Core\DynamicTags\Tag { public function get_name() { return 'skrift-stand'; } public function get_title() { return 'Stand-Datum'; } public function get_group() { return 'skrift'; } public function get_categories() { return array( \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY ); } protected function register_controls() { $this->add_control( 'format', array( 'label' => 'Datumsformat', 'type' => \Elementor\Controls_Manager::TEXT, 'default' => 'd.m.Y', 'description' => 'PHP-Datumsformat, z. B. d.m.Y oder j. F Y', ) ); } public function render() { echo esc_html( skrift_stand_formatiert( get_the_ID(), (string) $this->get_settings( 'format' ) ) ); } } } $dynamic_tags->register( new Skrift_Tag_Lesezeit() ); $dynamic_tags->register( new Skrift_Tag_FAQ_Ueberschrift() ); $dynamic_tags->register( new Skrift_Tag_Stand() ); } );