| @@ -2157,101 +2157,8 @@ | ||
| 2157 | 2157 | $result = preg_replace('/\[\/?\w[\w-]*[^\]]*\]/', '', $content); |
| 2158 | 2158 | return ($result !== null) ? $result : $content; |
| 2159 | 2159 | } |
| 2160 | 2160 | |
| 2161 | -/** | |
| 2162 | - * Text a page pulls in by REFERENCE, resolved by lookup, never rendered (plan 735943). | |
| 2163 | - * | |
| 2164 | - * A synced pattern is stored as a self-closing block comment that names a | |
| 2165 | - * wp_block post (wp:block with a ref), and theme block shortcodes such as | |
| 2166 | - * WoodMart's HTML Block name another post by id. The assembler never runs | |
| 2167 | - * the_content (that would execute every shortcode on the site inside a bulk | |
| 2168 | - * admin request), so both references stripped to nothing and the chatbot | |
| 2169 | - * never saw text the visitor reads on the page. Each reference is replaced | |
| 2170 | - * with the referenced post's own post_content when that post exists, is | |
| 2171 | - * published and has no password; the usual shortcode strip and tag strip | |
| 2172 | - * then run over the inlined markup exactly as over the page's own. | |
| 2173 | - * | |
| 2174 | - * Guards: depth 3 (a pattern may hold a pattern, which may hold one more), | |
| 2175 | - * the chain of ids being inlined ends a self-reference or a cycle, and the | |
| 2176 | - * text inlined per page is capped (mxchat_reference_inline_cap, 200,000 | |
| 2177 | - * characters). A reference that does not resolve is left in place and is | |
| 2178 | - * removed by the strips exactly as before. | |
| 2179 | - * | |
| 2180 | - * The shortcode map is filterable (mxchat_reference_shortcodes): shortcode | |
| 2181 | - * name => the attribute that carries the post id. Default: html_block / id. | |
| 2182 | - * | |
| 2183 | - * @param string $content Raw post_content. | |
| 2184 | - * @param int $depth 0 at the page itself. | |
| 2185 | - * @param array $chain Ids being inlined on the current chain (cycle guard). | |
| 2186 | - * @param int $budget Characters still allowed on this page. | |
| 2187 | - * @return string | |
| 2188 | - */ | |
| 2189 | -private function mxchat_inline_referenced_content($content, $depth = 0, $chain = array(), &$budget = null) { | |
| 2190 | - if (!is_string($content) || $content === '') { | |
| 2191 | - return (string) $content; | |
| 2192 | - } | |
| 2193 | - if ($budget === null) { | |
| 2194 | - $budget = max(0, (int) apply_filters('mxchat_reference_inline_cap', 200000)); | |
| 2195 | - } | |
| 2196 | - if ($depth >= 3 || $budget <= 0) { | |
| 2197 | - return $content; | |
| 2198 | - } | |
| 2199 | - | |
| 2200 | - $self = $this; | |
| 2201 | - $resolve = function ($id) use ($self, $depth, $chain, &$budget) { | |
| 2202 | - $id = (int) $id; | |
| 2203 | - if ($id <= 0 || in_array($id, $chain, true) || $budget <= 0) { | |
| 2204 | - return null; | |
| 2205 | - } | |
| 2206 | - $ref = get_post($id); | |
| 2207 | - if (!($ref instanceof WP_Post) || $ref->post_status !== 'publish' || (string) $ref->post_password !== '') { | |
| 2208 | - return null; | |
| 2209 | - } | |
| 2210 | - $text = (string) $ref->post_content; | |
| 2211 | - if (strlen($text) > $budget) { | |
| 2212 | - $text = function_exists('mb_strcut') ? mb_strcut($text, 0, $budget) : substr($text, 0, $budget); | |
| 2213 | - } | |
| 2214 | - $budget -= strlen($text); | |
| 2215 | - $next = $chain; | |
| 2216 | - $next[] = $id; | |
| 2217 | - $r = new ReflectionMethod($self, 'mxchat_inline_referenced_content'); | |
| 2218 | - $r->setAccessible(true); | |
| 2219 | - return "\n" . $r->invokeArgs($self, array($text, $depth + 1, $next, &$budget)) . "\n"; | |
| 2220 | - }; | |
| 2221 | - | |
| 2222 | - // 1. Synced patterns: <!-- wp:block {"ref":123} /--> (pattern overrides keep the ref). | |
| 2223 | - if (strpos($content, 'wp:block') !== false) { | |
| 2224 | - $content = preg_replace_callback('/<!--\s*wp:block\s+(\{.*?\})\s*\/-->/s', function ($m) use ($resolve) { | |
| 2225 | - $attrs = json_decode($m[1], true); | |
| 2226 | - $id = (is_array($attrs) && isset($attrs['ref'])) ? (int) $attrs['ref'] : 0; | |
| 2227 | - $text = $id ? $resolve($id) : null; | |
| 2228 | - return ($text === null) ? $m[0] : $text; | |
| 2229 | - }, $content); | |
| 2230 | - } | |
| 2231 | - | |
| 2232 | - // 2. Reference shortcodes: [html_block id="123"] and whatever the filter adds. | |
| 2233 | - if (strpos($content, '[') !== false) { | |
| 2234 | - $map = apply_filters('mxchat_reference_shortcodes', array('html_block' => 'id')); | |
| 2235 | - foreach ((array) $map as $tag => $attr) { | |
| 2236 | - if (!is_string($tag) || !is_string($attr) || !preg_match('/^[A-Za-z0-9_-]+$/', $tag) || !preg_match('/^[A-Za-z0-9_-]+$/', $attr)) { | |
| 2237 | - continue; | |
| 2238 | - } | |
| 2239 | - if (strpos($content, '[' . $tag) === false) { | |
| 2240 | - continue; | |
| 2241 | - } | |
| 2242 | - $content = preg_replace_callback('/\[' . preg_quote($tag, '/') . '(\s[^\]]*)?\]/', function ($m) use ($resolve, $attr) { | |
| 2243 | - $atts = shortcode_parse_atts(isset($m[1]) ? trim(rtrim(trim($m[1]), '/')) : ''); | |
| 2244 | - $id = (is_array($atts) && isset($atts[$attr])) ? (int) $atts[$attr] : 0; | |
| 2245 | - $text = $id ? $resolve($id) : null; | |
| 2246 | - return ($text === null) ? $m[0] : $text; | |
| 2247 | - }, $content); | |
| 2248 | - } | |
| 2249 | - } | |
| 2250 | - | |
| 2251 | - return $content; | |
| 2252 | -} | |
| 2253 | - | |
| 2254 | 2161 | public function mxchat_sanitize_content_for_api($content) { |
| 2255 | 2162 | //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...'); |
| 2256 | 2163 | |
| 2257 | 2164 | // Remove shortcode tags but PRESERVE content inside them |
| @@ -2946,10 +2853,9 @@ | ||
| 2946 | 2853 | 'full_content' => wp_kses_post(wpautop($display_content)), |
| 2947 | 2854 | 'content_length' => mb_strlen($display_content), |
| 2948 | 2855 | 'preview_length' => $preview_length, |
| 2949 | 2856 | 'source_url' => $entry->source_url, |
| 2950 | - 'has_link' => !empty($entry->source_url) && strpos($entry->source_url, 'mxchat://') !== 0 && !MxChat_Utils::is_upload_source($entry->source_url), | |
| 2951 | - 'source_html' => self::mxchat_source_cell_html($entry->source_url, $entry->bot_id ?? 'default', false), | |
| 2857 | + 'has_link' => !empty($entry->source_url) && strpos($entry->source_url, 'mxchat://') !== 0, | |
| 2952 | 2858 | 'chunk_metadata' => $chunk_metadata, |
| 2953 | 2859 | 'bot_id' => $entry->bot_id ?? 'default', |
| 2954 | 2860 | 'edit_nonce' => wp_create_nonce('mxchat_edit_entry_nonce'), |
| 2955 | 2861 | 'delete_nonce' => wp_create_nonce('mxchat_delete_prompt_nonce') |
| @@ -3154,9 +3060,16 @@ | ||
| 3154 | 3060 | </span> |
| 3155 | 3061 | </div> |
| 3156 | 3062 | </td> |
| 3157 | 3063 | <td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;"> |
| 3158 | - <?php echo self::mxchat_source_cell_html($source_url, $current_bot_id, true); ?> | |
| 3064 | + <?php if (!empty($source_url) && strpos($source_url, 'mxchat://') !== 0 && strpos($source_url, '_ungrouped_') !== 0) : ?> | |
| 3065 | + <a href="<?php echo esc_url($source_url); ?>" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"> | |
| 3066 | + <span class="dashicons dashicons-external" style="font-size: 14px;"></span> | |
| 3067 | + <?php esc_html_e('View Source', 'mxchat'); ?> | |
| 3068 | + </a> | |
| 3069 | + <?php else : ?> | |
| 3070 | + <span style="color: var(--mxch-text-muted);"><?php esc_html_e('Manual Content', 'mxchat'); ?></span> | |
| 3071 | + <?php endif; ?> | |
| 3159 | 3072 | </td> |
| 3160 | 3073 | <td class="mxchat-actions-cell" style="padding: 12px 16px; white-space: nowrap;"> |
| 3161 | 3074 | <button type="button" |
| 3162 | 3075 | class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-inspect-entry-btn" |
| @@ -3301,10 +3214,16 @@ | ||
| 3301 | 3214 | $actual_source = $source_url; |
| 3302 | 3215 | if (strpos($source_url, '_ungrouped_') === 0) { |
| 3303 | 3216 | $actual_source = $prompt->source_url ?? ''; |
| 3304 | 3217 | } |
| 3305 | - echo self::mxchat_source_cell_html($actual_source, $current_bot_id, false); | |
| 3306 | - ?> | |
| 3218 | + if (!empty($actual_source) && strpos($actual_source, 'mxchat://') !== 0) : ?> | |
| 3219 | + <a href="<?php echo esc_url($actual_source); ?>" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"> | |
| 3220 | + <span class="dashicons dashicons-external" style="font-size: 14px;"></span> | |
| 3221 | + <?php esc_html_e('View', 'mxchat'); ?> | |
| 3222 | + </a> | |
| 3223 | + <?php else : ?> | |
| 3224 | + <span style="color: var(--mxch-text-muted);"><?php esc_html_e('Manual', 'mxchat'); ?></span> | |
| 3225 | + <?php endif; ?> | |
| 3307 | 3226 | </td> |
| 3308 | 3227 | <td style="padding: 12px 16px; white-space: nowrap;"> |
| 3309 | 3228 | <button type="button" |
| 3310 | 3229 | class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-inspect-entry-btn" |
| @@ -3407,50 +3326,8 @@ | ||
| 3407 | 3326 | * fiction; the page number here is only the cursor step, which the manager |
| 3408 | 3327 | * maps to a stored paginationToken. Used by the page template on first render |
| 3409 | 3328 | * and by the AJAX paginator, so the two can never drift. |
| 3410 | 3329 | */ |
| 3411 | -/** | |
| 3412 | - * The Source column of the Knowledge list, one helper for its five render | |
| 3413 | - * sites (plan b339c0). A web source links; an uploaded file (upload://) is | |
| 3414 | - * named in plain text ("Uploaded file: manual.pdf, page 3"), and when it has | |
| 3415 | - * a Source URL that page gets the existing View Source link beside the name; | |
| 3416 | - * manual content keeps its muted label. esc_url() empties upload://, which | |
| 3417 | - * used to leave those rows with a View Source link whose address was empty. | |
| 3418 | - * | |
| 3419 | - * @param string $source_url Stored source identity. | |
| 3420 | - * @param string $bot_id Bot the entry belongs to (Source URLs are kept per bot). | |
| 3421 | - * @param bool $long "View Source" / "Manual Content" (grouped rows) or "View" / "Manual" (single rows). | |
| 3422 | - * @return string HTML. | |
| 3423 | - */ | |
| 3424 | -public static function mxchat_source_cell_html($source_url, $bot_id = 'default', $long = true) { | |
| 3425 | - $view = $long ? __('View Source', 'mxchat') : __('View', 'mxchat'); | |
| 3426 | - $manual = $long ? __('Manual Content', 'mxchat') : __('Manual', 'mxchat'); | |
| 3427 | - $muted = '<span style="color: var(--mxch-text-muted);">%s</span>'; | |
| 3428 | - $link = '<a href="%s" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"><span class="dashicons dashicons-external" style="font-size: 14px;"></span> %s</a>'; | |
| 3429 | - | |
| 3430 | - if (empty($source_url) || !is_string($source_url) || strpos($source_url, 'mxchat://') === 0 || strpos($source_url, '_ungrouped_') === 0) { | |
| 3431 | - return sprintf($muted, esc_html($manual)); | |
| 3432 | - } | |
| 3433 | - | |
| 3434 | - if (MxChat_Utils::is_upload_source($source_url)) { | |
| 3435 | - $name = MxChat_Utils::upload_source_name($source_url); | |
| 3436 | - $page = MxChat_Utils::upload_source_page($source_url); | |
| 3437 | - $label = $page > 0 | |
| 3438 | - /* translators: 1: file name, 2: page number */ | |
| 3439 | - ? sprintf(__('Uploaded file: %1$s, page %2$d', 'mxchat'), $name, $page) | |
| 3440 | - /* translators: %s: file name */ | |
| 3441 | - : sprintf(__('Uploaded file: %s', 'mxchat'), $name); | |
| 3442 | - $html = sprintf($muted, esc_html($label)); | |
| 3443 | - $page_url = MxChat_Utils::get_upload_source_url($source_url, $bot_id); | |
| 3444 | - if ($page_url !== '') { | |
| 3445 | - $html .= ' ' . sprintf($link, esc_url($page_url), esc_html($view)); | |
| 3446 | - } | |
| 3447 | - return $html; | |
| 3448 | - } | |
| 3449 | - | |
| 3450 | - return sprintf($link, esc_url($source_url), esc_html($view)); | |
| 3451 | -} | |
| 3452 | - | |
| 3453 | 3330 | public static function mxchat_cursor_pagination_html($page, $has_next, $search_query = '', $content_type_filter = '') { |
| 3454 | 3331 | $page = max(1, absint($page)); |
| 3455 | 3332 | if ($page <= 1 && !$has_next) { |
| 3456 | 3333 | return ''; |
| @@ -3519,18 +3396,12 @@ | ||
| 3519 | 3396 | case 'manual': |
| 3520 | 3397 | $where_clauses[] = "(source_url = '' OR source_url IS NULL OR source_url LIKE 'mxchat://%')"; |
| 3521 | 3398 | break; |
| 3522 | 3399 | case 'pdf': |
| 3523 | - // A PDF's pages are stored as <pdf>#page=<n>, so the address | |
| 3524 | - // alone never ended in .pdf and this filter found no page of | |
| 3525 | - // any PDF (plan b339c0). The stored content_type is the honest | |
| 3526 | - // test; the two LIKEs keep rows written before it existed. | |
| 3527 | - $where_clauses[] = "(content_type = 'pdf' OR source_url LIKE '%.pdf' OR source_url LIKE '%.pdf#page=%')"; | |
| 3400 | + $where_clauses[] = "source_url LIKE '%.pdf'"; | |
| 3528 | 3401 | break; |
| 3529 | 3402 | case 'url': |
| 3530 | - // Everything with a web address: not manual content, not a PDF | |
| 3531 | - // page, not an uploaded file (upload://, which has no address). | |
| 3532 | - $where_clauses[] = "source_url != '' AND source_url IS NOT NULL AND source_url NOT LIKE 'mxchat://%' AND source_url NOT LIKE 'upload://%' AND NOT (content_type = 'pdf' OR source_url LIKE '%.pdf' OR source_url LIKE '%.pdf#page=%')"; | |
| 3403 | + $where_clauses[] = "source_url != '' AND source_url IS NOT NULL AND source_url NOT LIKE 'mxchat://%' AND source_url NOT LIKE '%.pdf'"; | |
| 3533 | 3404 | break; |
| 3534 | 3405 | } |
| 3535 | 3406 | } |
| 3536 | 3407 | |
| @@ -3717,9 +3588,16 @@ | ||
| 3717 | 3588 | </span> |
| 3718 | 3589 | </div> |
| 3719 | 3590 | </td> |
| 3720 | 3591 | <td class="mxchat-url-cell" style="padding: 12px 16px; font-size: 13px;"> |
| 3721 | - <?php echo self::mxchat_source_cell_html($source_url, $current_bot_id, true); ?> | |
| 3592 | + <?php if (!empty($source_url) && strpos($source_url, 'mxchat://') !== 0 && strpos($source_url, '_ungrouped_') !== 0) : ?> | |
| 3593 | + <a href="<?php echo esc_url($source_url); ?>" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"> | |
| 3594 | + <span class="dashicons dashicons-external" style="font-size: 14px;"></span> | |
| 3595 | + <?php esc_html_e('View Source', 'mxchat'); ?> | |
| 3596 | + </a> | |
| 3597 | + <?php else : ?> | |
| 3598 | + <span style="color: var(--mxch-text-muted);"><?php esc_html_e('Manual Content', 'mxchat'); ?></span> | |
| 3599 | + <?php endif; ?> | |
| 3722 | 3600 | </td> |
| 3723 | 3601 | <td class="mxchat-actions-cell" style="padding: 12px 16px; white-space: nowrap;"> |
| 3724 | 3602 | <?php if ($data_source !== 'pinecone') : ?> |
| 3725 | 3603 | <button type="button" |
| @@ -3855,10 +3733,16 @@ | ||
| 3855 | 3733 | $actual_source = $source_url; |
| 3856 | 3734 | if (strpos($source_url, '_ungrouped_') === 0) { |
| 3857 | 3735 | $actual_source = $prompt->source_url ?? ''; |
| 3858 | 3736 | } |
| 3859 | - echo self::mxchat_source_cell_html($actual_source, $current_bot_id, false); | |
| 3860 | - ?> | |
| 3737 | + if (!empty($actual_source) && strpos($actual_source, 'mxchat://') !== 0) : ?> | |
| 3738 | + <a href="<?php echo esc_url($actual_source); ?>" target="_blank" style="color: var(--mxch-primary); text-decoration: none;"> | |
| 3739 | + <span class="dashicons dashicons-external" style="font-size: 14px;"></span> | |
| 3740 | + <?php esc_html_e('View', 'mxchat'); ?> | |
| 3741 | + </a> | |
| 3742 | + <?php else : ?> | |
| 3743 | + <span style="color: var(--mxch-text-muted);"><?php esc_html_e('Manual', 'mxchat'); ?></span> | |
| 3744 | + <?php endif; ?> | |
| 3861 | 3745 | </td> |
| 3862 | 3746 | <td style="padding: 12px 16px; white-space: nowrap;"> |
| 3863 | 3747 | <button type="button" |
| 3864 | 3748 | class="mxch-btn mxch-btn-ghost mxch-btn-sm mxchat-edit-entry-btn" |
| @@ -7096,20 +6980,18 @@ | ||
| 7096 | 6980 | |
| 7097 | 6981 | // Assemble the indexable text via the shared post-kind assembler (a3d60c), |
| 7098 | 6982 | // reading from the FILTERED post object — not re-fetched by ID, which would |
| 7099 | 6983 | // discard it. Auto-sync reads content/excerpt in its historical |
| 7100 | - // get_post_field() display context, appends a product's custom tabs | |
| 7101 | - // (plan f9573e — the bulk import wrote them and this save path dropped them | |
| 7102 | - // again; its product branch is reachable only with the WooCommerce | |
| 7103 | - // integration off, the state every store set up from today's screens is in), | |
| 7104 | - // and gates ACF→PDF extraction behind its own opt-in option — default OFF, | |
| 7105 | - // because re-parsing every ACF PDF on every editor save is expensive and | |
| 7106 | - // most sites don't want it (the 25 MB size cap lives in the shared | |
| 7107 | - // extractor either way). | |
| 6984 | + // get_post_field() display context, never appended product custom tabs | |
| 6985 | + // (its product branch is reachable only with the WooCommerce integration | |
| 6986 | + // off), and gates ACF→PDF extraction behind its own opt-in option — | |
| 6987 | + // default OFF, because re-parsing every ACF PDF on every editor save is | |
| 6988 | + // expensive and most sites don't want it (the 25 MB size cap lives in the | |
| 6989 | + // shared extractor either way). | |
| 7108 | 6990 | $prepared = $this->mxchat_prepare_post_content_for_indexing($post_id, $post, array( |
| 7109 | 6991 | 'read_display' => true, |
| 7110 | 6992 | 'extract_acf_pdfs' => get_option('mxchat_auto_sync_acf_pdfs', '0') === '1', |
| 7111 | - 'include_product_tabs' => true, | |
| 6993 | + 'include_product_tabs' => false, | |
| 7112 | 6994 | )); |
| 7113 | 6995 | $final_content = $prepared['content']; |
| 7114 | 6996 | |
| 7115 | 6997 | // Embedding decision — custom-provider-aware. Gating on a cloud API key |
| @@ -7153,17 +7035,13 @@ | ||
| 7153 | 7035 | * is deliberate (plan 11720c). Gates BOTH the PDF-id |
| 7154 | 7036 | * collection walk and the extraction loop; the ids are |
| 7155 | 7037 | * only ever read inside the extraction branch, so |
| 7156 | 7038 | * gating collection is output-identical on every install. |
| 7157 | - * 'include_product_tabs' bool Append the yikes_woo custom tabs (own + applied | |
| 7158 | - * reusable) to product content. The bulk path has always | |
| 7159 | - * done so; the auto-sync product branch (reachable only | |
| 7160 | - * with the WooCommerce integration off) did not until | |
| 7161 | - * plan f9573e, so every editor save of a product dropped | |
| 7162 | - * the tabs its import had written. Both callers pass true | |
| 7163 | - * now; kb-reimport's 'sync' route mirrors the auto-sync | |
| 7164 | - * value so "what a save writes today" stays the truth it | |
| 7165 | - * compares against. Empty text for a product with no tabs. | |
| 7039 | + * 'include_product_tabs' bool The bulk path has always appended yikes_woo custom | |
| 7040 | + * tabs to product content; the auto-sync product branch | |
| 7041 | + * (reachable only with the WooCommerce integration off) | |
| 7042 | + * never did. Preserved per-path — converging it would be | |
| 7043 | + * a behavior change, recorded on the plan instead. | |
| 7166 | 7044 | * |
| 7167 | 7045 | * Returns array: 'content' (the assembled indexable text), 'acf_fields_found' and |
| 7168 | 7046 | * 'pdf_extracted_count' (the bulk path reports both in its AJAX response). |
| 7169 | 7047 | */ |
| @@ -7193,13 +7071,11 @@ | ||
| 7193 | 7071 | if (trim($clean_excerpt) !== '') { |
| 7194 | 7072 | $content .= "Short Description: " . $this->mxchat_decode_entities_for_indexing(wp_strip_all_tags($clean_excerpt)) . "\n\n"; |
| 7195 | 7073 | } |
| 7196 | 7074 | |
| 7197 | - // Main content — resolve text the page pulls in by reference (synced patterns, | |
| 7198 | - // WoodMart HTML Blocks; plan 735943) by lookup, then remove shortcode tags but | |
| 7199 | - // preserve content inside them, then strip tags (don't use 'the_content' | |
| 7200 | - // filter as it may re-add shortcodes and would execute every shortcode). | |
| 7201 | - $clean_content = $this->strip_shortcode_tags_preserve_content($this->mxchat_inline_referenced_content($raw_content)); | |
| 7075 | + // Main content — remove shortcode tags but preserve content inside them, then | |
| 7076 | + // strip tags (don't use 'the_content' filter as it may re-add shortcodes). | |
| 7077 | + $clean_content = $this->strip_shortcode_tags_preserve_content($raw_content); | |
| 7202 | 7078 | $content .= $this->mxchat_decode_entities_for_indexing(wp_strip_all_tags($clean_content)); |
| 7203 | 7079 | |
| 7204 | 7080 | // WooCommerce product enrichment (post-fields kind). The WC-object assembler |
| 7205 | 7081 | // (mxchat_prepare_product_content_for_indexing) owns product rows whenever the |
| @@ -7902,12 +7778,8 @@ | ||
| 7902 | 7778 | 'store' => $store, |
| 7903 | 7779 | 'source' => isset($args['source']) && $args['source'] !== '' ? (string) $args['source'] : 'all', |
| 7904 | 7780 | 'before' => $before, |
| 7905 | 7781 | 'cap' => isset($args['cap']) ? (int) $args['cap'] : 100, |
| 7906 | - // Plan a00ef8: also rewrite entries whose page was edited after import; | |
| 7907 | - // with the default date they are offered whatever their own date says. | |
| 7908 | - 'with_edited' => !empty($args['edited']), | |
| 7909 | - 'before_set' => !empty($args['before_set']), | |
| 7910 | 7782 | 'cursor' => $store === 'wordpress' ? 0 : '', |
| 7911 | 7783 | 'max_id' => $store === 'wordpress' ? (int) $wpdb->get_var("SELECT MAX(id) FROM {$wpdb->prefix}mxchat_system_prompt_content") : 0, |
| 7912 | 7784 | 'scan_done' => false, |
| 7913 | 7785 | 'scanned' => 0, |
| @@ -7912,9 +7784,8 @@ | ||
| 7912 | 7784 | 'scan_done' => false, |
| 7913 | 7785 | 'scanned' => 0, |
| 7914 | 7786 | 'unchanged' => 0, |
| 7915 | 7787 | 'changed' => 0, |
| 7916 | - 'edited' => 0, | |
| 7917 | 7788 | 'skipped' => 0, |
| 7918 | 7789 | 'candidates' => array(), |
| 7919 | 7790 | 'notes' => array(), |
| 7920 | 7791 | 'pos' => 0, |
| @@ -7924,10 +7795,10 @@ | ||
| 7924 | 7795 | 'updated' => time(), |
| 7925 | 7796 | ); |
| 7926 | 7797 | } |
| 7927 | 7798 | |
| 7928 | -/** True when the entry passes --source and --before ($ignore_date: --source only). */ | |
| 7929 | -private function mxchat_reimport_selected(array $entry, array $state, $ignore_date = false) { | |
| 7799 | +/** True when the entry passes --source and --before. */ | |
| 7800 | +private function mxchat_reimport_selected(array $entry, array $state) { | |
| 7930 | 7801 | $url = (string) $entry['source_url']; |
| 7931 | 7802 | $source = (string) $state['source']; |
| 7932 | 7803 | if ($source === 'upload') { |
| 7933 | 7804 | if (!MxChat_Utils::is_upload_source($url)) { |
| @@ -7941,40 +7812,19 @@ | ||
| 7941 | 7812 | if (strpos($url, $source) !== 0) { |
| 7942 | 7813 | return false; |
| 7943 | 7814 | } |
| 7944 | 7815 | } |
| 7945 | - if ($ignore_date) { | |
| 7946 | - return true; | |
| 7947 | - } | |
| 7948 | 7816 | $limit = strtotime($state['before'] . ' 00:00:00'); |
| 7949 | - $when = $this->mxchat_reimport_entry_time($entry); | |
| 7950 | - // An entry with no date on record predates the date field: treat it as old. | |
| 7951 | - return ($when === 0) || ($limit === false) || ($when < $limit); | |
| 7952 | -} | |
| 7953 | - | |
| 7954 | -/** When the entry was last written, as a Unix time; 0 when nothing is on record. */ | |
| 7955 | -private function mxchat_reimport_entry_time(array $entry) { | |
| 7956 | - $when = $entry['updated']; | |
| 7817 | + $when = $entry['updated']; | |
| 7957 | 7818 | if (is_string($when) && $when !== '' && !ctype_digit($when)) { |
| 7958 | 7819 | $when = strtotime($when); |
| 7959 | 7820 | } |
| 7960 | - return (int) $when; | |
| 7821 | + $when = (int) $when; | |
| 7822 | + // An entry with no date on record predates the date field: treat it as old. | |
| 7823 | + return ($when === 0) || ($limit === false) || ($when < $limit); | |
| 7961 | 7824 | } |
| 7962 | 7825 | |
| 7963 | 7826 | /** |
| 7964 | - * True when the post, page, product or Media Library file behind an entry was | |
| 7965 | - * modified after the entry was written (plan a00ef8). With no date on the | |
| 7966 | - * entry there is nothing to compare, so false. | |
| 7967 | - */ | |
| 7968 | -private function mxchat_reimport_edited_after(array $entry, $post_id) { | |
| 7969 | - $when = $this->mxchat_reimport_entry_time($entry); | |
| 7970 | - if ($when <= 0 || !$post_id) { | |
| 7971 | - return false; | |
| 7972 | - } | |
| 7973 | - return ((int) get_post_modified_time('U', true, (int) $post_id)) > $when; | |
| 7974 | -} | |
| 7975 | - | |
| 7976 | -/** | |
| 7977 | 7827 | * One page of entries (not rows: a chunked entry is one entry). |
| 7978 | 7828 | * @return array{entries:array,done:bool}|WP_Error |
| 7979 | 7829 | */ |
| 7980 | 7830 | private function mxchat_reimport_list_page(array &$state, $limit) { |
| @@ -8306,11 +8156,10 @@ | ||
| 8306 | 8156 | $filtered = apply_filters('mxchat_before_process_post', $post, $route === 'sync' ? 'default' : $bot_id); |
| 8307 | 8157 | if (!($filtered instanceof WP_Post)) { |
| 8308 | 8158 | $filtered = $post; |
| 8309 | 8159 | } |
| 8310 | - // Same argument sets as the two writers (auto-sync appends product tabs since plan f9573e). | |
| 8311 | 8160 | $args = ($route === 'sync') |
| 8312 | - ? array('read_display' => true, 'extract_acf_pdfs' => get_option('mxchat_auto_sync_acf_pdfs', '0') === '1', 'include_product_tabs' => true) | |
| 8161 | + ? array('read_display' => true, 'extract_acf_pdfs' => get_option('mxchat_auto_sync_acf_pdfs', '0') === '1', 'include_product_tabs' => false) | |
| 8313 | 8162 | : array('read_display' => false, 'extract_acf_pdfs' => get_option('mxchat_acf_pdf_extraction', '0') === '1', 'include_product_tabs' => true); |
| 8314 | 8163 | $prepared = $this->mxchat_prepare_post_content_for_indexing($post_id, $filtered, $args); |
| 8315 | 8164 | return array('text' => (string) $prepared['content']); |
| 8316 | 8165 | } |
| @@ -8399,34 +8248,8 @@ | ||
| 8399 | 8248 | |
| 8400 | 8249 | if ($repair === null) { |
| 8401 | 8250 | $result['outcome'] = 'changed'; |
| 8402 | 8251 | $result['note'] = __('its source reads differently now, and not because of these fixes', 'mxchat'); |
| 8403 | - $post_id = isset($fresh['post_id']) ? (int) $fresh['post_id'] : 0; | |
| 8404 | - $type = (string) $entry['content_type']; | |
| 8405 | - if ($type === 'url') { | |
| 8406 | - // Imported by address: nothing on this site says when the page changed. | |
| 8407 | - $result['note'] .= ' ' . __('(imported by address: no edit date to compare)', 'mxchat'); | |
| 8408 | - } elseif ($post_id && $type !== 'content' && $type !== '' && $this->mxchat_reimport_edited_after($entry, $post_id)) { | |
| 8409 | - // A post, page, product or Media Library file edited after this entry | |
| 8410 | - // was written (plan a00ef8): offered as "edited", rewritten only when | |
| 8411 | - // asked. Text an owner typed under a page's address (type content) is | |
| 8412 | - // never read as edited: there is no import to compare it with. | |
| 8413 | - $best = -1; | |
| 8414 | - foreach ($fresh['texts'] as $text) { | |
| 8415 | - $text = $this->mxchat_reimport_storable($text); | |
| 8416 | - $shared = count(array_intersect_key($stored_lines, array_flip(array_filter(array_map('trim', explode("\n", $text)), 'strlen')))); | |
| 8417 | - if ($shared > $best) { | |
| 8418 | - $best = $shared; | |
| 8419 | - $result['fresh'] = $text; | |
| 8420 | - } | |
| 8421 | - } | |
| 8422 | - $result['outcome'] = 'edited'; | |
| 8423 | - $result['reasons'] = array('edited'); | |
| 8424 | - $result['note'] = __('edited since import', 'mxchat'); | |
| 8425 | - $result['title'] = isset($fresh['title']) ? (string) $fresh['title'] : ''; | |
| 8426 | - $result['post_id'] = $post_id; | |
| 8427 | - $result['is_file'] = !empty($fresh['is_file']); | |
| 8428 | - } | |
| 8429 | 8252 | return $result; |
| 8430 | 8253 | } |
| 8431 | 8254 | $result['outcome'] = 'repair'; |
| 8432 | 8255 | $result['reasons'] = $repair['reasons']; |
| @@ -8537,11 +8360,11 @@ | ||
| 8537 | 8360 | * changed between the scan and the confirm) cannot write something the scan |
| 8538 | 8361 | * did not show. |
| 8539 | 8362 | * @return array{outcome:string,reasons:array,note:string} |
| 8540 | 8363 | */ |
| 8541 | -private function mxchat_reimport_apply_entry(array $entry, $bot_id, $api_key, $rewrite_edited = false) { | |
| 8364 | +private function mxchat_reimport_apply_entry(array $entry, $bot_id, $api_key) { | |
| 8542 | 8365 | $eval = $this->mxchat_reimport_evaluate($entry, $bot_id); |
| 8543 | - if ($eval['outcome'] !== 'repair' && !($rewrite_edited && $eval['outcome'] === 'edited')) { | |
| 8366 | + if ($eval['outcome'] !== 'repair') { | |
| 8544 | 8367 | return array('outcome' => $eval['outcome'], 'reasons' => array(), 'note' => $eval['note']); |
| 8545 | 8368 | } |
| 8546 | 8369 | $role = $this->mxchat_reimport_role($entry); |
| 8547 | 8370 | |
| @@ -8635,30 +8458,18 @@ | ||
| 8635 | 8458 | $state['scan_done'] = true; |
| 8636 | 8459 | break; |
| 8637 | 8460 | } |
| 8638 | 8461 | foreach ($page['entries'] as $entry) { |
| 8639 | - $in_window = $this->mxchat_reimport_selected($entry, $state); | |
| 8640 | - // "Also rewrite edited entries" with the default date (plan a00ef8): an | |
| 8641 | - // entry whose page was edited after import is stale whatever its own | |
| 8642 | - // date says, so it is looked at for that one outcome only. | |
| 8643 | - $for_edit_only = !$in_window && !empty($state['with_edited']) && empty($state['before_set']) | |
| 8644 | - && $this->mxchat_reimport_selected($entry, $state, true); | |
| 8645 | - if (!$in_window && !$for_edit_only) { | |
| 8462 | + if (!$this->mxchat_reimport_selected($entry, $state)) { | |
| 8646 | 8463 | continue; |
| 8647 | 8464 | } |
| 8465 | + $state['scanned']++; | |
| 8648 | 8466 | $eval = $this->mxchat_reimport_evaluate($entry, $state['bot']); |
| 8649 | - if ($for_edit_only && $eval['outcome'] !== 'edited') { | |
| 8650 | - continue; | |
| 8651 | - } | |
| 8652 | - $state['scanned']++; | |
| 8653 | - if ($eval['outcome'] === 'repair' || ($eval['outcome'] === 'edited' && !empty($state['with_edited']))) { | |
| 8467 | + if ($eval['outcome'] === 'repair') { | |
| 8654 | 8468 | unset($entry['text']); |
| 8655 | 8469 | $entry['reasons'] = $eval['reasons']; |
| 8656 | 8470 | $entry['label'] = $this->mxchat_reimport_label($entry); |
| 8657 | 8471 | $state['candidates'][] = $entry; |
| 8658 | - } elseif ($eval['outcome'] === 'edited') { | |
| 8659 | - $state['edited']++; | |
| 8660 | - $this->mxchat_reimport_note($state, 'edited', $entry, $eval['note']); | |
| 8661 | 8472 | } elseif ($eval['outcome'] === 'unchanged') { |
| 8662 | 8473 | $state['unchanged']++; |
| 8663 | 8474 | } elseif ($eval['outcome'] === 'changed') { |
| 8664 | 8475 | $state['changed']++; |
| @@ -8688,9 +8499,9 @@ | ||
| 8688 | 8499 | break; |
| 8689 | 8500 | } |
| 8690 | 8501 | $i = $state['pos']; |
| 8691 | 8502 | $entry = $state['candidates'][$i]; |
| 8692 | - $done = $this->mxchat_reimport_apply_entry($entry, $state['bot'], $api_key, !empty($state['with_edited'])); | |
| 8503 | + $done = $this->mxchat_reimport_apply_entry($entry, $state['bot'], $api_key); | |
| 8693 | 8504 | $state['candidates'][$i]['result'] = $done['outcome']; |
| 8694 | 8505 | $state['candidates'][$i]['note'] = $done['note']; |
| 8695 | 8506 | if ($done['outcome'] === 'repaired') { |
| 8696 | 8507 | $state['repaired']++; |
| @@ -8753,15 +8564,8 @@ | ||
| 8753 | 8564 | * |
| 8754 | 8565 | * [--sleep=<ms>] |
| 8755 | 8566 | * : Pause this many milliseconds after each rewritten entry. |
| 8756 | 8567 | * |
| 8757 | - * [--edited] | |
| 8758 | - * : Also rewrite entries whose post, page, product or Media Library file was | |
| 8759 | - * edited after the entry was written (for sites that keep auto-sync off). | |
| 8760 | - * Without it such entries are listed as "Edited since import, not rewritten". | |
| 8761 | - * With it the default --before date does not apply to them; a --before you | |
| 8762 | - * give still does. Entries imported by address have no edit date and are left alone. | |
| 8763 | - * | |
| 8764 | 8568 | * [--yes] |
| 8765 | 8569 | * : Proceed when more than 25 entries need re-embedding (API cost gate). |
| 8766 | 8570 | * |
| 8767 | 8571 | * ## EXAMPLES |
| @@ -8768,9 +8572,8 @@ | ||
| 8768 | 8572 | * |
| 8769 | 8573 | * wp mxchat kb-reimport --dry-run |
| 8770 | 8574 | * wp mxchat kb-reimport --source=https://example.com/docs/ --limit=50 |
| 8771 | 8575 | * wp mxchat kb-reimport --before=2026-10-01 --yes |
| 8772 | - * wp mxchat kb-reimport --edited --dry-run | |
| 8773 | 8576 | */ |
| 8774 | 8577 | public function cli_kb_reimport($args, $assoc_args) { |
| 8775 | 8578 | $dry_run = !empty($assoc_args['dry-run']); |
| 8776 | 8579 | $yes = !empty($assoc_args['yes']); |
| @@ -8784,18 +8587,11 @@ | ||
| 8784 | 8587 | 'bot' => isset($assoc_args['bot']) ? $assoc_args['bot'] : 'default', |
| 8785 | 8588 | 'source' => isset($assoc_args['source']) ? $assoc_args['source'] : 'all', |
| 8786 | 8589 | 'before' => isset($assoc_args['before']) ? $assoc_args['before'] : self::REIMPORT_DEFAULT_BEFORE, |
| 8787 | 8590 | 'cap' => 0, |
| 8788 | - 'edited' => !empty($assoc_args['edited']), | |
| 8789 | - 'before_set' => isset($assoc_args['before']), | |
| 8790 | 8591 | )); |
| 8791 | 8592 | $stores = array('wordpress' => 'the WordPress database', 'pinecone' => 'Pinecone', 'documents' => 'a Pinecone document index'); |
| 8792 | 8593 | WP_CLI::log(sprintf('Checking entries in %s last written before %s (source: %s).', $stores[$state['store']], $state['before'], $state['source'])); |
| 8793 | - if ($state['with_edited']) { | |
| 8794 | - WP_CLI::log($state['before_set'] | |
| 8795 | - ? 'Entries whose page was edited after import are rewritten too (within the --before date).' | |
| 8796 | - : 'Entries whose page was edited after import are rewritten too, whatever their own date.'); | |
| 8797 | - } | |
| 8798 | 8594 | |
| 8799 | 8595 | while (!$state['scan_done']) { |
| 8800 | 8596 | $this->mxchat_reimport_scan_step($state, microtime(true) + 20); |
| 8801 | 8597 | } |
| @@ -8802,17 +8598,16 @@ | ||
| 8802 | 8598 | if ($state['error'] !== '') { |
| 8803 | 8599 | WP_CLI::error('The scan stopped: ' . $state['error'] . ' Nothing was changed.'); |
| 8804 | 8600 | } |
| 8805 | 8601 | |
| 8806 | - $kinds = array('changed' => 'Left alone', 'edited' => 'Edited since import, not rewritten', 'skipped' => 'Skipped'); | |
| 8807 | 8602 | foreach ($state['notes'] as $note) { |
| 8808 | - WP_CLI::log(sprintf('%s %s (%s)', isset($kinds[$note['kind']]) ? $kinds[$note['kind']] : 'Skipped', $note['label'], $note['note'])); | |
| 8603 | + WP_CLI::log(sprintf('%s %s (%s)', $note['kind'] === 'changed' ? 'Left alone' : 'Skipped', $note['label'], $note['note'])); | |
| 8809 | 8604 | } |
| 8810 | 8605 | foreach ($state['candidates'] as $c) { |
| 8811 | 8606 | WP_CLI::log(sprintf('%s %s (%s)', $dry_run ? 'Would rewrite' : 'Will rewrite', $c['label'], implode(', ', $c['reasons']))); |
| 8812 | 8607 | } |
| 8813 | 8608 | $count = count($state['candidates']); |
| 8814 | - $summary = sprintf('%d checked: %d to rewrite, %d already correct, %d left alone (source changed), %d edited since import, %d skipped.', $state['scanned'], $count, $state['unchanged'], $state['changed'], $state['edited'], $state['skipped']); | |
| 8609 | + $summary = sprintf('%d checked: %d to rewrite, %d already correct, %d left alone (source changed), %d skipped.', $state['scanned'], $count, $state['unchanged'], $state['changed'], $state['skipped']); | |
| 8815 | 8610 | if ($dry_run) { |
| 8816 | 8611 | WP_CLI::success('Dry run. ' . $summary . ' Nothing was changed.'); |
| 8817 | 8612 | return; |
| 8818 | 8613 | } |
| @@ -8866,10 +8661,8 @@ | ||
| 8866 | 8661 | 'scanned' => (int) $state['scanned'], |
| 8867 | 8662 | 'to_repair' => count($state['candidates']), |
| 8868 | 8663 | 'unchanged' => (int) $state['unchanged'], |
| 8869 | 8664 | 'changed' => (int) $state['changed'], |
| 8870 | - 'edited' => isset($state['edited']) ? (int) $state['edited'] : 0, | |
| 8871 | - 'with_edited' => !empty($state['with_edited']), | |
| 8872 | 8665 | 'skipped' => (int) $state['skipped'], |
| 8873 | 8666 | 'pos' => (int) $state['pos'], |
| 8874 | 8667 | 'repaired' => (int) $state['repaired'], |
| 8875 | 8668 | 'failed' => (int) $state['failed'], |
| @@ -8905,15 +8698,11 @@ | ||
| 8905 | 8698 | } |
| 8906 | 8699 | |
| 8907 | 8700 | if ($op === 'scan') { |
| 8908 | 8701 | if (!empty($_POST['restart']) || empty($state) || $state['phase'] !== 'scan') { |
| 8909 | - $before = isset($_POST['before']) ? sanitize_text_field(wp_unslash($_POST['before'])) : self::REIMPORT_DEFAULT_BEFORE; | |
| 8910 | 8702 | $state = $this->mxchat_reimport_new_state(array( |
| 8911 | - 'bot' => isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default', | |
| 8912 | - 'before' => $before, | |
| 8913 | - 'edited' => !empty($_POST['edited']) && $_POST['edited'] !== '0', | |
| 8914 | - // The card always posts a date; one left at the default is the default. | |
| 8915 | - 'before_set' => $before !== self::REIMPORT_DEFAULT_BEFORE, | |
| 8703 | + 'bot' => isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default', | |
| 8704 | + 'before' => isset($_POST['before']) ? sanitize_text_field(wp_unslash($_POST['before'])) : self::REIMPORT_DEFAULT_BEFORE, | |
| 8916 | 8705 | )); |
| 8917 | 8706 | } |
| 8918 | 8707 | $this->mxchat_reimport_scan_step($state, microtime(true) + 8); |
| 8919 | 8708 | update_option(self::REIMPORT_STATE_OPTION, $state, false); |