PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.3
4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 All 201 releases
← All changes | includes/Core/Request.php +47 -5 4.9.2 → 4.9.3 View file →
@@ -948,14 +948,40 @@
948 948
949 949 // Check if we have 'docs' query var (alternative to 'name')
950 950 if ( isset( $wp_query->query_vars['docs'] ) && ! empty( $wp_query->query_vars['docs'] ) ) {
951 951
952 + // Verify the requested doc actually exists (and is visible to the
953 + // current user) before forcing a single-doc render. Category-in-path
954 + // permalinks put the doc slug in the `docs` var alongside a valid
955 + // doc_category; without this guard a missing slug under a real
956 + // category rendered the single template against a null post — an
957 + // HTTP 200 soft-404 plus "read property on null" warnings on every
958 + // (often bot) hit. See betterdocs/betterdocs#169.
959 + $doc_post = get_page_by_path( $wp_query->query_vars['docs'], OBJECT, 'docs' );
960 + $doc_visible = $doc_post
961 + && ( 'private' !== $doc_post->post_status || current_user_can( 'read_private_docs' ) );
962 +
963 + if ( ! $doc_visible ) {
964 + // No such doc (or private and not permitted) — serve a genuine
965 + // 404 instead of a single render against a null post.
966 + $wp_query->set_404();
967 + status_header( 404 );
968 + nocache_headers();
969 + add_filter( 'template_include', function( $template ) {
970 + $not_found = get_404_template();
971 + return $not_found ? $not_found : $template;
972 + }, 999 );
973 + return;
974 + }
975 +
952 976 // Explicitly set this as a single post
953 - $wp_query->is_single = true;
954 - $wp_query->is_singular = true;
955 - $wp_query->is_404 = false;
956 - $wp_query->is_archive = false;
957 - $wp_query->is_tax = false;
977 + $wp_query->is_single = true;
978 + $wp_query->is_singular = true;
979 + $wp_query->is_404 = false;
980 + $wp_query->is_archive = false;
981 + $wp_query->is_tax = false;
982 + $wp_query->queried_object = $doc_post;
983 + $wp_query->queried_object_id = $doc_post->ID;
958 984 return;
959 985 }
960 986
961 987 // If 'name' is set, check if a post with that name exists
@@ -1075,8 +1101,24 @@
1075 1101 if ( $code == 404 && (
1076 1102 (isset($wp_query->query_vars['doc_category']) && ! empty($wp_query->query_vars['doc_category'])) ||
1077 1103 (isset($wp_query->query_vars['doc_tag']) && ! empty($wp_query->query_vars['doc_tag']))
1078 1104 ) ) {
1105 + // A single-doc request (category-in-path permalinks carry the doc slug
1106 + // in the `docs`/`name` var alongside doc_category) must resolve to a real
1107 + // doc. A valid doc is already served 200 by the queried-object branch
1108 + // above; if we reach here with a single-doc indicator but no resolvable
1109 + // post, the doc does not exist — keep the genuine 404 rather than forcing
1110 + // a soft-404 200 off the category term alone. See betterdocs/betterdocs#169.
1111 + $single_doc_slug = '';
1112 + if ( isset( $wp_query->query_vars['docs'] ) && ! empty( $wp_query->query_vars['docs'] ) ) {
1113 + $single_doc_slug = $wp_query->query_vars['docs'];
1114 + } elseif ( isset( $wp_query->query_vars['name'] ) && ! empty( $wp_query->query_vars['name'] ) ) {
1115 + $single_doc_slug = $wp_query->query_vars['name'];
1116 + }
1117 + if ( '' !== $single_doc_slug && ! get_page_by_path( $single_doc_slug, OBJECT, 'docs' ) ) {
1118 + return $status_header;
1119 + }
1120 +
1079 1121 // Validate existence before forcing 200
1080 1122 // Use encoded fallback so Bengali/Arabic/CJK slugs are found correctly.
1081 1123 if ( isset($wp_query->query_vars['doc_category']) && ! empty($wp_query->query_vars['doc_category']) ) {
1082 1124 $term = $this->get_term_by_slug_or_encoded( $wp_query->query_vars['doc_category'], 'doc_category' );