PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.24
MxChat – AI Chatbot & Content Generation for WordPress v3.2.24
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
← All changes | admin/class-knowledge-manager.php +67 -278 3.2.25 → 3.2.24 View file →
@@ -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);