id, 'aiprocessia-assistant')) { return; } printf( '

%s %s %s

', esc_html__('AIPROCESSIA Assistant:', 'aiprocessia-assistant'), esc_html__('one step left — connect your account so the chat can start helping your visitors.', 'aiprocessia-assistant'), esc_url(admin_url('options-general.php?page=aiprocessia-assistant')), esc_html__('Finish setup', 'aiprocessia-assistant') ); } /* ------------------------------------------------------------ chat widget */ /** * Inject the AIPROCESSIA chat widget on the storefront. * Guideline 7 compliance: nothing is loaded from remote servers until the * site owner has explicitly configured their widget token. */ add_action('wp_enqueue_scripts', 'aipf_enqueue_widget'); function aipf_enqueue_widget() { $token = get_option('aipf_widget_token', ''); if ('' === $token) { return; } wp_enqueue_script( 'aipf-widget', 'https://aiprocessia.com/cuenta/documentos/widget/?t=' . rawurlencode($token), array(), AIPF_VERSION, array( 'strategy' => 'defer', 'in_footer' => true, ) ); } /* ------------------------------------------------------------ product feed */ add_action('rest_api_init', 'aipf_register_feed_route'); function aipf_register_feed_route() { register_rest_route( 'aipf/v1', '/feed', array( 'methods' => 'GET', 'callback' => 'aipf_feed_callback', // The endpoint is public by design (AIPROCESSIA servers fetch it); // access is controlled by the secret token checked in the callback. 'permission_callback' => '__return_true', ) ); } /** * Serve the catalog as a Google Shopping RSS feed (same contract as the * AIPROCESSIA PrestaShop module): title, description, link, image, price, * availability, units sold in custom_label_0 and the product data sheet * (attributes) as product_detail entries. */ function aipf_feed_callback(WP_REST_Request $request) { $secret = (string) get_option('aipf_feed_token', ''); $given = (string) $request->get_param('token'); if ('' === $secret || !hash_equals($secret, $given)) { return new WP_Error('aipf_forbidden', __('Invalid feed token.', 'aiprocessia-assistant'), array('status' => 403)); } if (!get_option('aipf_feed_enabled')) { return new WP_Error('aipf_disabled', __('The product feed is disabled in Settings.', 'aiprocessia-assistant'), array('status' => 404)); } if (!class_exists('WooCommerce')) { return new WP_Error('aipf_no_woo', __('WooCommerce is not active on this site.', 'aiprocessia-assistant'), array('status' => 404)); } $fresh = (bool) $request->get_param('fresh'); $xml = $fresh ? false : get_transient('aipf_feed_xml'); if (false === $xml) { $xml = aipf_build_feed_xml(); set_transient('aipf_feed_xml', $xml, AIPF_FEED_CACHE_TTL); } // Raw XML output: WP_REST_Response would JSON-encode the body. header('Content-Type: application/xml; charset=utf-8'); echo $xml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML built with esc_xml in aipf_build_feed_xml(). exit; } /** XML-escape helper (feed values). */ function aipf_x($value) { return htmlspecialchars((string) $value, ENT_XML1 | ENT_COMPAT, 'UTF-8'); } function aipf_build_feed_xml() { $items = array(); $page = 1; do { $products = wc_get_products( array( 'status' => 'publish', 'limit' => 200, 'page' => $page, 'orderby' => 'ID', 'order' => 'ASC', ) ); foreach ($products as $product) { if (count($items) >= AIPF_FEED_MAX_PRODUCTS) { break 2; } if (!$product->is_visible()) { continue; } $price = (float) wc_get_price_to_display($product); if ($price <= 0) { continue; } $availability = 'in stock'; if (!$product->is_in_stock()) { $availability = $product->backorders_allowed() ? 'backorder' : 'out of stock'; } // Prefer the long description (enriched product sheets live there). $desc = trim(wp_strip_all_tags((string) $product->get_description())); if ('' === $desc) { $desc = trim(wp_strip_all_tags((string) $product->get_short_description())); } $desc = preg_replace('/\s+/u', ' ', $desc); if (function_exists('mb_substr')) { $desc = mb_substr($desc, 0, 1200); } $it = "\n"; $it .= '' . (int) $product->get_id() . "\n"; $it .= '' . aipf_x($product->get_name()) . "\n"; $it .= '' . aipf_x($desc) . "\n"; $it .= '' . aipf_x($product->get_permalink()) . "\n"; $image_id = $product->get_image_id(); if ($image_id) { $img = wp_get_attachment_image_url($image_id, 'large'); if ($img) { $it .= '' . aipf_x($img) . "\n"; } } $cats = get_the_terms($product->get_id(), 'product_cat'); if (is_array($cats) && !empty($cats)) { $it .= '' . aipf_x($cats[0]->name) . "\n"; } $it .= '' . number_format($price, 2, '.', '') . ' ' . aipf_x(get_woocommerce_currency()) . "\n"; $it .= '' . aipf_x($availability) . "\n"; $it .= "new\n"; // Units sold — the AIPROCESSIA platform turns this into the // "best sellers" popularity signal. $sales = (int) $product->get_total_sales(); if ($sales > 0) { $it .= 'ventas:' . $sales . "\n"; } // Data sheet (product attributes) as standard product_detail pairs. foreach ($product->get_attributes() as $attribute) { if (!($attribute instanceof WC_Product_Attribute)) { continue; } $label = wc_attribute_label($attribute->get_name()); if ($attribute->is_taxonomy()) { $terms = wp_get_post_terms($product->get_id(), $attribute->get_name(), array('fields' => 'names')); $values = is_wp_error($terms) ? array() : $terms; } else { $values = $attribute->get_options(); } $value = implode(', ', array_map('strval', (array) $values)); if ('' === $label || '' === $value) { continue; } $it .= '' . aipf_x($label) . '' . aipf_x($value) . "\n"; } $items[] = $it . ''; } $page++; } while (!empty($products)); $shop = get_bloginfo('name'); return '' . "\n" . '' . "\n\n" . '' . aipf_x($shop) . "\n" . '' . aipf_x(home_url('/')) . "\n" . '' . aipf_x($shop) . " - product catalog for the AI assistant\n" . implode("\n", $items) . "\n\n\n"; } // Keep the cached feed reasonably current when the catalog changes. add_action('woocommerce_update_product', 'aipf_flush_feed_cache'); add_action('woocommerce_delete_product', 'aipf_flush_feed_cache'); function aipf_flush_feed_cache() { delete_transient('aipf_feed_xml'); } /* ---------------------------------------------------------------- settings */ add_action('admin_menu', 'aipf_admin_menu'); function aipf_admin_menu() { add_options_page( __('AIPROCESSIA Assistant', 'aiprocessia-assistant'), __('AIPROCESSIA Assistant', 'aiprocessia-assistant'), 'manage_options', 'aiprocessia-assistant', 'aipf_settings_page' ); } add_action('admin_init', 'aipf_register_settings'); function aipf_register_settings() { register_setting( 'aipf_settings', 'aipf_widget_token', array( 'type' => 'string', 'sanitize_callback' => 'aipf_sanitize_token', 'default' => '', ) ); register_setting( 'aipf_settings', 'aipf_feed_enabled', array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'default' => false, ) ); } function aipf_sanitize_token($value) { return substr(preg_replace('/[^a-z0-9]/', '', strtolower((string) $value)), 0, 64); } function aipf_settings_page() { if (!current_user_can('manage_options')) { return; } $widget_token = get_option('aipf_widget_token', ''); $feed_enabled = (bool) get_option('aipf_feed_enabled'); $feed_url = rest_url('aipf/v1/feed'); $feed_url = add_query_arg('token', rawurlencode((string) get_option('aipf_feed_token', '')), $feed_url); $has_woo = class_exists('WooCommerce'); $locale_es = (0 === strpos(get_locale(), 'es')); // The site domain travels in the signup link so the AIPROCESSIA panel can // pre-fill the widget's authorized domains and the catalog connection. $signup_url = add_query_arg( array( 'utm_source' => 'wordpress-plugin', 'utm_medium' => 'settings', 'site' => rawurlencode(home_url('/')), ), $locale_es ? 'https://aiprocessia.com/chatbot-tienda-online/' : 'https://aiprocessia.com/en/ai-shopping-assistant/' ); ?>

→

' . esc_html__('Settings', 'aiprocessia-assistant') . '' ); return $links; }