Changeset 3491287
- Timestamp:
- 03/25/2026 10:11:16 PM (10 days ago)
- Location:
- ask-my-content
- Files:
-
- 2 added
- 6 edited
- 23 copied
-
tags/1.2.0 (copied) (copied from ask-my-content/trunk)
-
tags/1.2.0/ask-my-content.php (copied) (copied from ask-my-content/trunk/ask-my-content.php) (4 diffs)
-
tags/1.2.0/assets/css/ask-my-content.css (copied) (copied from ask-my-content/trunk/assets/css/ask-my-content.css)
-
tags/1.2.0/assets/js/amc-admin-init.js (copied) (copied from ask-my-content/trunk/assets/js/amc-admin-init.js)
-
tags/1.2.0/assets/js/amc-chat-core.js (copied) (copied from ask-my-content/trunk/assets/js/amc-chat-core.js)
-
tags/1.2.0/assets/js/amc-floating.js (copied) (copied from ask-my-content/trunk/assets/js/amc-floating.js)
-
tags/1.2.0/assets/js/amc-payments.js (copied) (copied from ask-my-content/trunk/assets/js/amc-payments.js)
-
tags/1.2.0/assets/js/askmyco-settings.js (copied) (copied from ask-my-content/trunk/assets/js/askmyco-settings.js)
-
tags/1.2.0/build/ask-my-content/block.json (copied) (copied from ask-my-content/trunk/build/ask-my-content/block.json) (1 diff)
-
tags/1.2.0/build/blocks-manifest.php (copied) (copied from ask-my-content/trunk/build/blocks-manifest.php) (1 diff)
-
tags/1.2.0/includes/api.php (copied) (copied from ask-my-content/trunk/includes/api.php)
-
tags/1.2.0/includes/cli.php (copied) (copied from ask-my-content/trunk/includes/cli.php)
-
tags/1.2.0/includes/content.php (added)
-
tags/1.2.0/includes/deactivate.php (copied) (copied from ask-my-content/trunk/includes/deactivate.php)
-
tags/1.2.0/includes/indexing.php (copied) (copied from ask-my-content/trunk/includes/indexing.php)
-
tags/1.2.0/includes/settings.php (copied) (copied from ask-my-content/trunk/includes/settings.php)
-
tags/1.2.0/includes/settings_embed.php (copied) (copied from ask-my-content/trunk/includes/settings_embed.php)
-
tags/1.2.0/includes/settings_options.php (copied) (copied from ask-my-content/trunk/includes/settings_options.php)
-
tags/1.2.0/includes/settings_payments.php (copied) (copied from ask-my-content/trunk/includes/settings_payments.php)
-
tags/1.2.0/includes/settings_shared.php (copied) (copied from ask-my-content/trunk/includes/settings_shared.php)
-
tags/1.2.0/includes/sync.php (copied) (copied from ask-my-content/trunk/includes/sync.php)
-
tags/1.2.0/includes/utils.php (copied) (copied from ask-my-content/trunk/includes/utils.php) (2 diffs)
-
tags/1.2.0/readme.txt (copied) (copied from ask-my-content/trunk/readme.txt) (5 diffs)
-
tags/1.2.0/src/ask-my-content/block.json (copied) (copied from ask-my-content/trunk/src/ask-my-content/block.json) (1 diff)
-
trunk/ask-my-content.php (modified) (4 diffs)
-
trunk/build/ask-my-content/block.json (modified) (1 diff)
-
trunk/build/blocks-manifest.php (modified) (1 diff)
-
trunk/includes/content.php (added)
-
trunk/includes/utils.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (5 diffs)
-
trunk/src/ask-my-content/block.json (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
ask-my-content/tags/1.2.0/ask-my-content.php
r3486628 r3491287 4 4 * Plugin Name: Ask My Content - AI Q&A Chatbot 5 5 * Description: AI-powered Q&A chatbot, allowing users to ask questions and receive answers sourced from the site’s own posts and pages. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 5.8 8 8 * Requires PHP: 7.4 … … 19 19 } 20 20 require_once plugin_dir_path(__FILE__) . 'includes/utils.php'; 21 require_once plugin_dir_path(__FILE__) . 'includes/content.php'; 21 22 require_once plugin_dir_path(__FILE__) . 'includes/activate.php'; 22 23 require_once plugin_dir_path(__FILE__) . 'includes/api.php'; … … 92 93 $settings_path = $base_dir . 'assets/js/askmyco-settings.js'; 93 94 94 $core_ver = file_exists($core_path) ? filemtime($core_path) : '1. 1.0';95 $frontend_ver = file_exists($frontend_path) ? filemtime($frontend_path) : '1. 1.0';96 $admin_init_ver = file_exists($admin_init_path) ? filemtime($admin_init_path) : '1. 1.0';97 $style_ver = file_exists($style_path) ? filemtime($style_path) : '1. 1.0';98 $settings_ver = file_exists($settings_path) ? filemtime($settings_path) : '1. 1.0';95 $core_ver = file_exists($core_path) ? filemtime($core_path) : '1.2.0'; 96 $frontend_ver = file_exists($frontend_path) ? filemtime($frontend_path) : '1.2.0'; 97 $admin_init_ver = file_exists($admin_init_path) ? filemtime($admin_init_path) : '1.2.0'; 98 $style_ver = file_exists($style_path) ? filemtime($style_path) : '1.2.0'; 99 $settings_ver = file_exists($settings_path) ? filemtime($settings_path) : '1.2.0'; 99 100 100 101 $payments_path = $base_dir . 'assets/js/amc-payments.js'; 101 $payments_ver = file_exists($payments_path) ? filemtime($payments_path) : '1. 1.0';102 $payments_ver = file_exists($payments_path) ? filemtime($payments_path) : '1.2.0'; 102 103 103 104 if (! wp_script_is($core_handle, 'registered')) { … … 261 262 262 263 $floating_path = plugin_dir_path(__FILE__) . 'assets/js/amc-floating.js'; 263 $floating_ver = file_exists($floating_path) ? filemtime($floating_path) : '1. 1.0';264 $floating_ver = file_exists($floating_path) ? filemtime($floating_path) : '1.2.0'; 264 265 265 266 wp_enqueue_script( -
ask-my-content/tags/1.2.0/build/ask-my-content/block.json
r3486628 r3491287 3 3 "apiVersion": 3, 4 4 "name": "amc/ask-my-content", 5 "version": "1. 1.0",5 "version": "1.2.0", 6 6 "title": "Ask My Content", 7 7 "category": "widgets", -
ask-my-content/tags/1.2.0/build/blocks-manifest.php
r3486628 r3491287 6 6 'apiVersion' => 3, 7 7 'name' => 'amc/ask-my-content', 8 'version' => '1. 1.0',8 'version' => '1.2.0', 9 9 'title' => 'Ask My Content', 10 10 'category' => 'widgets', -
ask-my-content/tags/1.2.0/includes/utils.php
r3487385 r3491287 269 269 add_action('init', 'askmyco_bootstrap_session_cookies'); 270 270 271 function askmyco_get_product_content_parts(WP_Post $post): array272 {273 if ($post->post_type !== 'product' || ! function_exists('wc_get_product')) {274 return [];275 }276 277 $product = wc_get_product($post->ID);278 if (! $product) {279 return [];280 }281 282 $product_parts = [];283 284 $sku = trim((string) $product->get_sku());285 if ($sku !== '') {286 $product_parts[] = 'SKU: ' . $sku;287 }288 289 $price = trim((string) $product->get_price());290 if ($price !== '') {291 $product_parts[] = 'Price: ' . $price;292 }293 294 $weight = trim((string) $product->get_weight());295 if ($weight !== '') {296 $product_parts[] = 'Weight: ' . $weight;297 }298 299 $dimensions = array_filter([300 'Length: ' . trim((string) $product->get_length()),301 'Width: ' . trim((string) $product->get_width()),302 'Height: ' . trim((string) $product->get_height()),303 ], function ($value) {304 return substr($value, -2) !== ': ';305 });306 307 if ($dimensions !== []) {308 $product_parts[] = implode("\n", $dimensions);309 }310 311 $attributes = [];312 foreach ($product->get_attributes() as $attribute) {313 if (! is_object($attribute) || ! method_exists($attribute, 'get_name') || ! method_exists($attribute, 'get_options')) {314 continue;315 }316 317 $attribute_name = (string) $attribute->get_name();318 if ($attribute_name === '') {319 continue;320 }321 322 $label = function_exists('wc_attribute_label') ? wc_attribute_label($attribute_name) : $attribute_name;323 $values = [];324 325 if (method_exists($attribute, 'is_taxonomy') && $attribute->is_taxonomy() && function_exists('wc_get_product_terms')) {326 $values = wc_get_product_terms($post->ID, $attribute_name, ['fields' => 'names']);327 if (is_wp_error($values)) {328 $values = [];329 }330 } else {331 $values = $attribute->get_options();332 }333 334 $values = array_values(array_filter(array_map('strval', (array) $values), function ($value) {335 return trim($value) !== '';336 }));337 338 if ($values === []) {339 continue;340 }341 342 $attributes[] = $label . ': ' . implode(', ', $values);343 }344 345 if ($attributes !== []) {346 $product_parts[] = "Attributes:\n" . implode("\n", $attributes);347 }348 349 return $product_parts;350 }351 352 function askmyco_convert_post_to_rest_like_array(WP_Post $post): array353 {354 $content_parts = [];355 356 $content = askmyco_get_plain_text_from_html($post->post_content);357 if ($content !== '') {358 $content_parts[] = $content;359 }360 361 $excerpt = askmyco_get_plain_text_from_html($post->post_excerpt);362 if ($excerpt !== '') {363 $content_parts[] = 'Excerpt: ' . $excerpt;364 }365 366 $product_parts = askmyco_get_product_content_parts($post);367 if ($product_parts !== []) {368 $content_parts[] = implode("\n", $product_parts);369 }370 371 return [372 'id' => $post->ID,373 'date' => get_post_time('c', false, $post), // local time in ISO 8601374 'date_gmt' => get_post_time('c', true, $post),375 'guid' => [376 'rendered' => $post->guid,377 ],378 'modified' => get_post_modified_time('c', false, $post),379 'modified_gmt' => get_post_modified_time('c', true, $post),380 'slug' => $post->post_name,381 'status' => $post->post_status,382 'type' => $post->post_type,383 'link' => askmyco_get_canonical_permalink($post),384 'title' => [385 'rendered' => get_the_title($post),386 ],387 'content' => [388 'rendered' => implode("\n\n", array_filter($content_parts, function ($value) {389 return $value !== '';390 })),391 ],392 ];393 }394 395 /**396 * Build a permalink using the path from get_permalink($post) but forcing397 * scheme/host/port to match home_url(). This avoids cases where the host398 * resolves to an IP due to server headers or proxy configuration.399 */400 function askmyco_get_canonical_permalink($post): string401 {402 $permalink = get_permalink($post);403 $home = home_url('/');404 405 $p = wp_parse_url($permalink);406 $h = wp_parse_url($home);407 408 if ($p === false || $h === false || empty($h['host'])) {409 return $permalink;410 }411 $scheme = $h['scheme'] ?? 'https';412 $host = $h['host'];413 $port = isset($h['port']) ? ':' . $h['port'] : '';414 $path = $p['path'] ?? '/';415 $query = isset($p['query']) ? '?' . $p['query'] : '';416 $frag = isset($p['fragment']) ? '#' . $p['fragment'] : '';417 418 return $scheme . '://' . $host . $port . $path . $query . $frag;419 }420 421 /**422 * Build a canonical URL using home_url() for scheme/host/port and a provided423 * path/query/fragment. Useful for virtual resources like header/footer.424 */425 function askmyco_build_canonical_url(string $path = '/', array $query = [], ?string $fragment = null): string426 {427 $home = home_url('/');428 $h = wp_parse_url($home);429 if ($h === false || empty($h['host'])) {430 return $home;431 }432 $scheme = $h['scheme'] ?? 'https';433 $host = $h['host'];434 $port = isset($h['port']) ? ':' . $h['port'] : '';435 // Ensure path starts with '/'436 $path = '/' . ltrim($path, '/');437 $query_str = '';438 if (!empty($query)) {439 $query_str = '?' . http_build_query($query, '', '&');440 }441 $frag_str = $fragment !== null && $fragment !== '' ? '#' . ltrim($fragment, '#') : '';442 return $scheme . '://' . $host . $port . $path . $query_str . $frag_str;443 }444 445 271 /** 446 272 * Return the last indexed timestamp (UTC) for a given kind ('pages' or 'posts'). … … 505 331 } 506 332 507 // $html - The raw HTML‑containing block comment markup and paragraph tags508 function askmyco_get_plain_text_from_html($html): string509 {510 // 1. Remove any shortcode-derived tokens (just drops shortcodes)511 $no_shortcodes = strip_shortcodes($html);512 513 // 2. Strip ALL HTML, including scripts/styles, leaving visible text only514 $text = wp_strip_all_tags($no_shortcodes, true);515 516 // 3. Collapse whitespace (optional but helpful)517 $trimmed = preg_replace('/\s+/', ' ', $text);518 519 return trim($trimmed);520 }521 522 // $type: 'header' or 'footer'523 function askmyco_virtual_post_array_for_header_footer($type): ?array524 {525 if ($type !== 'header' && $type !== 'footer') {526 return null;527 }528 529 // Unique virtual post IDs530 $id = ($type === 'header') ? 1000001 : 1000002;531 532 // 1) Get rendered HTML for the header/footer533 $html = askmyco_render_header_footer_html($type);534 535 // 2) Extract visible text536 $text = askmyco_get_plain_text_from_html($html);537 538 // 3) Detect changes by hash so widget/customizer edits update modified date539 $hash_option = "askmyco_{$type}_content_hash";540 $old_hash = get_option($hash_option);541 $new_hash = md5($text);542 543 if ($new_hash !== $old_hash) {544 update_option($hash_option, $new_hash);545 $modified = current_time('c');546 } else {547 // Keep last known modified so you don’t thrash re-embeds548 $modified = get_option("askmyco_{$type}_modified") ?: current_time('c');549 }550 update_option("askmyco_{$type}_modified", $modified);551 552 // 4) Site info553 $site_uuid = get_option('askmyco_site_uuid');554 $url = home_url('/');555 $title = ucfirst($type);556 557 // 5) Return virtual post array558 return [559 'id' => $id,560 'date' => $modified,561 'date_gmt' => get_gmt_from_date($modified, 'c'),562 'guid' => ['rendered' => $url],563 'modified' => $modified,564 'modified_gmt' => get_gmt_from_date($modified, 'c'),565 'slug' => $type,566 'status' => 'publish',567 'type' => $type,568 'link' => askmyco_build_canonical_url('/', [], $type),569 'title' => ['rendered' => $title],570 'content' => ['rendered' => $text],571 'siteId' => $site_uuid,572 ];573 }574 575 function askmyco_render_header_footer_html($type)576 {577 if ($type !== 'header' && $type !== 'footer') return '';578 579 // Prefer child theme path first580 $classic = get_stylesheet_directory() . "/{$type}.php";581 if (file_exists($classic)) {582 ob_start();583 include $classic;584 return ob_get_clean();585 }586 // DB-stored template part (Site Editor edits)587 $theme = wp_get_theme()->get_stylesheet();588 if (function_exists('get_block_template')) {589 $template = get_block_template($theme . '//' . $type, 'wp_template_part');590 if ($template && !empty($template->content)) {591 $content = $template->content;592 return function_exists('do_blocks') ? do_blocks($content) : $content;593 }594 }595 596 $tpl = get_posts([597 'post_type' => 'wp_template_part',598 'name' => $type,599 'posts_per_page' => 3,600 'post_status' => 'publish',601 'no_found_rows' => true,602 'update_post_term_cache' => true,603 'update_post_meta_cache' => false,604 ]);605 foreach ($tpl as $candidate) {606 $terms = get_the_terms($candidate, 'wp_theme');607 if (is_wp_error($terms) || empty($terms)) {608 continue;609 }610 foreach ($terms as $term) {611 if ($term->slug === $theme) {612 $content = $candidate->post_content;613 return function_exists('do_blocks') ? do_blocks($content) : $content;614 }615 }616 }617 // Block template files fallback618 $theme_dir = get_stylesheet_directory();619 $cands = [620 "{$theme_dir}/parts/{$type}.html",621 "{$theme_dir}/templates/parts/{$type}.html",622 "{$theme_dir}/block-templates/parts/{$type}.html",623 "{$theme_dir}/block-templates/{$type}.html",624 "{$theme_dir}/templates/{$type}.html",625 ];626 foreach ($cands as $cand) {627 if (file_exists($cand)) {628 $raw = file_get_contents($cand);629 if (function_exists('do_blocks')) {630 return do_blocks($raw);631 }632 return $raw;633 }634 }635 // Last resort636 ob_start();637 if ($type === 'header') {638 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WP action639 do_action('wp_head');640 } else {641 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WP action642 do_action('wp_footer');643 }644 return ob_get_clean();645 }646 647 333 /** 648 334 * Build full API endpoint from stored URL and optional port. -
ask-my-content/tags/1.2.0/readme.txt
r3486628 r3491287 5 5 Requires PHP: 7.4 6 6 Tested up to: 6.9 7 Stable tag: 1. 1.07 Stable tag: 1.2.0 8 8 License: GPL-2.0-or-later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 17 17 Unlike standard content search plugins that rely on keyword matching, Ask My Content uses semantic embeddings and retrieval-augmented generation to return direct answers in full sentences. 18 18 19 WordPress Block Editor is fully supported.20 However not all dynamic WordPress content creation themes and plugins are supported. See FAQ for details.19 Supports Gutenberg, Elementor, Avada, and WooCommerce content. 20 Some highly customized dynamic content may still require review. See FAQ for details. 21 21 22 22 **Key Features** … … 59 59 60 60 = Why my indexed token count is low = 61 Dynamic WordPress content creation themes and plugins (which may include Elementor Pro widgets) are currently not supported. 62 You will see persistently low indexed token count, even after indexing is finished, and indexing token count stays low after clicking the Refresh button. 63 In this case your chat questions will not be answered correctly ("No available content"). 64 For more info email to: info@pavelweb.com 61 Low indexed token count usually means the plugin did not find much readable page content to send for indexing. 62 63 This can happen if: 64 1. The page is mostly built from theme templates, banners, sliders, or other repeated global sections. 65 2. The important content is loaded by a plugin or builder widget that does not expose much text to WordPress. 66 3. Indexing finished before the latest content changes were re-run. 67 68 Ask My Content now supports common WordPress content builders much better, including **Gutenberg**, **Elementor**, **Avada**, and **WooCommerce** product content. 69 70 If your indexed token count still stays unexpectedly low after re-running indexing, see the next FAQ section: **What content is supported for indexing?** 71 72 If the chat still answers with "No available content," the page may rely heavily on content generated outside the main WordPress content flow. 73 74 = What content is supported for indexing? = 75 Ask My Content is designed to index the main readable text of your site content. 76 77 Currently supported: 78 * Standard WordPress pages and posts 79 * Gutenberg block content 80 * Elementor content in many common setups 81 * Avada content in many common setups 82 * WooCommerce product content, including product descriptions, short descriptions, price, SKU, weight, dimensions, and attributes 83 * Other public post types, if enabled in plugin settings 84 85 In general, the plugin reads the main post content the way WordPress normally renders it, using the standard content pipeline (`apply_filters('the_content', ...)`). 86 87 That means it usually works well with content that lives inside the main editor content area, but it may still miss text that comes only from: 88 * headers or footers 89 * sliders, popups, and announcement bars 90 * heavily customized builder widgets 91 * content injected from external sources or loaded later with JavaScript 92 93 If a page is important for search quality, open it in WordPress and make sure the main text is actually stored in the page, post, or product content itself before re-running indexing. 65 94 66 95 = Does this plugin use my OpenAI API key? = … … 167 196 == Changelog == 168 197 198 = 1.2.0 = 199 Improved content indexing support for Gutenberg, Elementor, Avada, WooCommerce, and other post types. 200 169 201 = 1.1.0 = 170 202 Added support for other content types (Products, Books, Events, etc.), including WooCommerce products. … … 217 249 == Upgrade Notice == 218 250 219 = 1.1.0 = 220 Added support for other content types (Products, Books, Events, etc.), including WooCommerce products. 221 222 = 1.0.0 = 223 Added `Payments` tab to `Ask My Content Settings`, where you can check your usage and pay the balance (currently with Stripe) 251 = 1.2.0 = 252 Improved content indexing support for Gutenberg, Elementor, Avada, WooCommerce, and other post types. 253 If your site was indexed with an older plugin version, email info@pavelweb.com for re-index instructions. -
ask-my-content/tags/1.2.0/src/ask-my-content/block.json
r3486628 r3491287 3 3 "apiVersion": 3, 4 4 "name": "amc/ask-my-content", 5 "version": "1. 1.0",5 "version": "1.2.0", 6 6 "title": "Ask My Content", 7 7 "category": "widgets", -
ask-my-content/trunk/ask-my-content.php
r3486628 r3491287 4 4 * Plugin Name: Ask My Content - AI Q&A Chatbot 5 5 * Description: AI-powered Q&A chatbot, allowing users to ask questions and receive answers sourced from the site’s own posts and pages. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 5.8 8 8 * Requires PHP: 7.4 … … 19 19 } 20 20 require_once plugin_dir_path(__FILE__) . 'includes/utils.php'; 21 require_once plugin_dir_path(__FILE__) . 'includes/content.php'; 21 22 require_once plugin_dir_path(__FILE__) . 'includes/activate.php'; 22 23 require_once plugin_dir_path(__FILE__) . 'includes/api.php'; … … 92 93 $settings_path = $base_dir . 'assets/js/askmyco-settings.js'; 93 94 94 $core_ver = file_exists($core_path) ? filemtime($core_path) : '1. 1.0';95 $frontend_ver = file_exists($frontend_path) ? filemtime($frontend_path) : '1. 1.0';96 $admin_init_ver = file_exists($admin_init_path) ? filemtime($admin_init_path) : '1. 1.0';97 $style_ver = file_exists($style_path) ? filemtime($style_path) : '1. 1.0';98 $settings_ver = file_exists($settings_path) ? filemtime($settings_path) : '1. 1.0';95 $core_ver = file_exists($core_path) ? filemtime($core_path) : '1.2.0'; 96 $frontend_ver = file_exists($frontend_path) ? filemtime($frontend_path) : '1.2.0'; 97 $admin_init_ver = file_exists($admin_init_path) ? filemtime($admin_init_path) : '1.2.0'; 98 $style_ver = file_exists($style_path) ? filemtime($style_path) : '1.2.0'; 99 $settings_ver = file_exists($settings_path) ? filemtime($settings_path) : '1.2.0'; 99 100 100 101 $payments_path = $base_dir . 'assets/js/amc-payments.js'; 101 $payments_ver = file_exists($payments_path) ? filemtime($payments_path) : '1. 1.0';102 $payments_ver = file_exists($payments_path) ? filemtime($payments_path) : '1.2.0'; 102 103 103 104 if (! wp_script_is($core_handle, 'registered')) { … … 261 262 262 263 $floating_path = plugin_dir_path(__FILE__) . 'assets/js/amc-floating.js'; 263 $floating_ver = file_exists($floating_path) ? filemtime($floating_path) : '1. 1.0';264 $floating_ver = file_exists($floating_path) ? filemtime($floating_path) : '1.2.0'; 264 265 265 266 wp_enqueue_script( -
ask-my-content/trunk/build/ask-my-content/block.json
r3486628 r3491287 3 3 "apiVersion": 3, 4 4 "name": "amc/ask-my-content", 5 "version": "1. 1.0",5 "version": "1.2.0", 6 6 "title": "Ask My Content", 7 7 "category": "widgets", -
ask-my-content/trunk/build/blocks-manifest.php
r3486628 r3491287 6 6 'apiVersion' => 3, 7 7 'name' => 'amc/ask-my-content', 8 'version' => '1. 1.0',8 'version' => '1.2.0', 9 9 'title' => 'Ask My Content', 10 10 'category' => 'widgets', -
ask-my-content/trunk/includes/utils.php
r3487385 r3491287 269 269 add_action('init', 'askmyco_bootstrap_session_cookies'); 270 270 271 function askmyco_get_product_content_parts(WP_Post $post): array272 {273 if ($post->post_type !== 'product' || ! function_exists('wc_get_product')) {274 return [];275 }276 277 $product = wc_get_product($post->ID);278 if (! $product) {279 return [];280 }281 282 $product_parts = [];283 284 $sku = trim((string) $product->get_sku());285 if ($sku !== '') {286 $product_parts[] = 'SKU: ' . $sku;287 }288 289 $price = trim((string) $product->get_price());290 if ($price !== '') {291 $product_parts[] = 'Price: ' . $price;292 }293 294 $weight = trim((string) $product->get_weight());295 if ($weight !== '') {296 $product_parts[] = 'Weight: ' . $weight;297 }298 299 $dimensions = array_filter([300 'Length: ' . trim((string) $product->get_length()),301 'Width: ' . trim((string) $product->get_width()),302 'Height: ' . trim((string) $product->get_height()),303 ], function ($value) {304 return substr($value, -2) !== ': ';305 });306 307 if ($dimensions !== []) {308 $product_parts[] = implode("\n", $dimensions);309 }310 311 $attributes = [];312 foreach ($product->get_attributes() as $attribute) {313 if (! is_object($attribute) || ! method_exists($attribute, 'get_name') || ! method_exists($attribute, 'get_options')) {314 continue;315 }316 317 $attribute_name = (string) $attribute->get_name();318 if ($attribute_name === '') {319 continue;320 }321 322 $label = function_exists('wc_attribute_label') ? wc_attribute_label($attribute_name) : $attribute_name;323 $values = [];324 325 if (method_exists($attribute, 'is_taxonomy') && $attribute->is_taxonomy() && function_exists('wc_get_product_terms')) {326 $values = wc_get_product_terms($post->ID, $attribute_name, ['fields' => 'names']);327 if (is_wp_error($values)) {328 $values = [];329 }330 } else {331 $values = $attribute->get_options();332 }333 334 $values = array_values(array_filter(array_map('strval', (array) $values), function ($value) {335 return trim($value) !== '';336 }));337 338 if ($values === []) {339 continue;340 }341 342 $attributes[] = $label . ': ' . implode(', ', $values);343 }344 345 if ($attributes !== []) {346 $product_parts[] = "Attributes:\n" . implode("\n", $attributes);347 }348 349 return $product_parts;350 }351 352 function askmyco_convert_post_to_rest_like_array(WP_Post $post): array353 {354 $content_parts = [];355 356 $content = askmyco_get_plain_text_from_html($post->post_content);357 if ($content !== '') {358 $content_parts[] = $content;359 }360 361 $excerpt = askmyco_get_plain_text_from_html($post->post_excerpt);362 if ($excerpt !== '') {363 $content_parts[] = 'Excerpt: ' . $excerpt;364 }365 366 $product_parts = askmyco_get_product_content_parts($post);367 if ($product_parts !== []) {368 $content_parts[] = implode("\n", $product_parts);369 }370 371 return [372 'id' => $post->ID,373 'date' => get_post_time('c', false, $post), // local time in ISO 8601374 'date_gmt' => get_post_time('c', true, $post),375 'guid' => [376 'rendered' => $post->guid,377 ],378 'modified' => get_post_modified_time('c', false, $post),379 'modified_gmt' => get_post_modified_time('c', true, $post),380 'slug' => $post->post_name,381 'status' => $post->post_status,382 'type' => $post->post_type,383 'link' => askmyco_get_canonical_permalink($post),384 'title' => [385 'rendered' => get_the_title($post),386 ],387 'content' => [388 'rendered' => implode("\n\n", array_filter($content_parts, function ($value) {389 return $value !== '';390 })),391 ],392 ];393 }394 395 /**396 * Build a permalink using the path from get_permalink($post) but forcing397 * scheme/host/port to match home_url(). This avoids cases where the host398 * resolves to an IP due to server headers or proxy configuration.399 */400 function askmyco_get_canonical_permalink($post): string401 {402 $permalink = get_permalink($post);403 $home = home_url('/');404 405 $p = wp_parse_url($permalink);406 $h = wp_parse_url($home);407 408 if ($p === false || $h === false || empty($h['host'])) {409 return $permalink;410 }411 $scheme = $h['scheme'] ?? 'https';412 $host = $h['host'];413 $port = isset($h['port']) ? ':' . $h['port'] : '';414 $path = $p['path'] ?? '/';415 $query = isset($p['query']) ? '?' . $p['query'] : '';416 $frag = isset($p['fragment']) ? '#' . $p['fragment'] : '';417 418 return $scheme . '://' . $host . $port . $path . $query . $frag;419 }420 421 /**422 * Build a canonical URL using home_url() for scheme/host/port and a provided423 * path/query/fragment. Useful for virtual resources like header/footer.424 */425 function askmyco_build_canonical_url(string $path = '/', array $query = [], ?string $fragment = null): string426 {427 $home = home_url('/');428 $h = wp_parse_url($home);429 if ($h === false || empty($h['host'])) {430 return $home;431 }432 $scheme = $h['scheme'] ?? 'https';433 $host = $h['host'];434 $port = isset($h['port']) ? ':' . $h['port'] : '';435 // Ensure path starts with '/'436 $path = '/' . ltrim($path, '/');437 $query_str = '';438 if (!empty($query)) {439 $query_str = '?' . http_build_query($query, '', '&');440 }441 $frag_str = $fragment !== null && $fragment !== '' ? '#' . ltrim($fragment, '#') : '';442 return $scheme . '://' . $host . $port . $path . $query_str . $frag_str;443 }444 445 271 /** 446 272 * Return the last indexed timestamp (UTC) for a given kind ('pages' or 'posts'). … … 505 331 } 506 332 507 // $html - The raw HTML‑containing block comment markup and paragraph tags508 function askmyco_get_plain_text_from_html($html): string509 {510 // 1. Remove any shortcode-derived tokens (just drops shortcodes)511 $no_shortcodes = strip_shortcodes($html);512 513 // 2. Strip ALL HTML, including scripts/styles, leaving visible text only514 $text = wp_strip_all_tags($no_shortcodes, true);515 516 // 3. Collapse whitespace (optional but helpful)517 $trimmed = preg_replace('/\s+/', ' ', $text);518 519 return trim($trimmed);520 }521 522 // $type: 'header' or 'footer'523 function askmyco_virtual_post_array_for_header_footer($type): ?array524 {525 if ($type !== 'header' && $type !== 'footer') {526 return null;527 }528 529 // Unique virtual post IDs530 $id = ($type === 'header') ? 1000001 : 1000002;531 532 // 1) Get rendered HTML for the header/footer533 $html = askmyco_render_header_footer_html($type);534 535 // 2) Extract visible text536 $text = askmyco_get_plain_text_from_html($html);537 538 // 3) Detect changes by hash so widget/customizer edits update modified date539 $hash_option = "askmyco_{$type}_content_hash";540 $old_hash = get_option($hash_option);541 $new_hash = md5($text);542 543 if ($new_hash !== $old_hash) {544 update_option($hash_option, $new_hash);545 $modified = current_time('c');546 } else {547 // Keep last known modified so you don’t thrash re-embeds548 $modified = get_option("askmyco_{$type}_modified") ?: current_time('c');549 }550 update_option("askmyco_{$type}_modified", $modified);551 552 // 4) Site info553 $site_uuid = get_option('askmyco_site_uuid');554 $url = home_url('/');555 $title = ucfirst($type);556 557 // 5) Return virtual post array558 return [559 'id' => $id,560 'date' => $modified,561 'date_gmt' => get_gmt_from_date($modified, 'c'),562 'guid' => ['rendered' => $url],563 'modified' => $modified,564 'modified_gmt' => get_gmt_from_date($modified, 'c'),565 'slug' => $type,566 'status' => 'publish',567 'type' => $type,568 'link' => askmyco_build_canonical_url('/', [], $type),569 'title' => ['rendered' => $title],570 'content' => ['rendered' => $text],571 'siteId' => $site_uuid,572 ];573 }574 575 function askmyco_render_header_footer_html($type)576 {577 if ($type !== 'header' && $type !== 'footer') return '';578 579 // Prefer child theme path first580 $classic = get_stylesheet_directory() . "/{$type}.php";581 if (file_exists($classic)) {582 ob_start();583 include $classic;584 return ob_get_clean();585 }586 // DB-stored template part (Site Editor edits)587 $theme = wp_get_theme()->get_stylesheet();588 if (function_exists('get_block_template')) {589 $template = get_block_template($theme . '//' . $type, 'wp_template_part');590 if ($template && !empty($template->content)) {591 $content = $template->content;592 return function_exists('do_blocks') ? do_blocks($content) : $content;593 }594 }595 596 $tpl = get_posts([597 'post_type' => 'wp_template_part',598 'name' => $type,599 'posts_per_page' => 3,600 'post_status' => 'publish',601 'no_found_rows' => true,602 'update_post_term_cache' => true,603 'update_post_meta_cache' => false,604 ]);605 foreach ($tpl as $candidate) {606 $terms = get_the_terms($candidate, 'wp_theme');607 if (is_wp_error($terms) || empty($terms)) {608 continue;609 }610 foreach ($terms as $term) {611 if ($term->slug === $theme) {612 $content = $candidate->post_content;613 return function_exists('do_blocks') ? do_blocks($content) : $content;614 }615 }616 }617 // Block template files fallback618 $theme_dir = get_stylesheet_directory();619 $cands = [620 "{$theme_dir}/parts/{$type}.html",621 "{$theme_dir}/templates/parts/{$type}.html",622 "{$theme_dir}/block-templates/parts/{$type}.html",623 "{$theme_dir}/block-templates/{$type}.html",624 "{$theme_dir}/templates/{$type}.html",625 ];626 foreach ($cands as $cand) {627 if (file_exists($cand)) {628 $raw = file_get_contents($cand);629 if (function_exists('do_blocks')) {630 return do_blocks($raw);631 }632 return $raw;633 }634 }635 // Last resort636 ob_start();637 if ($type === 'header') {638 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WP action639 do_action('wp_head');640 } else {641 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WP action642 do_action('wp_footer');643 }644 return ob_get_clean();645 }646 647 333 /** 648 334 * Build full API endpoint from stored URL and optional port. -
ask-my-content/trunk/readme.txt
r3486628 r3491287 5 5 Requires PHP: 7.4 6 6 Tested up to: 6.9 7 Stable tag: 1. 1.07 Stable tag: 1.2.0 8 8 License: GPL-2.0-or-later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 17 17 Unlike standard content search plugins that rely on keyword matching, Ask My Content uses semantic embeddings and retrieval-augmented generation to return direct answers in full sentences. 18 18 19 WordPress Block Editor is fully supported.20 However not all dynamic WordPress content creation themes and plugins are supported. See FAQ for details.19 Supports Gutenberg, Elementor, Avada, and WooCommerce content. 20 Some highly customized dynamic content may still require review. See FAQ for details. 21 21 22 22 **Key Features** … … 59 59 60 60 = Why my indexed token count is low = 61 Dynamic WordPress content creation themes and plugins (which may include Elementor Pro widgets) are currently not supported. 62 You will see persistently low indexed token count, even after indexing is finished, and indexing token count stays low after clicking the Refresh button. 63 In this case your chat questions will not be answered correctly ("No available content"). 64 For more info email to: info@pavelweb.com 61 Low indexed token count usually means the plugin did not find much readable page content to send for indexing. 62 63 This can happen if: 64 1. The page is mostly built from theme templates, banners, sliders, or other repeated global sections. 65 2. The important content is loaded by a plugin or builder widget that does not expose much text to WordPress. 66 3. Indexing finished before the latest content changes were re-run. 67 68 Ask My Content now supports common WordPress content builders much better, including **Gutenberg**, **Elementor**, **Avada**, and **WooCommerce** product content. 69 70 If your indexed token count still stays unexpectedly low after re-running indexing, see the next FAQ section: **What content is supported for indexing?** 71 72 If the chat still answers with "No available content," the page may rely heavily on content generated outside the main WordPress content flow. 73 74 = What content is supported for indexing? = 75 Ask My Content is designed to index the main readable text of your site content. 76 77 Currently supported: 78 * Standard WordPress pages and posts 79 * Gutenberg block content 80 * Elementor content in many common setups 81 * Avada content in many common setups 82 * WooCommerce product content, including product descriptions, short descriptions, price, SKU, weight, dimensions, and attributes 83 * Other public post types, if enabled in plugin settings 84 85 In general, the plugin reads the main post content the way WordPress normally renders it, using the standard content pipeline (`apply_filters('the_content', ...)`). 86 87 That means it usually works well with content that lives inside the main editor content area, but it may still miss text that comes only from: 88 * headers or footers 89 * sliders, popups, and announcement bars 90 * heavily customized builder widgets 91 * content injected from external sources or loaded later with JavaScript 92 93 If a page is important for search quality, open it in WordPress and make sure the main text is actually stored in the page, post, or product content itself before re-running indexing. 65 94 66 95 = Does this plugin use my OpenAI API key? = … … 167 196 == Changelog == 168 197 198 = 1.2.0 = 199 Improved content indexing support for Gutenberg, Elementor, Avada, WooCommerce, and other post types. 200 169 201 = 1.1.0 = 170 202 Added support for other content types (Products, Books, Events, etc.), including WooCommerce products. … … 217 249 == Upgrade Notice == 218 250 219 = 1.1.0 = 220 Added support for other content types (Products, Books, Events, etc.), including WooCommerce products. 221 222 = 1.0.0 = 223 Added `Payments` tab to `Ask My Content Settings`, where you can check your usage and pay the balance (currently with Stripe) 251 = 1.2.0 = 252 Improved content indexing support for Gutenberg, Elementor, Avada, WooCommerce, and other post types. 253 If your site was indexed with an older plugin version, email info@pavelweb.com for re-index instructions. -
ask-my-content/trunk/src/ask-my-content/block.json
r3486628 r3491287 3 3 "apiVersion": 3, 4 4 "name": "amc/ask-my-content", 5 "version": "1. 1.0",5 "version": "1.2.0", 6 6 "title": "Ask My Content", 7 7 "category": "widgets",
Note: See TracChangeset
for help on using the changeset viewer.