id, 'aiprocessia-assistant')) {
return;
}
printf(
'
',
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;
}