WP Manifestindependent plugin directory
manifest / ecommerce / ts-comments-overview

TS Comments Overview

نمایش خلاصه‌ی نظرات کاربران (تحلیل سرویس mytsapp.ir) در بخش نظرات صفحه محصول تهران‌اسپیکر، با حالت‌های خاموش/خلاصه/خلاصه + نقاط قوت و ضعف، آستانه‌ی کیفیت برای پنهان کردن خلاصه‌ی محصول‌های ضعیف، و نشان‌گذاری ورودی «نظرات» در نوار بخش‌های صفحه.

by Keyvan Havestin · github.com/keyvansolha/ts-comments-overview · website

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/keyvansolha/ts-comments-overview/archive/refs/heads/main.zip

بخش «آنچه کاربران درباره این محصول می‌گویند» را بالای فهرست نظرات صفحه‌ی محصول تهران‌اسپیکر نمایش می‌دهد. داده از سرویس تحلیل نظرات (mytsapp.ir) می‌آید که نظرات کاربران در چند سایت مرجع را می‌خواند و خلاصه‌ی آن‌ها را برمی‌گرداند.

English: Displays an automatically generated "what users say" summary of a product's external user reviews at the top of the product comment section, sourced from the mytsapp.ir comments-analysis service. Off by default, three display modes, token read from wp-config.php, all styling built from the Amazing theme's semantic design tokens (light + dark).


چرا افزونه و نه قالب؟

  • حالت نمایش با یک گزینه در پیشخوان روشن/خاموش می‌شود (بدون دست زدن به کد قالب).
  • توکن سرویس در wp-config.php می‌ماند، نه در دیتابیس.
  • با غیرفعال کردن افزونه، قالب دقیقاً به حالت قبل برمی‌گردد: قالب فقط یک هوک صدا می‌زند و هیچ وابستگی مستقیمی به کلاس‌های افزونه ندارد.

نصب و راه‌اندازی

  1. پوشه‌ی ts-comments-overview را در wp-content/plugins/ قرار دهید و افزونه را از پیشخوان فعال کنید.
  2. توکن سرویس را در wp-config.php بگذارید:
// توکن سرویس تحلیل نظرات (افزونه ts-comments-overview) — هدر X-API-Token.
define( 'TS_COMMENTS_OVERVIEW_API_TOKEN', 'توکن-واقعی' );
  1. به تنظیمات ← خلاصه نظرات محصولات بروید و حالت نمایش را انتخاب کنید.
  2. چون صفحات محصول با WP Rocket کش می‌شوند، بعد از روشن کردن حالت (و هر بار تغییر حالت) کش صفحات را پاک کنید تا صفحات محصول دوباره با بخش خلاصه ساخته شوند. داده‌ی سرویس هم به‌صورت ترنزینت کش می‌شود؛ پاک‌سازی آن از همان صفحه‌ی تنظیمات انجام می‌شود.

تا وقتی توکن تنظیم نشده باشد، بخش خلاصه در سایت نمایش داده نمی‌شود و در پیشخوان یک هشدار می‌بینید. این کار عمدی است تا با توکن جعلی، درخواست بی‌فایده به سرویس فرستاده نشود.


حالت‌های نمایش

گزینه رفتار
خاموش هیچ بخشی رندر نمی‌شود؛ هیچ درخواستی به سرویس زده نمی‌شود.
روشن (خلاصه) فقط خلاصه‌ی نظرات، آمار، نوار احساسات و موضوع‌ها.
روشن + نقاط قوت و ضعف همان خلاصه به‌همراه دو پنل «نقاط قوت» و «نقاط ضعف» از فیلدهای strengths و weaknesses.

اگر برای محصولی strengths/weaknesses خالی باشد، همان حالت سوم هم فقط خلاصه را نشان می‌دهد (پنل خالی ساخته نمی‌شود). مقدار پیش‌فرض افزونه «خاموش» است.


آستانه‌ی کیفیت (نمایش ندادن خلاصه روی محصول ضعیف)

اگر محصولی نظرهای ضعیفی دارد، جمع‌بندی منفی آن به کاربر نشان داده نمی‌شود. تنظیم min_recommend در «تنظیمات ← خلاصه نظرات محصولات» تعیین می‌کند حداقل چند درصد پیشنهاد لازم است:

مقدار رفتار
50 (پیش‌فرض) محصولی که کمتر از ۵۰٪ کاربران آن را پیشنهاد کرده‌اند، خلاصه نشان نمی‌دهد.
هر عدد ۱ تا ۱۰۰ همان قاعده با سخت‌گیری دلخواه.
0 قاعده خاموش؛ خلاصه برای همه‌ی محصولات نمایش داده می‌شود.

نکته‌های پیاده‌سازی:

  • مبنای تصمیم اول recommendPercentage سرویس است (همان عددی که در کارت بالای خلاصه نوشته می‌شود) و اگر سرویس آن را نداده باشد، سهم نظرهای مثبت از مجموع احساسات.
  • اگر سرویس هیچ عددی برای تصمیم ندهد، خلاصه نمایش داده می‌شود؛ پنهان کردن پیش‌فرض نیست.
  • مقدار نامعتبر (خالی، غیرعددی، خارج از ۰..۱۰۰) به پیش‌فرض برمی‌گردد و باعث پنهان شدن سراسری نمی‌شود. ارقام فارسی هم پذیرفته می‌شوند.
  • ذخیره‌ی فقط حالت نمایش، آستانه را پاک نمی‌کند.
  • ابزار «بررسی زنده‌ی سرویس» برای هر محصول می‌گوید با تنظیمات فعلی خلاصه نمایش داده می‌شود یا نه و چرا.

اتصال به سرویس

GET https://mytsapp.ir/api/external/comments-analysis/?woocommerce_id=<product-id>
Header: X-API-Token: <token>
  • شناسه‌ی ارسالی، شناسه‌ی محصول والد است ($product->get_id())، چون خلاصه در سطح محصول معنا دارد.
  • پاسخ واقعی سرویس به این شکل است و تحلیل داخل کلید analysis می‌آید:
{
  "woocommerce_id": 238607,
  "analysis": {
    "topics": [{ "label": "کیفیت صدا", "count": 10, "direction": "up" }],
    "summary": "…",
    "sentiment": { "positive": 80, "neutral": 10, "negative": 10 },
    "strengths": [{ "text": "کیفیت صدا", "count": 10 }],
    "weaknesses": [{ "text": "تنظیمات پیچیده", "count": 2 }],
    "providerName": "همه فروشگاه‌ها",
    "providerBreakdown": [{ "providerName": "TECHNOLIFE", "count": 20 }],
    "overallRating": 4.35,
    "totalComments": 20,
    "recommendPercentage": 85,
    "coverage": "full",
    "is_derived": false,
    "stale": false,
    "updatedAt": "2026-09-22T08:55:38.917162+00:00"
  }
}
  • کلیدهای camelCase و snake_case هر دو پذیرفته می‌شوند (overallRating و overall_rating، isDerived و is_derived، sourceCommentCount و sourcecommentcount و …) و نام پوشش هم مهم نیست: analysis، data، result، results یا خود رکورد بدون پوشش.

  • topics هم شکل ساختاریافته (label/count) و هم رشته‌ی ساده را می‌پذیرد؛ strengths/weaknesses هم text/count و هم رشته‌ی ساده.

  • providerName / providerBreakdown خوانده و نرمال‌سازی می‌شوند، اما در سایت نمایش داده نمی‌شوند: نه نام سایت‌های مرجع و نه تعدادشان. این داده‌ها فقط در ابزار بررسی پیشخوان دیده می‌شوند (برای اطمینان از درستی اتصال).

  • فیلد direction موضوع‌ها در ساختار داخلی نگه داشته می‌شود ولی در رابط کاربری نمایش داده نمی‌شود؛ چون معنای دقیق آن (روند یا قطبیت) تأیید نشده و نمایش حدسی می‌تواند گمراه‌کننده باشد. اگر تأیید شد، افزودنش یک تغییر کوچک در قالب است.

  • خطاها بر اساس detail سرویس به پیام فارسی تبدیل می‌شوند: ۴۰۱ «توکن نامعتبر»، ۴۰۴ «برای این محصول خلاصه‌ای ثبت نشده»، ۴۲۹ «تعداد درخواست‌ها زیاد بود».

کش

هر تحلیل تا ۶ ساعت در ترنزینت نگه داشته می‌شود (یک درخواست به‌ازای هر محصول در هر بازه). خطاها هم ۱۵ دقیقه کش می‌شوند تا سرویس خواب‌رفته، رندر صفحه‌ی محصول را کند نکند. پاک‌سازی کش از همان صفحه‌ی تنظیمات (یک محصول یا همه) انجام می‌شود.

نکته‌ی عددی: مقادیر sentiment

برچسب «بر پایه‌ی N نظر تحلیل‌شده» و کارت «نظر از سایت‌های دیگر» هر دو از totalComments می‌آیند. سرویس ممکن است sentiment را درصدی بفرستد (مثل ۸۰/۱۰/۱۰ که جمعشان ۱۰۰ است)؛ در آن حالت چاپ جمع آن‌ها به‌عنوان «تعداد نظرات» نادرست می‌شد. نسبت‌های نوار احساسات در هر دو حالت (شمارش واقعی یا درصد) درست محاسبه می‌شوند، چون از نسبت همان سه مقدار به دست می‌آیند.

اگر پاسخ سرویس هیچ محتوایی نداشته باشد (نه خلاصه، نه قوت/ضعف، نه احساسات)، بخش خلاصه بی‌صدا نمایش داده نمی‌شود؛ هیچ پیام خطایی روی سایت چاپ نمی‌شود.


یکپارچگی با قالب (هوک)

قالب Amazing در دو فایل، پیش از فهرست نظرات، این هوک را صدا می‌زند:

// lib/Product/template/desktop/comments.php
// lib/Product/template/mobile/panels/comments.php
do_action( 'wbs_product_comments_overview', $product );

نام هوک با ثابت TS_COMMENTS_OVERVIEW_HOOK (پیش از بارگذاری افزونه) قابل بازنویسی است. صفحه‌ی تنظیمات بررسی می‌کند که قالب فعال این هوک را صدا می‌زند یا نه و نتیجه را نشان می‌دهد.


UI/UX

  • همه‌ی رنگ‌ها از توکن‌های معنایی قالب (assets/css/theme-system.css) می‌آیند: --surface-*, --text-*, --border-*, --theme-accent, --state-*. به همین دلیل دارک و لایت خودکار درست است و هیچ قاعده‌ی جداگانه‌ای برای body.dark نوشته نشده. تنها استثنا، حلقه‌ی رنگین‌کمانی ورودی «نظرات» است که با نشانه‌های ts-co:rainbow-ring:start/end در همان فایل جدا شده و تست نگهبان مطمئن می‌شود بیرون آن محدوده هیچ رنگ ثابتی وجود ندارد.
  • استایل با وابستگی به amazing-theme-system صف‌بندی می‌شود تا همیشه بعد از لایه‌ی توکن‌ها بارگذاری شود، و فقط در صفحه‌ی محصول و فقط وقتی بخش فعال است.
  • ساختار داخل خود پنل نظرات (.comments-panel) قرار می‌گیرد، بین هدر بخش نظرات و فهرست نظرات؛ بنابراین همان چیدمان و فاصله‌های سایت را ادامه می‌دهد.
  • آیکون‌ها از فونت آیکون خود قالب‌اند (icon-chat, icon-star-empty, icon-like, icon-dislike, icon-categories, icon-info, icon-history).
  • RTL کامل با ویژگی‌های منطقی CSS (padding-inline-start, margin-inline-start). موبایل: آمار و پنل‌های قوت/ضعف تک‌ستونه می‌شوند.
  • نوار احساسات با flex-grow بر پایه‌ی شمارش خام تقسیم می‌شود تا جمع سهم‌ها همیشه دقیقاً ۱۰۰٪ باشد و ته نوار شکاف خالی نماند.

حلقه‌ی رنگین‌کمانی روی ورودی «نظرات» در نوار بخش‌ها

نوار بخش‌های صفحه محصول (تب‌های «مشخصات / نقد و بررسی / متداول / مشابه / نظرات») چسبان است. وقتی برای محصول خلاصه‌ای رندر شده باشد، ورودی نظرات یک حلقه‌ی رنگین‌کمانی متحرک می‌گیرد تا کاربر بفهمد پشت آن تب چیز تازه‌ای هست.

  • دسکتاپ: تب data-tab="productComments" در همان نوار چسبان.

  • موبایل: نوار چسبان موبایل هم ورودی «نظرات» دارد؛ ولی چون پنل نظرات در موبایل داخل گروه تب‌ها نیست و پایین‌تر در صفحه رندر می‌شود، این ورودی یک لینک به #productComments است نه تب (data-tab ندارد تا موتور تب‌های قالب آن را به‌عنوان تب انتخاب نکند). نرم اسکرول این لینک در رانتایم خود قالب (product-detail-ui.js) انجام می‌شود و بدون جاوااسکریپت هم لینک معمولی کار می‌کند.

  • مالکیت: همه‌ی این‌ها کار قالب است — ورودی نوار، چیدمان چهارستونه‌ی موبایل، کلاس has-comments-summary، و استایل حلقه در مسیر SCSS قالب (lib/Product/assets/scss/_comments-ring.scss → scss/desktop|mobile/product.css). این افزونه فقط یک پاسخ بله/خیر می‌دهد:

    function_exists( 'ts_comments_overview_shows_summary' ) && ts_comments_overview_shows_summary()

    قالب با همین تابع (و با محافظ function_exists تا بدون افزونه هم کار کند) تصمیم می‌گیرد کلاس را بگذارد یا نه. چون این تصمیم از همان داده‌ی رندر خلاصه می‌آید، نوار و بخش خلاصه هرگز ناهماهنگ نمی‌شوند: روی محصولی که خلاصه ندارد یا آستانه‌ی کیفیت جلوی آن را گرفته، حلقه هم نمی‌آید.

  • این افزونه هیچ اسکریپت و هیچ استایلی برای نوار تزریق نمی‌کند؛ بنابراین استایل افزونه کاملاً روی توکن‌های طراحی قالب می‌ماند و هیچ رنگ ثابتی ندارد.

  • حلقه یک گرادیان مخروطی چرخان است که هر ۷ ثانیه یک دور کامل می‌زند: دو لایه‌ی پس‌زمینه روی خود عنصر، یکی پرکننده‌ی داخل تب (رنگ سطح نوار) و یکی گرادیان رنگین‌کمان، با background-clip: padding-box, border-box و border شفاف؛ پس فقط حلقه دیده می‌شود و گوشه‌های گرد تب هم درست می‌ماند. زاویه با @property --ts-co-ring-angle ثبت شده تا انیمیشن پیوسته و نرم باشد.

  • چرا شبه‌عنصر نه؟ قالب در product-detail-ui.css تمام شبه‌عنصرهای تب (::before/::after) را با display:none !important خاموش کرده است.

  • حلقه در حالت فوکوس هم می‌ماند؛ حلقه‌ی فوکوس خود قالب (outline) بیرون آن کشیده می‌شود و هر دو با هم دیده می‌شوند.

  • اعلان‌های !important قالب برای background/border آیتم (از جمله حالت فعال) با یک کلاس اضافه در انتخابگر خنثی شده‌اند تا نتیجه به ترتیب بارگذاری فایل‌های CSS وابسته نباشد.

  • prefers-reduced-motion: reduce چرخش را خاموش می‌کند (حلقه ثابت می‌ماند).

سه تصمیم آگاهانه در متن و چیدمان

۱. عنوان صریح است: «آنچه کاربران در اینترنت درباره این محصول می‌گویند» تا از همان تیتر روشن باشد این نظرات از سایت‌های دیگر است.

۲. هشدار «این نظرات از فروشگاه دیگری است» برجسته و پیش از متن خلاصه می‌آید (نه به‌عنوان پانویس ریز در انتها). دلیل: سرویس ممکن است شکایت‌های مربوط به تجربه‌ی خرید در فروشگاه‌های دیگر را در خلاصه بیاورد — مثل بسته‌بندی نامناسب یا دریافت کالای آسیب‌دیده — و کاربر باید پیش از خواندن آن جملات بداند که آن ایرادها به خرید از تهران‌اسپیکر مربوط نیست. متن هشدار دو بخش دارد: یک جمله‌ی پررنگ (منبع نظرات از ما نیست) و یک توضیح (ایرادهای بسته‌بندی/کالای آسیب‌دیده/کالای مرجوعی مربوط به همان فروشگاه‌هاست). رنگ آن از توکن --state-info می‌آید.

۳. زمان آخرین به‌روزرسانی تحلیل و نشان کهنگی داده نمایش داده نمی‌شود. داده‌ی updatedAt و stale خوانده و در ساختار داخلی (و ابزار پیشخوان) موجود است، ولی کارت هیچ تاریخ/ساعتی و هیچ نشان کهنگی‌ای نشان نمی‌دهد؛ برای بازدیدکننده معنای روشنی نداشت.

۴. روی محصول با نظرهای ضعیف، خلاصه پنهان می‌شود (آستانه‌ی کیفیت). بخش نظرات خود محصول دست‌نخورده می‌ماند؛ فقط جمع‌بندی خودکار نمایش داده نمی‌شود.

سلب مسئولیت پایانی هم ذکر می‌شود که این خلاصه خودکار است و نظر تهران‌اسپیکر نیست.


امنیت

  • توکن فقط از wp-config.php خوانده می‌شود؛ نه در آپشن‌ها ذخیره می‌شود، نه در کش، نه در HTML پیشخوان چاپ می‌شود. فقط «تنظیم شده / نشده + تعداد کاراکتر» گزارش می‌شود.
  • درخواست با wp_remote_get، مهلت ۸ ثانیه، سقف ۵۱۲KB و بدون دنبال کردن ریدایرکت.
  • نشانی سرویس فقط https پذیرفته می‌شود.
  • همه‌ی رشته‌های سرویس با wp_strip_all_tags تمیز و با esc_html چاپ می‌شوند. خلاصه به‌صورت متن ساده با قالب‌بندی حداقلی و امن رندر می‌شود (escape پیش از ساخت تگ؛ فقط فهرست و **پررنگ**). هیچ HTML از سرویس اجرا نمی‌شود.
  • اعداد در بازه‌ی منطقی خود محدود می‌شوند (امتیاز ۰..۵، درصد ۰..۱۰۰، شمارش‌ها ≥ ۰). ارقام فارسی/عربی و پسوند ٪ هم خوانده می‌شوند.

تنظیمات از wp-config.php (همه اختیاری)

define( 'TS_COMMENTS_OVERVIEW_API_TOKEN', '...' );            // الزامی برای نمایش
define( 'TS_COMMENTS_OVERVIEW_API_ENDPOINT', 'https://...' ); // پیش‌فرض: mytsapp.ir
define( 'TS_COMMENTS_OVERVIEW_HOOK', 'wbs_product_comments_overview' );
define( 'TS_COMMENTS_OVERVIEW_CACHE_TTL', 21600 );            // ۶ ساعت
define( 'TS_COMMENTS_OVERVIEW_ERROR_TTL', 900 );              // ۱۵ دقیقه

فیلترها: ts_comments_overview_cache_ttl, ts_comments_overview_error_ttl, ts_comments_overview_api_endpoint, ts_comments_overview_api_token (فقط تست).


تست

# تست‌های PHP افزونه (بدون نیاز به نصب وردپرس؛ توابع وردپرس شبیه‌سازی می‌شوند)
php tests/run.php

# تست نگهبان سمت قالب: توکن‌های طراحی + هوک + escape
cd ../../themes/amazing && node --test tests/comments-overview-integration.test.mjs

تست‌های PHP واقعاً کد افزونه را اجرا می‌کنند: سه حالت نمایش، کش، کش خطا، خطاهای HTTP، پیلود مخرب (XSS)، محدودسازی اعداد، سه گزینه‌ی پیشخوان، عدم افشای توکن، آستانه‌ی کیفیت (پاک‌سازی مقدار، تصمیم نمایش، مبنای جایگزین) و نشان‌گذاری ورودی نظرات. مجموع: ۳۳۶ ادعا در ۶ فایل.

تست‌های قالب: هوک، نبود وابستگی سخت به افزونه، قاعده‌ی توکن‌های طراحی (به‌جز ناحیه‌ی نشانه‌گذاری‌شده‌ی حلقه)، وجود ورودی‌های نظرات که اسکریپت هدف می‌گیرد، و چسبیدن نوار «افزودن به سبد» موبایل به فوتر چسبان.


ساختار فایل‌ها

ts-comments-overview.php                            هدر، ثابت‌ها، راه‌اندازی
includes/class-ts-comments-overview-settings.php    حالت نمایش، توکن، نشانی، TTL
includes/class-ts-comments-overview-payload.php     نرمال‌سازی و پاکسازی پاسخ سرویس
includes/class-ts-comments-overview-api.php         کلاینت HTTP + کش
includes/class-ts-comments-overview-render.php      رندر بخش و هوک قالب
includes/class-ts-comments-overview-admin.php       صفحه‌ی تنظیمات، بررسی زنده، پاک‌سازی کش
templates/section.php                               مارک‌آپ بخش
assets/css/comments-overview.css                    استایل کارت خلاصه، فقط با توکن‌های قالب
tests/                                              بستر تست PHP بدون وردپرس

ابزار بررسی زنده در پیشخوان

در صفحه‌ی تنظیمات، با دادن شناسه‌ی یک محصول می‌توانید پاسخ واقعی سرویس را ببینید: هم داده‌ی نرمال‌شده‌ای که در سایت چاپ می‌شود، هم JSON خام. این درخواست کش نمی‌شود و برای تطبیق ساختار پیلود سرویس با نمایش سایت است. زیر جدول داده، وضعیت آستانه‌ی کیفیت هم گزارش می‌شود: درصد پیشنهاد محصول چند است و آیا خلاصه نمایش داده می‌شود.