405]); } if (!current_user_can('manage_options')) wp_die(esc_html__('You do not have permission to access this page.', 'mkb-glossar')); check_admin_referer('mkb_glossar_setup_' . $action); // Only the two names and primary URL base are accepted after nonce validation. // Other wizard actions need no POST body and never unslash unknown data. if ($action !== 'names') return []; return [ 'mkb_collection' => isset($_POST['mkb_collection']) && is_string($_POST['mkb_collection']) ? sanitize_text_field(wp_unslash($_POST['mkb_collection'])) : null, 'mkb_item' => isset($_POST['mkb_item']) && is_string($_POST['mkb_item']) ? sanitize_text_field(wp_unslash($_POST['mkb_item'])) : null, 'mkb_base' => isset($_POST['mkb_base']) && is_string($_POST['mkb_base']) ? sanitize_text_field(wp_unslash($_POST['mkb_base'])) : null, ]; } add_action('admin_post_mkb_glossar_setup_names', static function () { $request = mkb_glossar_setup_guard('names'); // mkb_glossar_setup_guard() has already unslashed and sanitized both names. $raw_collection = $request['mkb_collection'] ?? null; $raw_item = $request['mkb_item'] ?? null; $base = mkb_glossar_url_base_candidate($request['mkb_base'] ?? null); if (!is_string($raw_collection) || !is_string($raw_item)) { wp_safe_redirect(mkb_glossar_setup_url(['setup_error'=>'names'])); exit; } $collection = trim($raw_collection); $item = trim($raw_item); if ($collection === '' || $item === '' || preg_match_all('/./us', $collection) > 80 || preg_match_all('/./us', $item) > 80) { wp_safe_redirect(mkb_glossar_setup_url(['setup_error'=>'names'])); exit; } if ($base === '') { wp_safe_redirect(mkb_glossar_setup_url(['setup_error'=>'url'])); exit; } $old_base = (string)mkb_glossar_opts()['slug']; if (($base !== $old_base || mkb_glossar_setup_state() === 'pending') && mkb_glossar_url_base_problem($base) !== null) { wp_safe_redirect(mkb_glossar_setup_url(['setup_error'=>'conflict', 'setup_base'=>$base])); exit; } // Preserve every unrelated setting, especially language URL bases and Elementor templates. $saved = get_option('mkb_glossar_options', []); if (!is_array($saved)) $saved = []; $saved['label'] = $collection; $saved['singular'] = $item; $saved['slug'] = $base; // update_option() executes the registered sanitize_callback, which checks // all three values again and leaves unrelated options unchanged. $saved['_section'] = 'setup_names'; update_option('mkb_glossar_options', $saved); $confirmed = get_option('mkb_glossar_options', []); if (!is_array($confirmed) || ($confirmed['label'] ?? null) !== $collection || ($confirmed['singular'] ?? null) !== $item || ($confirmed['slug'] ?? null) !== $base) { wp_safe_redirect(mkb_glossar_setup_url(['setup_error'=>'save'])); exit; } update_option('mkb_glossar_setup_state', 'in_progress', false); delete_option('mkb_glossar_setup_redirect'); wp_safe_redirect(mkb_glossar_setup_url(['setup_step'=>1,'setup_saved'=>1])); exit; }); add_action('admin_post_mkb_glossar_setup_skip', static function () { mkb_glossar_setup_guard('skip'); update_option('mkb_glossar_setup_state', 'skipped', false); delete_option('mkb_glossar_setup_redirect'); wp_safe_redirect(admin_url('edit.php?post_type=mkb_glossar&page=mkb-glossar&tab=general&setup_skipped=1')); exit; }); add_action('admin_post_mkb_glossar_setup_complete', static function () { mkb_glossar_setup_guard('complete'); // First step must have been explicitly confirmed before completion. if (mkb_glossar_setup_state() !== 'in_progress') { wp_safe_redirect(mkb_glossar_setup_url()); exit; } update_option('mkb_glossar_setup_state', 'completed', false); wp_safe_redirect(admin_url('edit.php?post_type=mkb_glossar&page=mkb-glossar&tab=general&setup_completed=1')); exit; }); add_action('admin_post_mkb_glossar_setup_restart', static function () { mkb_glossar_setup_guard('restart'); // Restart only the guided process. Existing posts, URLs, and settings are unchanged. update_option('mkb_glossar_setup_state', 'pending', false); delete_option('mkb_glossar_setup_redirect'); wp_safe_redirect(mkb_glossar_setup_url()); exit; }); /** Trusted MKB stroke icons; inline SVG belongs inside the actual action element. */ function mkb_glossar_setup_icon($name) { $paths = [ 'book' => '', 'plus' => '', 'pages' => '', 'link' => '', 'palette' => '', 'copy' => '', 'left' => '', 'right' => '', 'settings' => '', ]; if (!isset($paths[$name])) return ''; return ''; } function mkb_glossar_setup_page() { if (!current_user_can('manage_options')) wp_die(esc_html__('You do not have permission to access this page.', 'mkb-glossar')); $options = mkb_glossar_opts(); $names = mkb_glossar_display_names($options); $state = mkb_glossar_setup_state(); $ready = $state !== 'pending'; $counts = wp_count_posts('mkb_glossar'); $published = (int)($counts->publish ?? 0); $pages = get_posts(['post_type'=>'page', 'post_status'=>'publish', 'posts_per_page'=>1, 's'=>$names['collection'], 'fields'=>'ids']); $steps = [ [ 'title'=>__('Choose names and web address', 'mkb-glossar'), 'description'=>__('Choose names for the WordPress interface and a separate public URL base. You may use matching or different names.', 'mkb-glossar'), 'reason'=>__('The names identify your content in WordPress. The URL base determines where its public addresses begin. Existing URLs are never changed without your action.', 'mkb-glossar'), 'icon'=>'settings', ], [ /* translators: %s is the configured singular name of an item (for example, Artikel). */ 'title'=>sprintf(__('Create %s', 'mkb-glossar'), $names['item']), 'description'=>__('Start with a clear title and a short explanation. Add synonyms and a longer description only when they help visitors.', 'mkb-glossar'), 'reason'=>__('Why this matters: good explanations are the foundation for searches, tooltips and internal glossary links.', 'mkb-glossar'), /* translators: %d is the number of published glossary entries. */ 'status'=>$published ? sprintf(__('Published content: %d', 'mkb-glossar'), $published) : __('No published content yet', 'mkb-glossar'), 'primary'=>__('Manage content', 'mkb-glossar'), 'url'=>admin_url('edit.php?post_type=mkb_glossar'), /* translators: %s is the configured singular name of an item (for example, Artikel). */ 'secondary'=>sprintf(__('Create %s', 'mkb-glossar'), $names['item']), 'secondary_url'=>admin_url('post-new.php?post_type=mkb_glossar'), 'icon'=>'book', 'primary_icon'=>'pages', 'secondary_icon'=>'plus', ], [ /* translators: %s is the site-configured collection or item name (for example, Wiki). */ 'title'=>sprintf(__('Overview: %s', 'mkb-glossar'), $names['collection']), 'description'=>mkb_glossar_elementor_available() ? __('Create a page in Elementor with the MKB Glossary widget. Alternatively use a Gutenberg Shortcode block or the shortcode below. Search and alphabet navigation appear automatically.', 'mkb-glossar') : __('Create a WordPress page with a Shortcode block and paste the code below. Search and alphabet navigation appear automatically.', 'mkb-glossar'), 'reason'=>__('Why this matters: visitors need a clear place where they can browse your published content.', 'mkb-glossar'), 'status'=>$pages ? __('A matching page title exists; check its contents', 'mkb-glossar') : __('Create a glossary page', 'mkb-glossar'), 'primary'=>__('Open pages', 'mkb-glossar'), 'url'=>admin_url('edit.php?post_type=page'), 'secondary'=>__('Add a page', 'mkb-glossar'), 'secondary_url'=>admin_url('post-new.php?post_type=page'), 'code'=>'[mkb_glossar show_topics="true"]', 'icon'=>'pages', 'primary_icon'=>'pages', 'secondary_icon'=>'plus', ], [ 'title'=>__('Choose where automatic links appear', 'mkb-glossar'), 'description'=>__('Automatic links are optional. You choose which articles or pages receive links, how often they appear and where they are excluded.', 'mkb-glossar'), 'reason'=>__('Why this matters: helpful links should support reading, not interrupt it. Nothing is linked automatically until you enable it.', 'mkb-glossar'), 'status'=>!empty($options['auto_link']) ? __('Automatic linking is enabled', 'mkb-glossar') : __('Automatic linking is currently off', 'mkb-glossar'), 'primary'=>__('Open linking settings', 'mkb-glossar'), 'url'=>admin_url('edit.php?post_type=mkb_glossar&page=mkb-glossar&tab=linking'), 'icon'=>'link', 'primary_icon'=>'link', ], [ 'title'=>__('Adjust the appearance and check visibility', 'mkb-glossar'), 'description'=>mkb_glossar_elementor_available() ? __('Style sources and related content with the built-in controls or one shared Elementor Free template, then check the published result.', 'mkb-glossar') : __('Style sources and related content with the built-in controls, then check the published result.', 'mkb-glossar'), 'reason'=>__('Consistent presentation helps visitors recognize sources and related content. Structured information helps search engines understand published explanations.', 'mkb-glossar'), 'status'=>!empty($options['schema_enabled']) ? __('DefinedTerm data is enabled for published terms', 'mkb-glossar') : __('DefinedTerm data is currently off', 'mkb-glossar'), 'primary'=>__('Open appearance and SEO', 'mkb-glossar'), 'url'=>admin_url('edit.php?post_type=mkb_glossar&page=mkb-glossar&tab=presentation'), 'secondary'=>__('Read the handbook', 'mkb-glossar'), 'secondary_url'=>function_exists('mkb_glossar_help_url') ? mkb_glossar_help_url('display-step-5') : admin_url('edit.php?post_type=mkb_glossar'), 'icon'=>'palette', 'primary_icon'=>'palette', 'secondary_icon'=>'book', ], ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only GET/navigation parameter; no state is changed and input is validated separately. $initial = isset($_GET['setup_step']) && is_scalar($_GET['setup_step']) ? absint($_GET['setup_step']) : 0; if (!$ready) $initial = 0; $initial = min($initial, count($steps)-1); echo '
'; echo '
'.esc_html__('GETTING STARTED', 'mkb-glossar').''; /* translators: %s is the site-configured collection or item name (for example, Wiki). */ echo '

'.esc_html(sprintf(__('Getting started: %s', 'mkb-glossar'), $names['collection'])).'

'; echo '

'.esc_html($ready ? __('Your names are set. Continue with the remaining steps at your own pace or finish setup when ready.', 'mkb-glossar') : __('Start by choosing the names shown on your website. You can keep the defaults, complete the remaining steps at your own pace, or skip setup entirely.', 'mkb-glossar')).'

'; echo '
'; echo ''; wp_nonce_field('mkb_glossar_setup_skip'); echo '
'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only GET/navigation parameter; no state is changed and input is validated separately. if (isset($_GET['setup_error'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only GET/navigation parameter; no state is changed and input is validated separately. $error = is_string($_GET['setup_error']) ? sanitize_key($_GET['setup_error']) : ''; if ($error === 'save') $message = __('Your settings could not be saved. No changes were confirmed.', 'mkb-glossar'); elseif ($error === 'url') $message = __('Enter a valid URL base using letters, numbers and hyphens.', 'mkb-glossar'); elseif ($error === 'conflict') { // Read-only GET feedback, bounded by the same scalar slug validator. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only GET value for escaped notice; all writes use a separate nonce-checked POST action. $attempt = isset($_GET['setup_base']) && is_string($_GET['setup_base']) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only attempted URL base is validated, not saved. ? mkb_glossar_url_base_candidate(sanitize_text_field(wp_unslash($_GET['setup_base']))) : ''; $message = $attempt ? (mkb_glossar_url_base_problem($attempt) ?: __('This URL base conflicts with an existing WordPress address. Choose another and save again.', 'mkb-glossar')) : __('This URL base conflicts with an existing WordPress address. Choose another and save again.', 'mkb-glossar'); } else $message = __('Enter both names (up to 80 characters each) to continue.', 'mkb-glossar'); echo '

'.esc_html($message).'

'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only GET/navigation parameter; no state is changed and input is validated separately. if (isset($_GET['setup_saved'])) echo '

'.esc_html__('Names and URL base saved. You can continue with the next steps.', 'mkb-glossar').'

'; echo ''; foreach ($steps as $i=>$step) { echo '
'; /* translators: %1$d is the current setup step number, %2$d is the total number of steps. */ echo ''.esc_html(sprintf(__('Step %1$d of %2$d', 'mkb-glossar'),$i+1,count($steps))).''; echo '

'.esc_html($step['title']).'

'; echo '

'.esc_html($step['description']).'

'; echo wp_kses(mkb_glossar_info_box($step['reason'], 'mkb-setup-reason'), mkb_glossar_allowed_visual_markup()); if ($i===0) { echo '
'; echo ''; wp_nonce_field('mkb_glossar_setup_names'); echo '
'; echo '
'; echo ''; echo '

'.esc_html__('For example: Glossar, Wiki, Wissensdatenbank', 'mkb-glossar').'

'; echo '
'; echo ''; echo '

'.esc_html__('For example: Begriff, Artikel, Fachbegriff', 'mkb-glossar').'

'; echo '
'; echo ''; echo '

'.esc_html__('For example: glossar, wiki or knowledge. You may use a different address than your collection name.', 'mkb-glossar').'

'; echo '

'.esc_html__('Example URL:', 'mkb-glossar').' '.esc_html(trailingslashit(home_url('/')).$options['slug'].'/example/').'

'; $base_problem = mkb_glossar_url_base_problem((string)$options['slug']); if ($base_problem !== null) echo ''; echo '

'.esc_html__('Changing the URL base on an existing site may require redirects. Other plugins can define additional rewrite rules.', 'mkb-glossar').'

'; echo '
'; echo '
'; } else { if (isset($step['code'])) { echo '
'.esc_html($step['code']).'
'; echo '

'; } echo '

'.esc_html($step['status']).'

'; echo ''; } echo '
'; if ($i && $ready) echo ''; if ($i && $i'.esc_html__('Next step', 'mkb-glossar').''.wp_kses(mkb_glossar_setup_icon('right'), mkb_glossar_allowed_visual_markup()).''; elseif ($i===count($steps)-1 && $ready && $state==='in_progress') { echo '
'; echo ''; wp_nonce_field('mkb_glossar_setup_complete'); echo '
'; } elseif ($i===count($steps)-1) echo ''.esc_html__('You can revisit the assistant under Settings → General.', 'mkb-glossar').''; echo '
'; } echo '
'; }