fm = new FiveNamesLlmTxt_File_Manager(); $this->gen = new FiveNamesLlmTxt_Generator(); $this->history = new FiveNamesLlmTxt_History(); $this->importer = new FiveNamesLlmTxt_Importer(); add_action( 'admin_menu', [ $this, 'register_menu' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'admin_notices', [ $this, 'show_notices' ] ); add_action( 'wp_ajax_fivenames_llmtxt_save', [ $this, 'ajax_save' ] ); add_action( 'wp_ajax_fivenames_llmtxt_preview', [ $this, 'ajax_preview' ] ); add_action( 'wp_ajax_fivenames_llmtxt_restore', [ $this, 'ajax_restore' ] ); add_action( 'wp_ajax_fivenames_llmtxt_import', [ $this, 'ajax_import' ] ); add_action( 'wp_ajax_fivenames_llmtxt_save_raw', [ $this, 'ajax_save_raw' ] ); add_action( 'wp_ajax_fivenames_llmtxt_dismiss_notice', [ $this, 'ajax_dismiss_notice' ] ); } public function register_menu() { add_options_page( __( 'LLMs.txt Settings', '5names-llms-txt-manager' ), __( 'LLMs.txt', '5names-llms-txt-manager' ), 'manage_options', '5names-llms-txt-manager', [ $this, 'render_page' ] ); } public function enqueue_assets( $hook ) { if ( 'settings_page_5names-llms-txt-manager' !== $hook ) return; wp_enqueue_style( 'fivenames-llmtxt-admin', FIVENAMES_LLMTXT_URL . 'admin/llms-admin.css', [], FIVENAMES_LLMTXT_VERSION ); wp_enqueue_script( 'fivenames-llmtxt-admin', FIVENAMES_LLMTXT_URL . 'admin/llms-admin.js', [ 'jquery' ], FIVENAMES_LLMTXT_VERSION, true ); $settings = get_option( 'fivenames_llmtxt_settings', [] ); wp_localize_script( 'fivenames-llmtxt-admin', 'fivenamesLlmtxtData', [ 'nonce' => wp_create_nonce( 'fivenames_llmtxt_nonce' ), 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'sections' => ! empty( $settings['sections'] ) ? $settings['sections'] : [], 'i18n' => [ 'confirmDeleteSection' => __( 'Delete this section and all its links?', '5names-llms-txt-manager' ), 'confirmRestore' => __( 'Overwrite the current llms.txt with this backup? This cannot be undone.', '5names-llms-txt-manager' ), 'saveRawLabel' => __( 'Save source to file', '5names-llms-txt-manager' ), 'saveVisualLabel' => __( 'Save & write to file', '5names-llms-txt-manager' ), 'connectionError' => __( 'Connection error — please try again.', '5names-llms-txt-manager' ), 'importing' => __( 'Importing…', '5names-llms-txt-manager' ), 'importButton' => __( 'Import existing file', '5names-llms-txt-manager' ), 'unknownError' => __( 'Unknown error', '5names-llms-txt-manager' ), 'errorPrefix' => __( 'Error: ', '5names-llms-txt-manager' ), 'restoring' => __( 'Restoring…', '5names-llms-txt-manager' ), 'restoreButton' => __( 'Restore', '5names-llms-txt-manager' ), 'labelPlaceholder' => __( 'Label', '5names-llms-txt-manager' ), 'urlPlaceholder' => __( 'https://...', '5names-llms-txt-manager' ), 'descPlaceholder' => __( 'Short description (optional)', '5names-llms-txt-manager' ), 'removeLink' => __( 'Remove link', '5names-llms-txt-manager' ), 'moveUp' => __( 'Move up', '5names-llms-txt-manager' ), 'moveDown' => __( 'Move down', '5names-llms-txt-manager' ), 'sectionNamePlaceholder' => __( 'Section name (e.g. Competitions)', '5names-llms-txt-manager' ), 'removeSection' => __( 'Remove section', '5names-llms-txt-manager' ), 'addLink' => __( 'Add link', '5names-llms-txt-manager' ), ], ] ); } public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Access denied.', '5names-llms-txt-manager' ) ); } $settings = get_option( 'fivenames_llmtxt_settings', [] ); $cpts = $this->gen->get_custom_post_types(); $backups = $this->history->get_backups(); $preview = $this->gen->generate( $settings ); $file_manager = $this->fm; $generator = $this->gen; $raw_content = $this->fm->read(); $has_import_notice = (bool) get_transient( 'fivenames_llmtxt_existing_file_notice' ); $sitemap_provider = $this->gen->detect_sitemap_provider(); $sitemap_url = $this->gen->get_sitemap_url(); include FIVENAMES_LLMTXT_PATH . 'admin/settings-page.php'; } // ── AJAX: opslaan (visueel) ─────────────────────────────────── public function ajax_save() { check_ajax_referer( 'fivenames_llmtxt_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); if ( ! $this->fm->is_writable() ) { wp_send_json_error( [ 'message' => sprintf( /* translators: %s: absolute path to the llms.txt file */ __( 'No write permission for %s.', '5names-llms-txt-manager' ), esc_html( FIVENAMES_LLMTXT_FILE ) ) ] ); } $settings = $this->sanitize_settings( $_POST ); update_option( 'fivenames_llmtxt_settings', $settings ); if ( $this->fm->exists() ) { $this->history->backup( $this->fm->read() ); } $content = $this->gen->generate( $settings ); $written = $this->fm->write( $content ); if ( $written ) { $this->maybe_fire_webhook( $settings ); wp_send_json_success( [ 'message' => sprintf( /* translators: %s: date and time of the save, in UTC */ __( 'Saved & written on %s UTC.', '5names-llms-txt-manager' ), gmdate( 'd-m-Y H:i:s' ) ), 'content' => $content, 'size' => size_format( strlen( $content ) ), ] ); } else { wp_send_json_error( [ 'message' => __( 'Save failed — check the file permissions.', '5names-llms-txt-manager' ) ] ); } } // ── AJAX: opslaan (broncode) ────────────────────────────────── public function ajax_save_raw() { check_ajax_referer( 'fivenames_llmtxt_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); if ( ! $this->fm->is_writable() ) { wp_send_json_error( [ 'message' => sprintf( /* translators: %s: absolute path to the llms.txt file */ __( 'No write permission for %s.', '5names-llms-txt-manager' ), esc_html( FIVENAMES_LLMTXT_FILE ) ) ] ); } $content = isset( $_POST['content'] ) ? wp_strip_all_tags( wp_unslash( $_POST['content'] ) ) : ''; $content = str_replace( [ "\0", "\r\n", "\r" ], [ '', "\n", "\n" ], $content ); if ( $this->fm->exists() ) { $this->history->backup( $this->fm->read() ); } $written = $this->fm->write( $content ); if ( $written ) { $settings = get_option( 'fivenames_llmtxt_settings', [] ); $this->maybe_fire_webhook( $settings ); wp_send_json_success( [ 'message' => sprintf( /* translators: %s: date and time of the save, in UTC */ __( 'Source saved on %s UTC.', '5names-llms-txt-manager' ), gmdate( 'd-m-Y H:i:s' ) ), 'size' => size_format( strlen( $content ) ), ] ); } else { wp_send_json_error( [ 'message' => __( 'Save failed — check the file permissions.', '5names-llms-txt-manager' ) ] ); } } // ── AJAX: voorbeeld ─────────────────────────────────────────── public function ajax_preview() { check_ajax_referer( 'fivenames_llmtxt_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); $settings = $this->sanitize_settings( $_POST ); wp_send_json_success( [ 'content' => $this->gen->generate( $settings ) ] ); } // ── AJAX: herstellen ────────────────────────────────────────── public function ajax_restore() { check_ajax_referer( 'fivenames_llmtxt_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); $index = isset( $_POST['index'] ) ? intval( $_POST['index'] ) : 0; $result = $this->history->restore( $index ); if ( $result ) { wp_send_json_success( [ 'message' => __( 'Backup restored as the active llms.txt.', '5names-llms-txt-manager' ) ] ); } else { wp_send_json_error( [ 'message' => __( 'Restore failed — backup not found.', '5names-llms-txt-manager' ) ] ); } } // ── AJAX: importeer bestaand bestand ────────────────────────── public function ajax_import() { check_ajax_referer( 'fivenames_llmtxt_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); if ( ! $this->fm->exists() ) { wp_send_json_error( [ 'message' => __( 'No existing llms.txt file found.', '5names-llms-txt-manager' ) ] ); } $content = $this->fm->read(); $parsed = $this->importer->parse( $content ); $summary = $this->importer->summarize( $parsed ); // Bestaande webhook-instelling bewaren (staat niet in het bestand). $existing = get_option( 'fivenames_llmtxt_settings', [] ); if ( ! empty( $existing['webhook_url'] ) && empty( $parsed['webhook_url'] ) ) { $parsed['webhook_url'] = $existing['webhook_url']; } update_option( 'fivenames_llmtxt_settings', $parsed ); delete_transient( 'fivenames_llmtxt_existing_file_notice' ); wp_send_json_success( [ 'message' => sprintf( /* translators: %s: summary of what was imported, e.g. "3 sections, 5 links imported." */ __( 'Import successful! %s Reloading page…', '5names-llms-txt-manager' ), $summary ), ] ); } // ── AJAX: verberg activeringsmelding ────────────────────────── public function ajax_dismiss_notice() { check_ajax_referer( 'fivenames_llmtxt_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); delete_transient( 'fivenames_llmtxt_existing_file_notice' ); wp_send_json_success(); } // ── Notices ─────────────────────────────────────────────────── public function show_notices() { if ( ! current_user_can( 'manage_options' ) ) return; // Bestaand-bestand-melding (op alle admin-pagina's). if ( get_transient( 'fivenames_llmtxt_existing_file_notice' ) ) { $settings_url = admin_url( 'options-general.php?page=5names-llms-txt-manager' ); echo '
'; echo '

' . esc_html__( '5Names LLMs.txt Manager:', '5names-llms-txt-manager' ) . ' '; printf( /* translators: %s: link to the plugin settings page */ wp_kses( __( 'An /llms.txt file already exists. %s and click Import existing file to load the current content as a starting point.', '5names-llms-txt-manager' ), [ 'code' => [], 'strong' => [] ] ), '' . esc_html__( 'Go to Settings → LLMs.txt', '5names-llms-txt-manager' ) . '' ); echo '

'; echo ''; echo '
'; } // Schrijfrechten-melding (alleen op de instellingenpagina). $screen = get_current_screen(); if ( $screen && 'settings_page_5names-llms-txt-manager' === $screen->id && ! $this->fm->is_writable() ) { echo '

'; echo '' . esc_html__( '5Names LLMs.txt Manager:', '5names-llms-txt-manager' ) . ' '; printf( /* translators: %s: absolute path to the WordPress root directory */ wp_kses( __( 'The directory %s is not writable. Fix the file permissions (chmod 755).', '5names-llms-txt-manager' ), [ 'strong' => [], 'code' => [] ] ), '' . esc_html( ABSPATH ) . '' ); echo '

'; } } // ── Privé ───────────────────────────────────────────────────── private function maybe_fire_webhook( array $settings ) { $url = ! empty( $settings['webhook_url'] ) ? $settings['webhook_url'] : ''; if ( ! $url || ! filter_var( $url, FILTER_VALIDATE_URL ) ) return; wp_remote_get( $url, [ 'timeout' => 5, 'blocking' => false, 'user-agent' => 'LLMsTxt-Manager/' . FIVENAMES_LLMTXT_VERSION . ' (WordPress)', ] ); } private function sanitize_settings( array $data ) { $s = []; $s['auto_description'] = ! empty( $data['auto_description'] ) && '1' === $data['auto_description']; $s['auto_pages'] = ! empty( $data['auto_pages'] ) && '1' === $data['auto_pages']; $s['auto_categories'] = ! empty( $data['auto_categories'] ) && '1' === $data['auto_categories']; $s['auto_sitemap'] = ! empty( $data['auto_sitemap'] ) && '1' === $data['auto_sitemap']; $s['optional_description'] = sanitize_textarea_field( wp_unslash( $data['optional_description'] ?? '' ) ); $s['webhook_url'] = esc_url_raw( wp_unslash( $data['webhook_url'] ?? '' ) ); $cpts = []; if ( ! empty( $data['auto_cpt'] ) && is_array( $data['auto_cpt'] ) ) { foreach ( $data['auto_cpt'] as $cpt ) { $cpts[] = sanitize_key( $cpt ); } } $s['auto_cpt'] = $cpts; $sections_raw = isset( $data['sections'] ) ? wp_unslash( $data['sections'] ) : '[]'; $decoded = is_string( $sections_raw ) ? json_decode( $sections_raw, true ) : []; $decoded = is_array( $decoded ) ? $decoded : []; $sections = []; foreach ( $decoded as $section ) { $clean = [ 'name' => sanitize_text_field( $section['name'] ?? '' ), 'links' => [], ]; foreach ( ( $section['links'] ?? [] ) as $link ) { $url = esc_url_raw( $link['url'] ?? '' ); if ( ! $url ) continue; $clean['links'][] = [ 'label' => sanitize_text_field( $link['label'] ?? '' ), 'url' => $url, 'desc' => sanitize_text_field( $link['desc'] ?? '' ), ]; } if ( $clean['name'] ) $sections[] = $clean; } $s['sections'] = $sections; return $s; } }