/** * Related Products from in-post links — Elementor-aware & recursion-safe * - کار با پست‌های ساخته‌شده در Elementor و ادیتور کلاسیک * - شناسایی لینک‌های داخلی محصولات (لینک مطلق/نسبی، نرمال‌سازی www) * - ذخیرهٔ ID محصولات در متای پست (کش با هش) * - شورتکد: [related_products_from_links] (تیتر داخلی پیش‌فرض غیرفعال است) * - ساخت/بازسازی کش: روی save_post، روی ذخیرهٔ المنتور، lazy در اولین رندر، و دستی با ?rp_rebuild=1 (ادمین) */ if ( ! defined( 'PETRONAFT_RP_META_KEY' ) ) { define( 'PETRONAFT_RP_META_KEY', '_petronaft_related_product_ids_v1' ); define( 'PETRONAFT_RP_META_HASH', '_petronaft_related_products_hash_v1' ); } /** پرچم جلوگیری از بازگشت بازگشتی هنگام اسکن (برای زمانی که شورتکد داخل المنتور اجرا می‌شود) */ $GLOBALS['petro_rp_scanning'] = false; /** نرمال‌سازی URL داخلی (پشتیبانی از لینک نسبی؛ نادیده‌گیری www.) */ if ( ! function_exists( 'petronaft_rp_to_internal_abs_url' ) ) { function petronaft_rp_to_internal_abs_url( $href ) { if ( ! is_string( $href ) || $href === '' ) return ''; if ( $href[0] === '#' ) return ''; $site_host = wp_parse_url( home_url(), PHP_URL_HOST ); $site_host_norm = preg_replace( '/^www\./i', '', (string) $site_host ); $parsed = wp_parse_url( $href ); // لینک نسبی مثل /product/... if ( empty( $parsed['host'] ) ) { if ( isset( $href[0] ) && $href[0] === '/' ) { return esc_url_raw( home_url( $href ) ); } return ''; } // لینک مطلق؛ فقط اگر دامنه یکی است (با نادیده‌گیری www) $host_norm = preg_replace( '/^www\./i', '', (string) $parsed['host'] ); if ( strtolower( $host_norm ) !== strtolower( $site_host_norm ) ) { return ''; } return esc_url_raw( $href ); } } /** دریافت HTML نهایی برای اسکن (المنتور/کلاسیک) — بدون حلقهٔ بی‌نهایت */ if ( ! function_exists( 'petronaft_rp_get_rendered_html' ) ) { function petronaft_rp_get_rendered_html( $post_id ) { $html = ''; // اولویت با خروجی المنتور if ( class_exists( '\Elementor\Plugin' ) ) { try { $html = \Elementor\Plugin::$instance->frontend->get_builder_content_for_display( $post_id, false ); } catch ( \Throwable $e ) { /* noop */ } } // اگر المنتور چیزی نداد، از محتوای کلاسیک استفاده کن (بدون افزودن باکس خودمان) if ( '' === trim( $html ) ) { $raw = (string) get_post_field( 'post_content', $post_id ); $html = $raw; // عمداً apply_filters('the_content', ...) نمی‌زنیم تا شورتکد خودمان اجرا نشود } return is_string( $html ) ? $html : ''; } } /** اسکن HTML و استخراج ID محصولات منتشرشده از لینک‌های داخلی */ if ( ! function_exists( 'petronaft_rp_extract_product_ids_from_html' ) ) { function petronaft_rp_extract_product_ids_from_html( $html ) { $ids = array(); if ( ! is_string( $html ) || $html === '' ) return $ids; if ( ! class_exists( 'DOMDocument' ) ) return $ids; $prev = libxml_use_internal_errors( true ); $dom = new DOMDocument(); $dom->loadHTML( '' . $html ); libxml_use_internal_errors( $prev ); $anchors = $dom->getElementsByTagName( 'a' ); foreach ( $anchors as $a ) { $href = $a->getAttribute( 'href' ); if ( empty( $href ) ) continue; $abs = petronaft_rp_to_internal_abs_url( $href ); if ( $abs === '' ) continue; // حذف پارامترهای ردیابی $clean = remove_query_arg( array( 'utm_source','utm_medium','utm_campaign','utm_term','utm_content','gclid','fbclid','ref' ), $abs ); $pid = url_to_postid( $clean ); if ( ! $pid ) continue; if ( get_post_type( $pid ) === 'product' && get_post_status( $pid ) === 'publish' ) { $ids[] = (int) $pid; } } return array_values( array_unique( $ids ) ); } } /** محاسبه و کش‌کردن IDها در متای پست (هش‌شده با HTML رندرشده + modified_gmt) */ if ( ! function_exists( 'petronaft_rp_compute_and_save' ) ) { function petronaft_rp_compute_and_save( $post_id, $force = false ) { $post = get_post( $post_id ); if ( ! $post || $post->post_type !== 'post' ) return false; // جلوگیری از حلقه: هنگام اسکن، شورتکد خروجی خالی می‌دهد $GLOBALS['petro_rp_scanning'] = true; $html = petronaft_rp_get_rendered_html( $post_id ); $GLOBALS['petro_rp_scanning'] = false; $hash = md5( $html . '|' . (string) $post->post_modified_gmt ); if ( ! $force ) { $stored_hash = get_post_meta( $post_id, PETRONAFT_RP_META_HASH, true ); if ( $stored_hash && $stored_hash === $hash ) return false; // کش به‌روز است } $ids = petronaft_rp_extract_product_ids_from_html( $html ); update_post_meta( $post_id, PETRONAFT_RP_META_KEY, $ids ); update_post_meta( $post_id, PETRONAFT_RP_META_HASH, $hash ); return true; } } /** تازه‌سازی کش هنگام ذخیرهٔ پست */ if ( ! function_exists( 'petronaft_rp_on_save_post' ) ) { function petronaft_rp_on_save_post( $post_id, $post, $update ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; if ( wp_is_post_revision( $post_id ) ) return; if ( $post->post_type !== 'post' ) return; if ( ! current_user_can( 'edit_post', $post_id ) ) return; petronaft_rp_compute_and_save( $post_id, false ); } add_action( 'save_post', 'petronaft_rp_on_save_post', 10, 3 ); } /** بازسازی بعد از ذخیره در ویرایشگر Elementor */ if ( ! function_exists( 'petronaft_rp_on_elementor_after_save' ) ) { function petronaft_rp_on_elementor_after_save( $post_id, $editor_data ) { $post = get_post( $post_id ); if ( $post && $post->post_type === 'post' ) { petronaft_rp_compute_and_save( $post_id, true ); } } add_action( 'elementor/editor/after_save', 'petronaft_rp_on_elementor_after_save', 10, 2 ); } /** دریافت IDهای کش‌شده؛ در صورت خالی‌بودن یک‌بار محاسبهٔ تنبل انجام می‌شود */ if ( ! function_exists( 'petronaft_rp_get_ids' ) ) { function petronaft_rp_get_ids( $post_id ) { $ids = get_post_meta( $post_id, PETRONAFT_RP_META_KEY, true ); if ( ! is_array( $ids ) ) $ids = array(); if ( empty( $ids ) ) { if ( petronaft_rp_compute_and_save( $post_id, false ) ) { $ids = get_post_meta( $post_id, PETRONAFT_RP_META_KEY, true ); if ( ! is_array( $ids ) ) $ids = array(); } } return $ids; } } /** رندر HTML برای استفاده در شورتکد */ if ( ! function_exists( 'petronaft_rp_render_html' ) ) { function petronaft_rp_render_html( $post_id, $args = array() ) { // چون تیتر را با Text Editor گذاشته‌ای، پیش‌فرض title=0 است $defaults = array( 'title' => '0', // "1" برای نمایش تیتر داخلی 'limit' => '12', 'columns' => '4', 'heading' => 'Explore Our Related Products', ); $args = wp_parse_args( $args, $defaults ); $ids = petronaft_rp_get_ids( $post_id ); if ( empty( $ids ) ) return ''; // کش HTML بر اساس هش متا + IDs + تنظیمات رندر $meta_hash = (string) get_post_meta( $post_id, PETRONAFT_RP_META_HASH, true ); $cache_key = 'petro_rp_html_' . $post_id . '_' . substr( md5( $meta_hash . '|' . implode( ',', $ids ) . '|' . json_encode( $args ) ), 0, 12 ); $cached_html = get_transient( $cache_key ); if ( is_string( $cached_html ) && $cached_html !== '' ) { return $cached_html; } $limit = max( 1, (int) $args['limit'] ); $columns = max( 1, (int) $args['columns'] ); $ids_cut = array_slice( $ids, 0, $limit ); $ids_attr = implode( ',', array_map( 'intval', $ids_cut ) ); $out = ''; set_transient( $cache_key, $out, 12 * HOUR_IN_SECONDS ); return $out; } } /** شورتکد اصلی */ if ( ! function_exists( 'petronaft_rp_shortcode_cb' ) ) { function petronaft_rp_shortcode_cb( $atts = array() ) { // اگر در حالت اسکن هستیم (برای جلوگیری از لوپ)، خروجی خالی بده if ( ! empty( $GLOBALS['petro_rp_scanning'] ) ) return ''; $post_id = get_the_ID(); if ( ! $post_id ) return ''; $atts = is_array( $atts ) ? $atts : array(); return petronaft_rp_render_html( $post_id, $atts ); } add_shortcode( 'related_products_from_links', 'petronaft_rp_shortcode_cb' ); } /** بازسازی دستی از طریق پارامتر (فقط ادمین): ?rp_rebuild=1 */ if ( ! function_exists( 'petronaft_rp_maybe_rebuild_from_query' ) ) { function petronaft_rp_maybe_rebuild_from_query() { if ( is_admin() || ! is_singular( 'post' ) || empty( $_GET['rp_rebuild'] ) ) return; if ( ! current_user_can( 'manage_options' ) ) return; $post_id = get_queried_object_id(); if ( $post_id ) { petronaft_rp_compute_and_save( $post_id, true ); } } add_action( 'template_redirect', 'petronaft_rp_maybe_rebuild_from_query', 0 ); }