options = $options; $this->init_hooks(); } /** * Initialize hooks. */ private function init_hooks() { add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); add_action( 'admin_init', array( $this, 'handle_admin_actions' ) ); add_action( 'save_post_page', array( $this, 'clear_cache_on_save' ), 10, 1 ); add_filter( 'plugin_action_links_' . AZGL_PLUGIN_BASENAME, array( $this, 'add_plugin_links' ) ); } /** * Add admin menu. */ public function add_admin_menu() { add_menu_page( __( '3task Glossary', '3task-glossary' ), __( '3task Glossary', '3task-glossary' ), 'manage_options', 'az-glossary', array( $this, 'render_admin_page' ), 'dashicons-book-alt', 30 ); } /** * Enqueue admin assets. * * @param string $hook Current admin page hook. */ public function enqueue_admin_assets( $hook ) { if ( 'toplevel_page_az-glossary' !== $hook ) { return; } wp_enqueue_style( 'azgl-admin', AZGL_PLUGIN_URL . 'assets/css/admin.css', array(), AZGL_VERSION ); wp_enqueue_script( 'azgl-admin', AZGL_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), AZGL_VERSION, true ); wp_localize_script( 'azgl-admin', 'azglAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'azgl_admin_nonce' ), 'strings' => array( 'confirmRemove' => __( 'Are you sure you want to remove this glossary?', '3task-glossary' ), 'saved' => __( 'Settings saved.', '3task-glossary' ), 'error' => __( 'An error occurred.', '3task-glossary' ), ), ) ); } /** * Handle admin actions. */ public function handle_admin_actions() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Only reading for tab display if ( isset( $_GET['page'] ) && 'az-glossary' === $_GET['page'] && isset( $_GET['tab'] ) ) { $this->current_tab = sanitize_key( wp_unslash( $_GET['tab'] ) ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended // Handle form submissions. // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() if ( isset( $_POST['azgl_action'] ) ) { $this->process_form_submission(); } } /** * Process form submission. */ private function process_form_submission() { // Verify nonce. if ( ! isset( $_POST['azgl_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['azgl_nonce'] ) ), 'azgl_admin_action' ) ) { wp_die( esc_html__( 'Security check failed.', '3task-glossary' ) ); } // Check capability. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', '3task-glossary' ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated in handle_admin_actions() $action = isset( $_POST['azgl_action'] ) ? sanitize_key( wp_unslash( $_POST['azgl_action'] ) ) : ''; switch ( $action ) { case 'add_glossary': $this->handle_add_glossary(); break; case 'remove_glossary': $this->handle_remove_glossary(); break; case 'save_glossary_mode': $this->handle_save_glossary_mode(); break; case 'save_settings': $this->handle_save_settings(); break; case 'save_design': $this->handle_save_design(); break; case 'save_supporter': $this->handle_save_supporter(); break; case 'save_language': $this->handle_save_language(); break; } } /** * Handle add glossary. * * Nonce verification happens in process_form_submission(). */ private function handle_add_glossary() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $has_page_id = isset( $_POST['page_id'] ); $page_id = $has_page_id ? absint( $_POST['page_id'] ) : 0; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( ! $has_page_id ) { $this->redirect_with_message( 'error', __( 'No page selected.', '3task-glossary' ) ); return; } if ( 0 === $page_id ) { $this->redirect_with_message( 'error', __( 'Invalid page selected.', '3task-glossary' ) ); return; } $glossaries = $this->options->get( 'glossaries', array() ); $result = $this->options->add_glossary( $page_id ); if ( $result ) { $this->redirect_with_message( 'success', __( 'Glossary added successfully.', '3task-glossary' ) ); } else { $this->redirect_with_message( 'error', __( 'This page is already a glossary.', '3task-glossary' ) ); } } /** * Handle remove glossary. * * Nonce verification happens in process_form_submission(). */ private function handle_remove_glossary() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $has_page_id = isset( $_POST['page_id'] ); $page_id = $has_page_id ? absint( $_POST['page_id'] ) : 0; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( ! $has_page_id ) { $this->redirect_with_message( 'error', __( 'No glossary specified.', '3task-glossary' ) ); return; } $result = $this->options->remove_glossary( $page_id ); if ( $result ) { $this->redirect_with_message( 'success', __( 'Glossary removed successfully.', '3task-glossary' ) ); } else { $this->redirect_with_message( 'error', __( 'Could not remove glossary.', '3task-glossary' ) ); } } /** * Handle mode switch of one glossary (A-Z glossary or knowledge base). * * Nonce verification happens in process_form_submission(). */ private function handle_save_glossary_mode() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $page_id = isset( $_POST['page_id'] ) ? absint( $_POST['page_id'] ) : 0; $mode = isset( $_POST['azgl_mode'] ) ? sanitize_key( wp_unslash( $_POST['azgl_mode'] ) ) : 'glossary'; $breadcrumbs = isset( $_POST['azgl_kb_breadcrumbs'] ); // phpcs:enable WordPress.Security.NonceVerification.Missing if ( AZGL_KB::save_settings( $page_id, $mode, $breadcrumbs ) ) { azgl()->clear_glossary_cache( $page_id ); $this->redirect_with_message( 'success', __( 'Glossary settings saved.', '3task-glossary' ) ); } else { $this->redirect_with_message( 'error', __( 'No glossary specified.', '3task-glossary' ) ); } } /** * Handle save settings. * * Nonce verification happens in process_form_submission(). */ private function handle_save_settings() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $settings = array( 'linking_mode' => isset( $_POST['linking_mode'] ) ? sanitize_key( wp_unslash( $_POST['linking_mode'] ) ) : 'selective', 'link_first_only' => isset( $_POST['link_first_only'] ), 'link_class' => isset( $_POST['link_class'] ) ? sanitize_html_class( wp_unslash( $_POST['link_class'] ) ) : 'glossary-term', 'cache_enabled' => isset( $_POST['cache_enabled'] ), // v2.3.0 Advanced Features. 'enable_schema' => isset( $_POST['enable_schema'] ), 'enable_print' => isset( $_POST['enable_print'] ), ); // phpcs:enable WordPress.Security.NonceVerification.Missing // Validate linking_mode. $valid_modes = array( 'disabled', 'selective' ); if ( ! in_array( $settings['linking_mode'], $valid_modes, true ) ) { $settings['linking_mode'] = 'selective'; } $this->options->update( $settings ); $this->redirect_with_message( 'success', __( 'Settings saved.', '3task-glossary' ), 'settings' ); } /** * Handle save design settings. * * Nonce verification happens in process_form_submission(). */ private function handle_save_design() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $settings = array( 'nav_style' => isset( $_POST['nav_style'] ) ? sanitize_key( wp_unslash( $_POST['nav_style'] ) ) : 'buttons', 'inactive_letters' => isset( $_POST['inactive_letters'] ) ? sanitize_key( wp_unslash( $_POST['inactive_letters'] ) ) : 'accessible', 'color_scheme' => isset( $_POST['color_scheme'] ) ? sanitize_key( wp_unslash( $_POST['color_scheme'] ) ) : 'emerald', 'dark_mode' => isset( $_POST['dark_mode'] ) ? sanitize_key( wp_unslash( $_POST['dark_mode'] ) ) : 'auto', ); // phpcs:enable WordPress.Security.NonceVerification.Missing // Validate nav_style. $valid_styles = array_keys( $this->options->get_nav_styles() ); if ( ! in_array( $settings['nav_style'], $valid_styles, true ) ) { $settings['nav_style'] = 'buttons'; } // Validate inactive_letters. $valid_inactive = array_keys( $this->options->get_inactive_letter_modes() ); if ( ! in_array( $settings['inactive_letters'], $valid_inactive, true ) ) { $settings['inactive_letters'] = 'accessible'; } // Validate color_scheme. $valid_schemes = array_keys( $this->options->get_color_schemes() ); if ( ! in_array( $settings['color_scheme'], $valid_schemes, true ) ) { $settings['color_scheme'] = 'emerald'; } // Validate dark_mode. $valid_modes = array_keys( $this->options->get_dark_mode_options() ); if ( ! in_array( $settings['dark_mode'], $valid_modes, true ) ) { $settings['dark_mode'] = 'auto'; } $this->options->update( $settings ); $this->redirect_with_message( 'success', __( 'Design settings saved.', '3task-glossary' ), 'design' ); } /** * Handle save supporter settings. * * Nonce verification happens in process_form_submission(). */ private function handle_save_supporter() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $settings = array( 'show_credit_link' => isset( $_POST['show_credit_link'] ), ); // phpcs:enable WordPress.Security.NonceVerification.Missing $this->options->update( $settings ); $this->redirect_with_message( 'success', __( 'Supporter settings saved.', '3task-glossary' ), 'support' ); } /** * Handle save language settings. * * Nonce verification happens in process_form_submission(). */ private function handle_save_language() { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in process_form_submission() $language = isset( $_POST['plugin_language'] ) ? sanitize_key( wp_unslash( $_POST['plugin_language'] ) ) : 'auto'; // Validate language. $valid_languages = array_keys( $this->options->get_available_languages() ); if ( ! in_array( $language, $valid_languages, true ) ) { $language = 'auto'; } $this->options->set( 'plugin_language', $language ); $this->redirect_with_message( 'success', __( 'Language saved. Please reload the page to see changes.', '3task-glossary' ), 'settings' ); } /** * Redirect with message. * * @param string $type Message type (success/error). * @param string $message Message text. * @param string $tab Tab to redirect to. */ private function redirect_with_message( $type, $message, $tab = 'glossaries' ) { set_transient( 'azgl_admin_message', array( 'type' => $type, 'message' => $message, ), 30 ); wp_safe_redirect( admin_url( 'admin.php?page=az-glossary&tab=' . $tab ) ); exit; } /** * Clear cache on page save. * * @param int $post_id Post ID. */ public function clear_cache_on_save( $post_id ) { $page = get_post( $post_id ); if ( ! $page ) { return; } // Clear cache if this is a glossary page. if ( $this->options->is_glossary_page( $post_id ) ) { azgl()->clear_glossary_cache( $post_id ); } // Clear cache if this is a child of a glossary page. if ( $page->post_parent && $this->options->is_glossary_page( $page->post_parent ) ) { azgl()->clear_glossary_cache( $page->post_parent ); } } /** * Add plugin action links. * * @param array $links Existing links. * @return array Modified links. */ public function add_plugin_links( $links ) { $custom_links = array( '' . esc_html__( 'Settings', '3task-glossary' ) . '', '' . esc_html__( 'Go Pro', '3task-glossary' ) . '', ); return array_merge( $custom_links, $links ); } /** * Render admin page. */ public function render_admin_page() { // Tab configuration with icons (Unicode emojis for DSGVO compliance). $tabs = array( 'dashboard' => array( 'label' => __( 'Dashboard', '3task-glossary' ), 'icon' => '📊', ), 'glossaries' => array( 'label' => __( 'Glossaries', '3task-glossary' ), 'icon' => '📚', 'count' => count( $this->options->get( 'glossaries', array() ) ), ), 'settings' => array( 'label' => __( 'Settings', '3task-glossary' ), 'icon' => '⚙️', ), 'design' => array( 'label' => __( 'Design', '3task-glossary' ), 'icon' => '🎨', ), 'help' => array( 'label' => __( 'Help', '3task-glossary' ), 'icon' => '❓', ), ); // Get current tab. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only reading for tab display $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'dashboard'; if ( ! array_key_exists( $current_tab, $tabs ) ) { $current_tab = 'dashboard'; } // Show admin message if exists. $message = get_transient( 'azgl_admin_message' ); if ( $message ) { delete_transient( 'azgl_admin_message' ); } ?>

render_dashboard_tab(); break; case 'glossaries': $this->render_glossaries_tab(); break; case 'settings': $this->render_settings_tab(); break; case 'design': $this->render_design_tab(); break; case 'help': $this->render_help_tab(); break; } ?>
options->get( 'glossaries', array() ); $total_terms = 0; $active_count = 0; foreach ( $glossaries as $glossary ) { $total_terms += azgl()->get_entry_count( absint( $glossary['page_id'] ) ); if ( ! empty( $glossary['active'] ) ) { ++$active_count; } } // Check if user is new (no glossaries yet). $is_new_user = empty( $glossaries ); ?>
👋

✨
📚 0 ) : ?> +
📝 0 ) : ?> +
✅
🔗
—

📚

get_entry_count( $page_id ); $initial = $page ? strtoupper( substr( $page->post_title, 0, 1 ) ) : '?'; ?>

post_title ) : esc_html__( 'Unknown', '3task-glossary' ); ?>

● ○
5 ) : ?>

🔍 🔗 🖨️ 📦

🎁
✓ ✓ ✓ ✓ ✓

Powered by 3task Glossary

→
options->get( 'glossaries', array() ); ?>

get_entry_count( $page_id ); ?>
post_title ); ?>

'page_id', 'show_option_none' => esc_html__( '— Select Page —', '3task-glossary' ), 'option_none_value' => 0, 'exclude' => array_map( 'absint', array_column( $glossaries, 'page_id' ) ), ) ); // phpcs:enable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude ?>

options->get( 'linking_mode', 'selective' ); $link_first_only = $this->options->get( 'link_first_only', true ); $link_class = $this->options->get( 'link_class', 'glossary-term' ); $cache_enabled = $this->options->get( 'cache_enabled', true ); $plugin_language = $this->options->get( 'plugin_language', 'auto' ); $languages = $this->options->get_available_languages(); // v2.3.0 Advanced Features. $enable_schema = $this->options->get( 'enable_schema', true ); $enable_print = $this->options->get( 'enable_print', true ); ?>

$label ) : ?>

options->get( 'nav_style', 'buttons' ); $inactive_letters = $this->options->get( 'inactive_letters', 'accessible' ); $color_scheme = $this->options->get( 'color_scheme', 'emerald' ); $dark_mode = $this->options->get( 'dark_mode', 'auto' ); // Migrate old boolean value to new string format. if ( true === $inactive_letters || '1' === $inactive_letters ) { $inactive_letters = 'show'; } elseif ( false === $inactive_letters || '' === $inactive_letters ) { $inactive_letters = 'none'; } $nav_styles = $this->options->get_nav_styles(); $inactive_modes = $this->options->get_inactive_letter_modes(); $color_schemes = $this->options->get_color_schemes(); $dark_modes = $this->options->get_dark_mode_options(); ?>

$label ) : ?>

PRO

🔗

💬

🔍

📊

→