Dateien nach „bavarian-rank-engine/includes/Admin“ hochladen

This commit is contained in:
Michael Fuchs 2026-02-22 14:39:55 +00:00
parent 52d7c4c39f
commit 24fb4f05ea
3 changed files with 329 additions and 0 deletions

View file

@ -75,6 +75,15 @@ class AdminMenu {
'bre-robots', 'bre-robots',
array( new RobotsPage(), 'render' ) array( new RobotsPage(), 'render' )
); );
add_submenu_page(
'bavarian-rank',
__( 'GEO Quick Overview', 'bavarian-rank-engine' ),
__( 'GEO Block', 'bavarian-rank-engine' ),
'manage_options',
'bre-geo',
array( new GeoPage(), 'render' )
);
} }
public function render_dashboard(): void { public function render_dashboard(): void {

View file

@ -0,0 +1,231 @@
<?php
namespace BavarianRankEngine\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use BavarianRankEngine\Features\GeoBlock;
class GeoEditorBox {
public function register(): void {
add_action( 'add_meta_boxes', array( $this, 'add_boxes' ) );
add_action( 'save_post', array( $this, 'save' ), 10, 2 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
add_action( 'wp_ajax_bre_geo_generate', array( $this, 'ajax_generate' ) );
add_action( 'wp_ajax_bre_geo_clear', array( $this, 'ajax_clear' ) );
}
public function add_boxes(): void {
$settings = GeoBlock::getSettings();
foreach ( $settings['post_types'] as $pt ) {
add_meta_box(
'bre_geo_box',
__( 'GEO Quick Overview (BRE)', 'bavarian-rank-engine' ),
array( $this, 'render' ),
$pt,
'normal',
'default'
);
}
}
public function render( \WP_Post $post ): void {
$settings = GeoBlock::getSettings();
$meta = GeoBlock::getMeta( $post->ID );
$enabled = get_post_meta( $post->ID, GeoBlock::META_ENABLED, true );
$lock = (bool) get_post_meta( $post->ID, GeoBlock::META_LOCK, true );
$generated_at = get_post_meta( $post->ID, GeoBlock::META_GENERATED, true );
$prompt_addon = get_post_meta( $post->ID, GeoBlock::META_ADDON, true ) ?: '';
$global = SettingsPage::getSettings();
$has_api_key = ! empty( $global['api_keys'][ $global['provider'] ] ?? '' );
wp_nonce_field( 'bre_geo_save_' . $post->ID, 'bre_geo_nonce' );
?>
<div id="bre-geo-box" data-post-id="<?php echo esc_attr( $post->ID ); ?>"
data-nonce="<?php echo esc_attr( wp_create_nonce( 'bre_admin' ) ); ?>">
<p style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;">
<label>
<input type="checkbox" name="bre_geo_enabled" value="1"
<?php checked( $enabled, '1' ); ?>>
<?php esc_html_e( 'Enable GEO block for this post', 'bavarian-rank-engine' ); ?>
</label>
<label>
<input type="checkbox" name="bre_geo_lock" value="1" id="bre-geo-lock"
<?php checked( $lock, true ); ?>>
<?php esc_html_e( 'Lock auto-regeneration', 'bavarian-rank-engine' ); ?>
</label>
<?php if ( $generated_at ) : ?>
<span style="font-size:11px;color:#666;">
<?php
// translators: %s = human-readable date
printf( esc_html__( 'Generated: %s', 'bavarian-rank-engine' ), esc_html( date_i18n( get_option( 'date_format' ) . ' H:i', (int) $generated_at ) ) );
?>
</span>
<?php endif; ?>
</p>
<?php if ( $has_api_key ) : ?>
<p>
<button type="button" class="button" id="bre-geo-generate">
<?php
empty( $meta['summary'] )
? esc_html_e( 'Generate now', 'bavarian-rank-engine' )
: esc_html_e( 'Regenerate', 'bavarian-rank-engine' );
?>
</button>
<?php if ( ! empty( $meta['summary'] ) ) : ?>
<button type="button" class="button" id="bre-geo-clear" style="margin-left:6px;">
<?php esc_html_e( 'Clear', 'bavarian-rank-engine' ); ?>
</button>
<?php endif; ?>
<span id="bre-geo-status" style="margin-left:10px;font-size:12px;"></span>
</p>
<?php endif; ?>
<p style="margin-bottom:4px;">
<label for="bre-geo-summary"><strong><?php esc_html_e( 'Summary', 'bavarian-rank-engine' ); ?></strong></label>
</p>
<textarea id="bre-geo-summary" name="bre_geo_summary" rows="3"
style="width:100%;box-sizing:border-box;"><?php echo esc_textarea( $meta['summary'] ); ?></textarea>
<p style="margin-bottom:4px;margin-top:10px;">
<label for="bre-geo-bullets"><strong><?php esc_html_e( 'Key Points', 'bavarian-rank-engine' ); ?></strong></label>
<span style="font-size:11px;color:#666;margin-left:8px;"><?php esc_html_e( '(one per line)', 'bavarian-rank-engine' ); ?></span>
</p>
<textarea id="bre-geo-bullets" name="bre_geo_bullets" rows="5"
style="width:100%;box-sizing:border-box;"><?php echo esc_textarea( implode( "\n", $meta['bullets'] ) ); ?></textarea>
<p style="margin-bottom:4px;margin-top:10px;">
<label for="bre-geo-faq"><strong><?php esc_html_e( 'FAQ', 'bavarian-rank-engine' ); ?></strong></label>
<span style="font-size:11px;color:#666;margin-left:8px;"><?php esc_html_e( '(Format: Question? | Answer — one per line)', 'bavarian-rank-engine' ); ?></span>
</p>
<textarea id="bre-geo-faq" name="bre_geo_faq" rows="4"
style="width:100%;box-sizing:border-box;">
<?php
$faq_lines = array_map(
function ( $item ) {
return ( $item['q'] ?? '' ) . ' | ' . ( $item['a'] ?? '' );
},
$meta['faq']
);
echo esc_textarea( implode( "\n", $faq_lines ) );
?>
</textarea>
<?php if ( $settings['allow_prompt_addon'] ) : ?>
<p style="margin-bottom:4px;margin-top:10px;">
<label for="bre-geo-addon"><strong><?php esc_html_e( 'Prompt add-on (optional)', 'bavarian-rank-engine' ); ?></strong></label>
</p>
<textarea id="bre-geo-addon" name="bre_geo_prompt_addon" rows="2"
style="width:100%;box-sizing:border-box;"><?php echo esc_textarea( $prompt_addon ); ?></textarea>
<?php endif; ?>
</div>
<?php
}
public function save( int $post_id, \WP_Post $post ): void {
if ( ! isset( $_POST['bre_geo_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['bre_geo_nonce'] ) ), 'bre_geo_save_' . $post_id ) ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
// Per-post enabled flag ('' = follow global, '1' = on, '0' = off)
$enabled = isset( $_POST['bre_geo_enabled'] ) ? '1' : '0';
update_post_meta( $post_id, GeoBlock::META_ENABLED, $enabled );
$lock = isset( $_POST['bre_geo_lock'] ) ? '1' : '';
update_post_meta( $post_id, GeoBlock::META_LOCK, $lock );
// Manual field edits
$summary = sanitize_text_field( wp_unslash( $_POST['bre_geo_summary'] ?? '' ) );
update_post_meta( $post_id, GeoBlock::META_SUMMARY, $summary );
$raw_bullets = sanitize_textarea_field( wp_unslash( $_POST['bre_geo_bullets'] ?? '' ) );
$bullets = array_values( array_filter( array_map( 'trim', explode( "\n", $raw_bullets ) ) ) );
update_post_meta( $post_id, GeoBlock::META_BULLETS, wp_json_encode( $bullets, JSON_UNESCAPED_UNICODE ) );
$raw_faq = sanitize_textarea_field( wp_unslash( $_POST['bre_geo_faq'] ?? '' ) );
$faq = array();
foreach ( array_filter( array_map( 'trim', explode( "\n", $raw_faq ) ) ) as $line ) {
$parts = explode( '|', $line, 2 );
if ( count( $parts ) === 2 ) {
$faq[] = array(
'q' => trim( $parts[0] ),
'a' => trim( $parts[1] ),
);
}
}
update_post_meta( $post_id, GeoBlock::META_FAQ, wp_json_encode( $faq, JSON_UNESCAPED_UNICODE ) );
if ( isset( $_POST['bre_geo_prompt_addon'] ) ) {
update_post_meta( $post_id, GeoBlock::META_ADDON, sanitize_textarea_field( wp_unslash( $_POST['bre_geo_prompt_addon'] ) ) );
}
}
public function enqueue( string $hook ): void {
if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
return;
}
wp_enqueue_script(
'bre-geo-editor',
BRE_URL . 'assets/geo-editor.js',
array( 'jquery' ),
BRE_VERSION,
true
);
}
public function ajax_generate(): void {
check_ajax_referer( 'bre_admin', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( __( 'Insufficient permissions.', 'bavarian-rank-engine' ) );
}
$post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) );
if ( ! $post_id || ! get_post( $post_id ) ) {
wp_send_json_error( __( 'Post not found.', 'bavarian-rank-engine' ) );
}
$geo = new GeoBlock();
if ( $geo->generate( $post_id, true ) ) {
$meta = GeoBlock::getMeta( $post_id );
wp_send_json_success(
array(
'summary' => $meta['summary'],
'bullets' => $meta['bullets'],
'faq' => $meta['faq'],
)
);
} else {
wp_send_json_error( __( 'Generation failed. Check API key and provider settings.', 'bavarian-rank-engine' ) );
}
}
public function ajax_clear(): void {
check_ajax_referer( 'bre_admin', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( __( 'Insufficient permissions.', 'bavarian-rank-engine' ) );
}
$post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) );
if ( ! $post_id ) {
wp_send_json_error( 'Invalid post ID' );
}
delete_post_meta( $post_id, GeoBlock::META_SUMMARY );
delete_post_meta( $post_id, GeoBlock::META_BULLETS );
delete_post_meta( $post_id, GeoBlock::META_FAQ );
delete_post_meta( $post_id, GeoBlock::META_GENERATED );
wp_send_json_success();
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace BavarianRankEngine\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use BavarianRankEngine\Features\GeoBlock;
class GeoPage {
public function register(): void {
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
public function register_settings(): void {
register_setting(
'bre_geo',
GeoBlock::OPTION_KEY,
array( 'sanitize_callback' => array( $this, 'sanitize' ) )
);
}
public function enqueue_assets( string $hook ): void {
if ( $hook !== 'bavarian-rank_page_bre-geo' ) {
return;
}
wp_enqueue_style( 'bre-admin', BRE_URL . 'assets/admin.css', array(), BRE_VERSION );
}
public function sanitize( mixed $input ): array {
$input = is_array( $input ) ? $input : array();
$clean = array();
$clean['enabled'] = ! empty( $input['enabled'] );
$clean['regen_on_update'] = ! empty( $input['regen_on_update'] );
$clean['minimal_css'] = ! empty( $input['minimal_css'] );
$clean['allow_prompt_addon'] = ! empty( $input['allow_prompt_addon'] );
$allowed_modes = array( 'auto_on_publish', 'manual_only', 'hybrid' );
$clean['mode'] = in_array( $input['mode'] ?? '', $allowed_modes, true )
? $input['mode'] : 'auto_on_publish';
$allowed_positions = array( 'after_first_p', 'top', 'bottom' );
$clean['position'] = in_array( $input['position'] ?? '', $allowed_positions, true )
? $input['position'] : 'after_first_p';
$allowed_styles = array( 'details_collapsible', 'open_always', 'store_only_no_frontend' );
$clean['output_style'] = in_array( $input['output_style'] ?? '', $allowed_styles, true )
? $input['output_style'] : 'details_collapsible';
$allowed_schemes = array( 'auto', 'light', 'dark' );
$clean['color_scheme'] = in_array( $input['color_scheme'] ?? 'auto', $allowed_schemes, true )
? $input['color_scheme'] : 'auto';
$clean['accent_color'] = sanitize_hex_color( $input['accent_color'] ?? '' ) ?? '';
$clean['title'] = sanitize_text_field( $input['title'] ?? 'Quick Overview' );
$clean['label_summary'] = sanitize_text_field( $input['label_summary'] ?? 'Summary' );
$clean['label_bullets'] = sanitize_text_field( $input['label_bullets'] ?? 'Key Points' );
$clean['label_faq'] = sanitize_text_field( $input['label_faq'] ?? 'FAQ' );
$clean['custom_css'] = sanitize_textarea_field( $input['custom_css'] ?? '' );
$clean['prompt_default'] = sanitize_textarea_field(
! empty( $input['prompt_default'] ) ? $input['prompt_default'] : GeoBlock::getDefaultPrompt()
);
$clean['word_threshold'] = max( 50, (int) ( $input['word_threshold'] ?? 350 ) );
$all_post_types = array_keys( get_post_types( array( 'public' => true ) ) );
$clean['post_types'] = array_values(
array_intersect(
array_map( 'sanitize_key', (array) ( $input['post_types'] ?? array() ) ),
$all_post_types
)
);
if ( empty( $clean['post_types'] ) ) {
$clean['post_types'] = array( 'post', 'page' );
}
return $clean;
}
public function render(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$settings = GeoBlock::getSettings();
$post_types = get_post_types( array( 'public' => true ), 'objects' );
include BRE_DIR . 'includes/Admin/views/geo.php';
}
}