---
title: "Changelog iga-core"
url: https://phatdev.net/docs/iga/changelog
lang: vi
updated: 2026-10-20
alternate:
  en: https://phatdev.net/en/docs/iga/changelog
markdown: https://phatdev.net/docs/iga/changelog.md
---

# Changelog iga-core

Thuộc bộ tài liệu: [IGA (Infinity Gems)](https://phatdev.net/docs/iga.md)

> Lịch sử thay đổi plugin iga-core.

## 0.5.33 - 2026-10-17
- `/chon-menh/`: khi mọi loại đá đã chọn, trang mở ra hiện đủ 62 loại (lọc "Tất cả") để nhân viên xem lại và sửa; còn loại chưa chọn thì vẫn chỉ hiện loại đó.

## 0.5.32 - 2026-10-16
- `data/menh.json` + `data/stones.json`: Thạch anh (chưa rõ loại), Thạch anh xanh, Thạch anh tóc (chưa rõ màu) gán đủ 5 mệnh theo nhân viên tư vấn (không rõ màu thì chọn đủ).

## 0.5.31 - 2026-10-16
- `/chon-menh/`: mặc định chỉ hiện loại đá chưa chọn (nút lọc "Chưa chọn" đứng đầu), chọn xong loại nào thì loại đó tự ẩn; hết thì báo đã chọn xong.

## 0.5.30 - 2026-10-16
- `data/menh.json` + `data/stones.json`: áp lựa chọn của nhân viên tư vấn (ảnh chụp trang /chon-menh/ 23:12) cho 23 loại đá khác bảng màu: Topaz (Kim, Thủy); Thạch anh tóc vàng, Thạch anh vàng (Kim, Thủy, Thổ); Thạch anh mắt chim ưng, Đá giông bão (Mộc, Thủy); Đá mắt rồng, Fluorit (Mộc, Hỏa, Thổ); Ngọc đế quang (Mộc, Hỏa); Pyrite (Kim, Thủy); đủ 5 mệnh: Opal, Zircon, Beryl, Ametrine, Thạch anh tóc tam tài, ngũ sắc, Super 23, cầu vồng, Super Seven, Chalcedony, Băng ngọc thủy tảo, Amazonite, Mica, Mix.

## 0.5.29 - 2026-10-16
- `/chon-menh/`: bấm chọn mệnh là lưu ngay (ghi chú tự lưu sau 1,2 giây), không còn bước Xác nhận dễ bị quên; thiếu tên thì báo rõ và tự lưu khi nhập tên; lựa chọn chưa lưu được giữ trong trình duyệt (localStorage) và tự gửi khi mở lại trang.

## 0.5.28 - 2026-10-16
- `data/menh.json` + `data/stones.json`: Thạch anh ưu linh, Thạch anh super seven gán đủ 5 mệnh (không kỵ mệnh, theo cửa hàng).

## 0.5.27 - 2026-10-16
- `data/menh.json` + `data/stones.json`: Ruby, Sapphire, Kim cương, Cẩm thạch, Hổ phách, Tourmaline, Thạch anh ưu linh gán đủ 5 mệnh (trường `menh` + `moi_menh: true`) theo cửa hàng: đá quý không kỵ mệnh. Ghi đè thắng cả màu trong tên sản phẩm (vd Sapphire vàng).

## 0.5.26 - 2026-10-16
- Trang nội bộ `/chon-menh/` (lớp `Tools\Menh_Vote`, `assets/js/chon-menh.js`, `assets/css/chon-menh.css`): nhân viên tư vấn chọn mệnh hợp cho từng loại đá trong `data/menh.json`, có ô ghi chú và tên người chọn. Lưu qua `GET/POST /wp-json/iga/v1/chon-menh`, mỗi loại đá một option `iga_chon_menh__<slug>` (autoload no, giữ 10 bản cũ). Chỉ bật khi `WP_ENVIRONMENT_TYPE` là staging hoặc `IGA_CHON_MENH` = true; trang noindex, không cache.

## 0.5.25 - 2026-10-16
- `Data\Menh::for_product`: áp dụng ghi đè `menh` của loại đá khi gán cho từng sản phẩm (trước đó chỉ `stone_menh` đọc trường này nên 0.5.24 không đổi sản phẩm nào); ghi đè thắng cả màu viết trong tên sản phẩm.

## 0.5.24 - 2026-10-16
- `data/menh.json`: loại đá có thể ghi đè mệnh bằng trường `menh` (Data\\Menh::stone_menh); cẩm thạch chỉ gán Mộc (bỏ Hỏa) theo ý kiến nhân viên tư vấn.

## 0.5.23 - 2026-10-16
- Lệnh mới `wp iga menh` (lớp `CLI\Menh_Command`, logic thuần ở `Data\Menh`, bảng ở `data/menh.json`): gán lại thuộc tính "Mệnh hợp" (`pa_menh`) theo MÀU CHỦ ĐẠO của loại đá. Đá màu hành E hợp mệnh E và hành mà E sinh ra (Ruby đỏ: Hỏa, Thổ; thạch anh tóc vàng: Kim, Thổ; Sapphire xanh dương/đen: Mộc, Thủy). Loại đá nhiều màu đọc màu viết ngay sau tên đá trong tên sản phẩm; không có thì lấy mệnh ghi rõ trong mô tả shop; không có nữa thì để trống (không đoán).
  - `apply --dry-run [--report=<file>]` thống kê và liệt kê sản phẩm có mô tả shop ghi mệnh khác bảng mới; `apply --backup-dir=<dir>` sao lưu `rows.jsonl` + `rows.sha256` (đọc ngược kiểm) rồi ghi `term_relationships` trong một giao dịch, tính lại số đếm term, reindex bộ lọc đúng các sản phẩm đổi, đổi phiên bản cache lọc, đọc lại so khớp; `rollback --dir=<dir> [--force]`; `table` in bảng. Chạy lại không đổi gì thêm.
- `data/stones.json`: cột `menh` của 62 loại đá đổi theo `menh.json` (32 loại đổi; `menh_source` chỉ còn `rule` hoặc `none`; nhiều màu để trống thay vì ghi đủ 5 mệnh), có kiểm thử so khớp (`tests/phpunit/Data/MenhTest.php`).
- `Schema\Product_Schema`: bỏ `hasMerchantReturnPolicy` (MerchantReturnPolicy đổi trả 3 ngày) và `shippingDetails` (OfferShippingDetails giao hàng miễn phí 0 VND) khỏi mọi Offer của Product/ProductGroup. Shop không cam kết đổi trả, và prod chỉ miễn phí giao hàng khi khách chuyển khoản trước nên schema không được hứa thay shop. Offer không có giá vẫn bị bỏ như cũ.

## 0.5.22 - 2026-10-16
- `Content\Desc_Fix` (sapo của `wp iga desc-fix`) bỏ hai chỗ tự suy: (1) "tự nhiên" chỉ ghi khi TÊN sản phẩm hoặc một dòng RIÊNG của mô tả ngắn nói là tự nhiên/thiên nhiên (dòng cam kết chung của shop bắt đầu bằng dấu tick hoặc có chữ "cam kết" nằm ở hầu hết mô tả ngắn nên không còn tính; hàm mới `excerpt_product_text()`); (2) xuất xứ chỉ ghi "có xuất xứ từ X" đúng dữ liệu (ưu tiên mục cụ thể nhất như "Phan Thiết (Bình Thuận)"), không còn "khai thác tại" vì prod chỉ có nhãn "Xuất xứ". Kiểm thử cập nhật + thêm `test_sapo_natural_only_from_product_own_text`.
- `CLI\Lp5_Hero` (trang tổng /bo-suu-tap/): câu giới thiệu 3 nhóm lấy nguyên văn prod hoặc chỉ liệt kê tên mẫu ("Phật giáo mật tông đưa ra thuyết 8 vị Bản Tôn chủ quản 12 con giáp"; bỏ "được thờ và đeo nhiều nhất" và "mang may mắn, tài lộc và bình an" vì prod không có).

## 0.5.21 - 2026-10-16
- `wp iga desc-fix` v2 (cấu trúc mô tả chuẩn hơn, chạy đúng trên nội dung GỐC): SAPO dựng từ dữ liệu thật của sản phẩm (tên không "#mã" in đậm, loại sản phẩm, hình tượng, loại đá, xuất xứ, nhóm đá + một câu giới thiệu dễ hiểu từ mô tả ngắn); H2 MỚI đặt tên cho cụm đoạn mở đầu cũ ("Ý nghĩa {hình tượng}", "Ý nghĩa Phật bản mệnh ...", "Ý nghĩa đá ...", "Giới thiệu {tên}"); heading phần A (hình tượng) là H3 dưới H2 mở đầu, heading mở phần B (loại đá) là H2 và các mục sau là H3/H4; không có phần B thì heading cũ là H3 (giữ H2 khi từ 3 chủ đề lớn cùng bậc). Mô tả đã mở đầu bằng sapo thì bỏ qua (idempotent).
- Tuỳ chọn `--original-from=<thư mục sao lưu>` (chỉ cùng `--dry-run`): xem trước trên nội dung gốc lấy từ `rows.jsonl` của một lần chạy trước mà không phải hoàn tác DB.
- `Content\Headings::retag()`/`step()` tách ra để lập bậc heading theo luật riêng; kiểm thử `tests/phpunit/Content/DescFixTest.php` viết lại cho v2.

## 0.5.20 - 2026-10-16
- Lệnh mới `wp iga desc-fix` (lớp `CLI\Desc_Fix_Command`, logic thuần ở `Content\Desc_Fix`): chuẩn hoá mô tả sản phẩm (`post_content`) thẳng trong DB.
  - Bỏ dòng tiêu đề lặp tên sản phẩm ở đầu (mọi h1 chữ thuần; h2/h3 chỉ khi lặp tên; tiêu đề mục như "Ý nghĩa ..." hoặc "... là gì?" được giữ).
  - Dựng sapo: khối kế tiếp là `<blockquote>` thì chuyển thành các `<p>` (p đầu `class="sapo"`); là `<p>`/chữ trần từ 60 ký tự thì gắn `class="sapo"`; còn lại lấy dòng giới thiệu đầu tiên (không phải "Nhãn: giá trị", không phải dòng cam kết có dấu tick, từ 40 ký tự) của mô tả ngắn làm `<p class="sapo">`.
  - Heading: h1 thành h2, cấp cao nhất là h2, không nhảy cấp (h3 mở đầu thành h2, h4 sau h2 thành h3).
- Lệnh mới `wp iga headings-fix [--type=page,post]` (lớp `CLI\Headings_Fix_Command`, logic thuần ở `Content\Headings`): cùng luật heading cho trang và bài viết đang xuất bản. Trang dựng bằng khối: đổi đồng bộ `level` trong chú thích `wp:heading` và thẻ HTML, kiểm bằng `parse_blocks` trước/sau (cùng số khối, bậc khớp thẻ). Bỏ qua trang WooCommerce; heading rỗng hoặc chỉ ảnh không đổi (chỉ ghi chú).
- Cả hai lệnh dùng chung `CLI\Content_Fix_Base`: `--dry-run` (thống kê + mẫu trước/sau), sao lưu `rows.jsonl` ngoài webroot (kiểm đọc ngược + băm) trước khi ghi, ghi bằng `$wpdb->update` trong một giao dịch rồi đọc lại so băm, `clean_post_cache` từng ID, `--rollback=<thư mục>` (kiểm băm từng hàng), chạy lại không đổi gì thêm. Mỗi hàng qua bước kiểm chữ trước/sau; hàng không đạt bị bỏ nguyên.
- Kiểm thử: `tests/phpunit/Content/HeadingsTest.php`, `DescFixTest.php`.

## 0.5.19 - 2026-10-15 (lô G1)
- Lệnh mới `wp iga image-swap` (lớp `CLI\Image_Swap_Command`, logic thuần ở `Media\Image_Swap`): thay ảnh lấy từ mạng bằng ảnh AI trong `post_content` và `post_excerpt` của bài/sản phẩm.
  - `import --dir=<thư mục>`: nhập ảnh mới vào Thư viện bằng `wp media import` (tiêu đề, alt tiếng Việt, chú thích "Ảnh AI tạo (G1)"), gắn meta `_iga_g1_job`; chạy lại không nhập trùng.
  - `swap [--dry-run|--apply --backup-dir=<ngoài webroot>]`: đổi URL cỡ gốc và mọi cỡ `-WxH` (src, srcset, dạng mã hoá phần trăm), lớp `wp-image-<id>` và `"id":<id>` của khối `wp:image`. Banner dùng `products` trong manifest để chỉ đổi ở đúng nhóm sản phẩm. Sao lưu `rows.jsonl` (kiểm đọc ngược + băm), ghi trong một giao dịch, đọc lại so băm rồi mới COMMIT; không xoá ảnh cũ, không đổi `post_modified`.
  - `rollback --dir=<thư mục sao lưu> [--force]`: khôi phục và kiểm băm từng hàng.
  - Job có `supersedes` (bản làm lại) thay luôn ảnh AI của bản trước; job có `featured_posts` đổi cả ảnh đại diện (`_thumbnail_id`) của các bài đó (sao lưu `thumbs.json`, hoàn tác kiểm giá trị).
  - Danh sách job: `data/image-swap/g1.json` (cộng dồn các đợt). Kiểm thử: `tests/phpunit/Media/ImageSwapTest.php`.

## 0.5.18 - 2026-10-15
- Sửa nút "Xem thêm" không bấm được ở nhóm có ô tìm (nút lấy nhầm dòng "Không có lựa chọn phù hợp" làm danh sách).

## 0.5.17 - 2026-10-15
- Mức giá: nhãn "nghìn" đổi thành "k" (`data/price-buckets.json`); không hiện lựa chọn "Giá liên hệ" (vẫn giữ khi URL cũ đang chọn, vẫn tính tỉ lệ phủ giá).

## 0.5.16 - 2026-10-15
- Nhóm lọc có hơn 10 lựa chọn có ô tìm (gõ không dấu vẫn ra, đang gõ thì hiện cả mục sau "Xem thêm"; giữ chữ đã gõ khi nhóm lọc được vẽ lại).
- `sort: "alpha"` (A-Z tiếng Việt qua `Collator` vi_VN) cho Loại đá và Hình tượng.
- Bỏ nhóm lọc Xuất xứ khỏi mọi bộ `type_facets` (giữ định nghĩa để URL cũ có `xuat-xu` vẫn lọc được).

## 0.5.15 - 2026-10-15
- Bộ lọc Loại đá, Chế tác, Hình tượng hiện danh sách phẳng (`ui` list) thay cây nhóm; `Query_Engine::term_options()` bỏ các term nhóm thuần `g-*` khỏi danh sách phẳng (trừ khi đang chọn qua URL). Xuất xứ giữ dạng cây.

## 0.5.14 - 2026-10-14 (lô LP5)
- `wp iga page-fixes` bước 9 (`Lp5_Hero::enable_load_more`): bật loadMore cho các khối `iga/product-grid`, giữ cột và hàng; chạy lại không đổi gì.

## 0.5.13 - 2026-10-14 (lô LH1)
- `wp iga page-fixes` bước 10 (`Lh1_Map`): trang `lien-he` thay khối HTML chứa iframe Google Maps (kèm khối đệm 15px ngay sau) bằng khối `iga/store-map` của theme (toạ độ, tên, địa chỉ, place_id của hồ sơ Google "Infinity Gems An Thịnh"). Chỉ ghi khi đúng một khung Google Maps và chưa có khối bản đồ; chạy lại không đổi gì. Sao lưu ngoài webroot, kiểm băm, hoàn tác bằng `--rollback`.
## 0.5.12 - 2026-10-14 (lô F2)
- Bỏ nhóm lọc `ton-kho` (Tình trạng): `data/facets.json`, `Query_Engine` (kind `stock`), `Index_Builder` (không còn ghi dòng `ton-kho`). `Archive::redirect_legacy_params()` (template_redirect) 301 URL có `ton-kho` về URL không có tham số đó; `Request` bỏ qua tham số.
- `Index_Builder::PRIORITY_AVAILABLE`/`PRIORITY_SOLD`: `stock_priority` chỉ còn 1 (còn bán, gồm onbackorder) và 3 (outofstock); `Indexer::reconcile()` sửa lệch theo quy tắc mới.
- `Schema::DB_VERSION` 2: bước nâng cấp xoá dòng index `ton-kho`, đổi `stock_priority` 2 và 4 thành 1, tăng phiên bản cache bộ lọc.
- Lớp mới `Filter\Stock_Order`: biến truy vấn `iga_sold_last`; `posts_clauses` thêm LEFT JOIN bảng `iga_product_sort` (khoá chính product_id; chưa có index thì dùng meta `_stock_status`) và chèn biểu thức đã-bán vào đầu ORDER BY (phân trang đúng). Đo: JOIN postmeta chậm gấp 10-40 lần (postmeta không có chỉ mục post_id + meta_key), JOIN bảng sắp xếp thì không; tự bật cho truy vấn chính của sản phẩm (tìm kiếm, thẻ, thuộc tính, danh mục, cửa hàng); `posts_pre_query` xếp lại kết quả tìm kiếm của engine tandoanh-code.
- Shortcode lưới sản phẩm cũ (`Compat\FlatsomeShortcodes::sc_products`) bật `iga_sold_last`.

## 0.5.11 - 2026-10-14 (lô LP5)
- `wp iga page-fixes` bước 8 (`Lp5_Hero::strip_dup_heading`): bỏ heading đầu nội dung trùng H1 của hero và khối spacer ngay sau; chạy lại không đổi gì.

## 0.5.9 - 2026-10-14 (lô LP5)
- `wp iga page-fixes`: bước 6 thêm điểm giữ ảnh dọc theo khoá ảnh (`AI_FY`) và đổi ảnh khi ảnh hoặc điểm giữ đổi; bước 7 cập nhật `imageId` của 6 thẻ stone ở trang chủ theo ảnh AI mới nhất.

## 0.5.8 - 2026-10-14 (lô LP5)
- `wp iga page-fixes` bước 7 (`Lp5_Hero`): đặt khối `iga/collection-grid` kiểu `stone` "Bộ sưu tập theo loại đá" vào chỗ khối cũ "Chọn theo loại tác phẩm" của trang chủ và gỡ khối cũ (nếu khối stone đã nằm chỗ khác thì dời về); không còn khối cũ thì chèn trước lưới "Top sản phẩm nổi bật"; bỏ qua nếu đã đúng chỗ hoặc thiếu ảnh AI. Sao lưu và hoàn tác như các bước cũ.

## 0.5.4 - 2026-10-14 (lô P3D)
- Mới `Portfolio\Portfolio`: đăng ký lại kiểu bài `featured_item`, danh mục `featured_item_category`, thẻ `featured_item_tag` và luật địa chỉ của Flatsome Portfolio (`/bo-suu-tap/<slug danh mục>/`, `/bo-suu-tap/<slug danh mục nối dấu phẩy>/<slug mẫu>/`, `/featured_item/`). Chỉ chạy khi chưa có kiểu bài này; phần đầu địa chỉ đổi được bằng filter `iga_core_portfolio_base`. Không ghi DB. Sau khi chép plugin lên một site phải chạy `wp rewrite flush` (người dùng www-data) một lần.
- `Forms\Ask_Price`: khoá hỏi giá `bst-<ID>` cho mẫu trang sức 3D (bài `featured_item` đã đăng); `subject()` và `message()` nhận thêm từ gọi đối tượng (mặc định "sản phẩm", mẫu 3D là "mẫu trang sức 3D"). Sản phẩm Woo không đổi.
- Test: `tests/phpunit/Portfolio/PortfolioTest.php`.

## 0.5.3 - 2026-10-14 (lô Q2)
- `Editor\Search` giữ `type=post`, hiển thị ngày/chuyên mục và chỉ cảnh báo khi bài chưa có ảnh đại diện (bỏ cảnh báo 1200 px).
- `wp iga page-fixes`: việc 1c chỉnh khối `iga/news-feed` của /kien-thuc/ (showBreadcrumb, showHeading, showFeatured, showChips = false; excludeCategories = chuyên mục slug `khuyen-mai`) và `excludeCategories` của banner. Sao lưu/hoàn tác như các việc cũ.

## 0.5.2 - 2026-10-14 (lô Q2)
- `Editor\Search`: kiểu mới `type=post` cho ô chọn bài của khối `iga/news-banner` (lọc không dấu trên tiêu đề các bài đã đăng, cache 5 phút; trả kèm ngày, chuyên mục, ảnh nhỏ và cảnh báo khi bài chưa có ảnh đại diện hoặc ảnh dưới 1200 px).
- `wp iga page-fixes`: thêm việc 1b, trang `kien-thuc` đã có khối `iga/news-feed` mà chưa có banner thì chèn khối `iga/news-banner` (bài mới nhất có ảnh lớn, 5 bài, 7 giây) ngay đầu nội dung, giữ nguyên phần còn lại. Có sao lưu ngoài webroot, kiểm băm, hoàn tác như các việc cũ; chạy lại không đổi gì.

## 0.5.1 - 2026-10-13 (lô Q1)
- `assets/js/iga-filter.js` (+ `.min.js`): class trạng thái trên `<html>` khi mở ngăn kéo đổi thành `is-filter-open` / `is-sort-open` (tên cũ trùng class của nút mở nên CSS nút áp lên `<html>` và làm vỡ trang từ 1100px trở xuống).
- Lệnh WP-CLI mới `wp iga page-fixes [--dry-run|--apply --backup-dir=<dir>|--rollback=<dir>]`: (1) trang `kien-thuc` dựng lại bằng một khối `iga/news-feed` (chỉ ghi khi còn dấu hiệu bản cũ: banner `linear-gradient` hoặc `iga/news-teaser`, chưa có `iga/news-feed`); (2) khối `iga/product-grid` có `heading` trùng chữ tiêu đề trang thì bỏ `heading` (H1 và H2 không còn giống nhau). Sao lưu JSON + `ROLLBACK.sql` ngoài webroot, kiểm băm từng file, kiểm slug/tiêu đề/trạng thái/meta không đổi sau khi ghi, hoàn tác bằng `--rollback`. Dùng lại được khi go-live.

## Chưa phát hành

### Sửa (lô L8, bảo mật và SEO)
- `Ip::client`: qua Cloudflare chỉ tin `CF-Connecting-IP` (trước đây còn đọc `X-Forwarded-For`/`X-Real-IP` khách tự gửi xuyên Cloudflare nên đổi IP né được giới hạn tần suất); qua proxy nội bộ lấy hop phải nhất không tin cậy của `X-Forwarded-For`. Thêm `Ip::bucket()`: IPv6 gộp theo /64 trong `Rate_Limiter` (cũng áp cho `Forms\Throttle`).
- `Filter\Seo::print_canonical`: URL lọc/sắp xếp vừa noindex,follow vừa có canonical về URL sạch (Rank Math bỏ thẻ canonical trên trang noindex nên filter canonical cũ không có tác dụng).
- `Editor\Search`: route `iga/v1/editor-search` không trả sản phẩm chưa công khai cho người chỉ có `edit_posts` (kiểm `read_post`).
- Test: `IpTest` (CF bỏ qua XFF/X-Real-IP, hop phải nhất, IPv6 /64), `RateLimiterTest` (cùng /64 chung bộ đếm).

## 0.5.0 - 2026-10-13

Lô LP3: trang landing sửa được kiểu hungphat (cột phải hiện đúng bảng thiết lập của từng phần).

### Thêm
- `wp iga blocks-upgrade --dry-run | --apply --backup-dir=<dir> | --rollback=<dir>`: nâng cấp trang block thô (core/group, core/cover, core/heading, core/spacer do `wp iga ux2blocks` sinh ra) thành khối `iga/*`. Chỉ nhận ra khuôn đã biết, khuôn lạ để nguyên; trang chủ (`page_on_front`) không bao giờ bị đụng.
  - A. Cặp cover băng rôn (một cái `iga-hide-sm` cho máy tính, một cái `iga-show-sm` cho điện thoại) thành một `iga/hero-slider` một slide (ảnh máy tính, ảnh điện thoại, điểm giữ ảnh, kiểu "Thấp"). Chữ điện thoại trùng chữ máy tính thì gộp; khác thì giữ làm `mobileTitle`. Băng rôn máy tính đứng một mình giữ nguyên (chuyển sẽ làm nó hiện thêm trên điện thoại).
  - B. Tiêu đề h2 (kèm khoảng cách) đứng trước `iga/product-grid` chưa có tiêu đề thành thuộc tính `heading` của lưới (kiểu "Gọn"); tiêu đề có thẻ lồng, h3 trở đi hoặc lớp lạ không gộp.
  - C. Nhóm `iga-sec`/`iga-col` trơn mà sau B chỉ còn khối `iga/*` thì bỏ lớp bọc.
  - Chỉ ghi trang đạt mọi phép kiểm: chữ khớp 100% (bộ trích chữ của `Ux\Text`), tập liên kết, ID và URL ảnh, thiết lập lưới sản phẩm, số H1 không đổi, tên khối đã đăng ký, chuỗi khối đọc lại ổn định. Sau ghi kiểm slug, tiêu đề, trạng thái, meta (gồm Rank Math, template) không đổi; lỗi thì tự khôi phục trang đó.
  - Sao lưu NGOÀI webroot trước khi ghi: `pages/<ID>.json` (nội dung và toàn bộ meta, có băm), `ROLLBACK.sql`, `posts-rows.sql.gz` (mysqldump `REPLACE INTO` đúng các hàng bài viết sẽ ghi, đọc lại kiểm), `manifest.json`, `report.json`. Mật khẩu DB cho mysqldump đi qua file tuỳ chọn tạm chmod 600, xoá ngay. Bản cũ còn trong revision.
  - `IGA\Core\Upgrade\Transformer` (thuần PHP trên mảng khối), `IGA\Core\Upgrade\Checker`, `IGA\Core\CLI\Blocks_Upgrade_Command`. PHPUnit `tests/phpunit/Upgrade`.
- `Ux\Text`: khoá thuộc tính `mobileTitle` tính là chữ hiển thị.

### Đổi
- `Ux\Runner::meta_hash` không tính `rank_math_og_content_image` (bộ nhớ đệm ảnh OG Rank Math tự tính lại khi trang được xem, làm lệch băm meta khi hoàn tác).

## 0.4.1 - 2026-10-13

Lô L6: form công khai (liên hệ, hỏi giá, thanh toán). Không ghi gì vào DB, không cần chạy lệnh dữ liệu khi lên prod.

### Thêm
- `IGA\Core\Forms\Phone`: kiểm tra số điện thoại Việt Nam đúng (di động 03x 05x 07x 08x 09x theo dải đầu số đang cấp, số bàn 02x 11 số, dạng +84, 84, 0084, `+84 (0) ...`, chấp nhận khoảng trắng, chấm, gạch ngang, ngoặc). Thay đoạn regex hỏng `/.../g` của child theme Flatsome cũ (cờ g không có trong PCRE nên hàm luôn trả false). Gắn vào `wpcf7_validate_tel` và `wpcf7_validate_tel*` với câu lỗi rõ ví dụ.
- `IGA\Core\Forms\Turnstile`: Cloudflare Turnstile cho CF7 qua tích hợp sẵn của CF7 6.x. Khoá lấy từ HẰNG SỐ `IGA_TURNSTILE_SITEKEY` và `IGA_TURNSTILE_SECRET` trong wp-config.php (filter `wpcf7_turnstile_sitekey`, `wpcf7_turnstile_secret`), không lưu khoá thật vào DB. Có khoá thì form bỏ khối quiz "10 x 5 = ?" và thêm `[turnstile theme:dark size:flexible]` trước nút gửi (viết lại mẫu form lúc chạy, không sửa form trong DB); chưa có khoá thì giữ quiz như cũ.
- `IGA\Core\Forms\Throttle`: giới hạn tần suất theo IP và theo nhóm (liên hệ 5 lần/10 phút, đặt hàng 12 lần/10 phút, đăng nhập 10 lần/10 phút; lọc qua `iga_core_throttle_limits`), dùng lại `Filter\Rate_Limiter` và `Filter\Ip` (chỉ tin header Cloudflare khi kết nối đến từ dải Cloudflare/nội bộ). Bộ đếm: object cache bền vững nếu có (Redis), không thì transient có hạn. Form liên hệ đếm sau khi qua kiểm tra trường và trước bước gọi Cloudflare; thanh toán chỉ đếm lần đặt hợp lệ (không chặn khách gõ sai).
- `IGA\Core\Forms\Ask_Price`: `/lien-he/?hoi-gia=<mã sp>` điền sẵn chủ đề `Hỏi giá sản phẩm <tên> (<mã>)` và nội dung `... <link>` cho form CF7 (tên trường mặc định `your-subject`, `your-message`; đổi qua `iga_core_ask_fields`) kèm dòng "Bạn đang hỏi giá". Tham số GET chỉ là khoá tra cứu (chữ, số, . _ -, tối đa 64 ký tự); chữ điền vào lấy từ DB nên không có đường XSS. Trang có tham số không vào cache trang.
- `IGA\Core\Forms\Comment_Guard`: bình luận gửi thẳng tới wp-comments-post.php giới hạn 3 lần/10 phút/IP (trả 429).
- `IGA\Core\Forms\Woo_Guard`: giới hạn đặt hàng và đăng nhập Woo. Không dùng captcha ở thanh toán (mỗi lần captcha lỗi là mất một đơn).
- CF7 chỉ nạp JS (và api.js của Turnstile) ở trang có form (`wpcf7_load_js`, filter `iga_core_cf7_needed` cho vị trí khác).
- Ô điện thoại của form nới `maxlength` 11 lên 20 (số có khoảng trắng hoặc +84), giữ `minlength`.
- Test PHPUnit `tests/phpunit/Forms` (số điện thoại 47 ca, giới hạn tần suất, viết lại form, hỏi giá).

## 0.4.0 - 2026-10-13

Lô LP v2: ô tìm trực tiếp cho trình soạn thảo block.

### Thêm
- `IGA\Core\Editor\Search`: REST `GET /iga/v1/editor-search?type=product|product_cat|product_tag|category|term[&tax=pa_*]&q=..|ids=..|slugs=..&limit<=20`, quyền `edit_posts`. Sản phẩm dùng engine tìm tiếng Việt không dấu của tandoanh-code (chỉ mục, cache) khi có, không thì truy vấn tên và SKU không phân biệt dấu; trả ảnh nhỏ, tên, mã, giá hoặc "Giá liên hệ", tồn kho. Term lọc tại chỗ không dấu (cache 5 phút). Chữ trả về không dùng gạch dài. Kiểm bằng PHPUnit `tests/phpunit/Editor`.

## Chưa phát hành

### Sửa (lô L8, bảo mật và SEO)
- `Ip::client`: qua Cloudflare chỉ tin `CF-Connecting-IP` (trước đây còn đọc `X-Forwarded-For`/`X-Real-IP` khách tự gửi xuyên Cloudflare nên đổi IP né được giới hạn tần suất); qua proxy nội bộ lấy hop phải nhất không tin cậy của `X-Forwarded-For`. Thêm `Ip::bucket()`: IPv6 gộp theo /64 trong `Rate_Limiter`.
- `Filter\Seo::print_canonical`: URL lọc/sắp xếp vừa noindex,follow vừa có canonical về URL sạch (Rank Math bỏ thẻ canonical trên trang noindex nên filter canonical cũ không có tác dụng).
- `Editor\Search`: route `iga/v1/editor-search` không trả sản phẩm chưa công khai cho người chỉ có `edit_posts` (kiểm `read_post`).
- Test: `IpTest` (CF bỏ qua XFF/X-Real-IP, hop phải nhất, IPv6 /64), `RateLimiterTest` (cùng /64 chung bộ đếm).

### Đổi
- JS bộ lọc: thêm `assets/js/iga-filter.min.js` (esbuild; 22,2 KB xuống còn khoảng 11 KB), không đổi hành vi. `Assets::enqueue` nạp bản rút gọn khi có file và không bật `SCRIPT_DEBUG`; tên file trong uploads mang mtime nên cache-bust tự động.

### Thêm
- `IGA\Core\Schema\Product_Schema` (lô L3): bổ sung schema Product cho Rank Math, thay đoạn hard-code của child theme Flatsome cũ. MerchantReturnPolicy (đổi trả 3 ngày, applicableCountry VN) và OfferShippingDetails gắn vào mọi Offer; sản phẩm giá 0 hoặc trống không xuất Offer; thêm VideoObject cho sản phẩm có video YouTube, `Product.video` trỏ tới nó. Filter `iga/schema/return_policy`, `iga/schema/shipping`.
## 0.2.1 - 2026-10-13

Lô L2 (giao diện danh mục) cần vài chỗ nhỏ trong module bộ lọc.

### Thêm
- Filter `iga/filter/mobile_breakpoint` (mặc định 959): điểm chuyển sang drawer; renderer ghi `data-iga-bp` vào cột lọc, JS đọc thay cho số cố định.
- Filter `iga/filter/grid_html`: theme đổi trạng thái rỗng và ưu tiên tải ảnh thẻ đầu (LCP) trên cả đường tải trang lẫn AJAX.
- Filter `iga/filter/card_version`: dấu phiên bản markup thẻ đưa vào khoá cache thẻ, đổi theme/thẻ thì thẻ cũ không còn được dùng.
- JS: hỗ trợ nhiều vùng chip `[data-iga-chips]` (cột lọc và trên lưới) và khung lỗi do theme cấp (`[data-iga-error]`, `[data-iga-error-msg]`, `[data-iga-retry]`), hiện ngay trên vùng kết quả kể cả khi drawer đóng.
- Chip đang chọn có tiền tố tên nhóm ("Loại đá: Sapphire").

### Sửa
- Endpoint nhẹ không nạp Rank Math nên URL sản phẩm và danh mục trong phản hồi AJAX là URL gốc của WooCommerce (`/sản phẩm/slug/`, `/danh-muc-san-pham/cha/con/`), khác URL trên trang và phải qua 301. Endpoint nay dựng lại cùng URL `/slug/` theo cài đặt của Rank Math (chỉ đọc option `rank-math-options-general`); JS đổi phần gốc URL phản hồi (pushState, liên kết phân trang) về `data-iga-base` của trang.
- Số đếm trong cột lọc và số lựa chọn đang tick không còn dấu ngoặc (theme tự định kiểu).
## 0.3.0 - 2026-10-13

Lô LP: chuyển UX Builder sang block và lớp tương thích Flatsome.

### Thêm
- `wp iga ux2blocks --dry-run|--apply [--ids=..] [--backup-dir=..] [--out-dir=..] [--no-home] [--home-file=..]` và `--rollback=<thư mục> [--force]`. Phân tích shortcode Flatsome (lồng nhiều cấp, thuộc tính nháy kép/đơn/không nháy, `]` trong giá trị, NBSP giữa thuộc tính, shortcode không đóng, thẻ đóng thừa) và chuyển sang khối core (heading, paragraph, list, quote, image, gallery, columns/column, group, cover, buttons, details, spacer, separator, embed YouTube, html, shortcode) và khối `iga/*` (ux_products thành `iga/product-grid` cùng nguồn, ux_slider thành `iga/hero-slider` kiểu banner, blog_posts thành `iga/news-teaser`).
- Giữ nguyên văn chữ: so chữ trước/sau từng trang, chỉ ghi khi khớp 100% (không tính khoảng trắng) và khối hợp lệ. `[block id=N]` thành khối dùng lại `wp_block` (meta `_iga_ux_block`) để sửa một chỗ đổi mọi trang. Trang chủ (page_on_front) được thay bằng `themes/iga/data/pages/home.blocks.html`.
- An toàn: sao lưu post_content + toàn bộ meta từng trang ra thư mục NGOÀI webroot và kiểm băm trước khi ghi, ghi trực tiếp `post_content`/`post_modified` (kèm revision của bản cũ; không đổi slug, tiêu đề, template, meta Rank Math), kiểm lại sau ghi và tự khôi phục trang lỗi, `ROLLBACK.sql` + `--rollback` kiểm băm sau khôi phục.
- `IGA\Core\Compat\FlatsomeShortcodes`: render tối thiểu section/row/col/gap/title/button/ux_image/ux_banner/ux_slider/ux_products/ux_gallery/block/blog_posts... chỉ khi shortcode chưa tồn tại (Flatsome không chạy), để không bao giờ hiện shortcode thô; filter `iga_core_flatsome_compat_tags`.
- Action `iga_core_ux2blocks_page_written` sau mỗi trang được ghi (để xoá cache).

## 0.2.0 - 2026-10-13

Lô F1: ghi dữ liệu lọc và module bộ lọc AJAX.

### Thêm
- `wp iga extract --apply [--batch=200] [--only=<nhóm>] --backup-dir=<thư mục>`: ghi taxonomy `pa_loai-san-pham`, `pa_loai-da`, `pa_nhom-da`, `pa_che-tac`, `pa_hinh-tuong`, `pa_menh`, `pa_xuat-xu`, `pa_chat-lieu` (đăng ký attribute WooCommerce nếu chưa có) và meta số đo `_iga_*`. Idempotent, theo lô trong giao dịch, không đổi `post_modified`, không đụng mô tả, chỉ ghi độ tin cậy high và medium, bỏ qua thuộc tính đang dùng làm biến thể, giữ term lạ sẵn có.
- Sao lưu trước khi ghi (file nén, manifest có hash và số dòng, checksum bảng), `ROLLBACK.sql` dựng trước khi ghi; `wp iga extract --rollback=<thư mục>` chạy trong một giao dịch rồi so checksum.
- Bảng mệnh theo màu đá sửa được trong admin (option `iga_core_menh_overrides`, D-12) và được bộ trích xuất đọc.
- Module bộ lọc `IGA\Core\Filter` (fork từ hungphat product-filter v6.12.1): bảng `{prefix}iga_filter_index` và `{prefix}iga_product_sort`, indexer rebuild theo lô không TRUNCATE, reindex khi lưu, đổi tồn kho hoặc giá (kể cả ghi meta trực tiếp), reconcile theo giờ, cờ đang rebuild kèm DONOTCACHEPAGE.
- `Query_Engine` dùng chung cho tải trang qua URL (thay truy vấn chính) và AJAX; AND giữa nhóm, OR trong nhóm, số đếm tính trừ nhóm hiện tại nên chọn một giá trị không ẩn giá trị khác cùng nhóm.
- Nhóm lọc: các `pa_*` ở trên, giá theo 7 mốc D-09 và nhóm Giá liên hệ (D-02, luôn xếp cuối khi sắp theo giá), khoảng số đo `g-*` (chiều cao, trọng lượng, carat, size hạt), tồn kho, Có video; cấu hình theo loại sản phẩm trong `data/facets.json` (D-10).
- Endpoint nhẹ `runtime/iga-filter.php` (chép ra webroot) + mu-plugin `runtime/iga-fast-filter.php`: giới hạn 60 yêu cầu mỗi phút mỗi IP (429), cache phản hồi Redis có tiền tố site và phiên bản, không cần nonce với khách.
- `assets/js/iga-filter.js` (không jQuery): pushState, popstate, phân trang AJAX cập nhật URL, drawer có bẫy focus, nút Xem N kết quả; sắp xếp là nút.
- Canonical về URL sạch và `noindex,follow` cho URL lọc (Rank Math và wp_robots).
- Trang admin iga-core > Bộ lọc: tình trạng index, nút dựng lại, phủ dữ liệu, giá trị chưa map, danh sách cần sửa tay (D-14), bảng mệnh, link YouTube hỏng, cài đặt.
- `IGA\Core\Video\ProductVideo::get()` và `iga_product_video()`: video YouTube từ meta Flatsome `wc_productdata_options`, không phụ thuộc theme; filter `iga/product_video`.
- Template tag cho theme: `iga_filter_toolbar()`, `iga_filter_sidebar()`, `iga_filter_results()`, `iga_filter_pagination()`; filter `iga/filter/card_html` để theme thay thẻ sản phẩm.
- Lệnh `wp iga filter rebuild|reindex|reconcile|status|query`.

### Ghi chú
- Tên meta số đo: `_iga_cao_cm`, `_iga_trong_luong_g`, `_iga_carat`, `_iga_size_hat_ly`...
- Bảng index được tạo khi kích hoạt plugin và chỉ bị xoá khi gỡ plugin (uninstall.php); vô hiệu hoá không xoá dữ liệu.

## 0.1.0 - 2026-10-13

- Khung plugin, bộ trích xuất thuộc tính (`wp iga extract --dry-run`), bảng ánh xạ loại đá, mệnh, hình tượng, xuất xứ, chất liệu.
