Changelog

Tất cả các thay đổi đáng chú ý của plugin SL Migrations được ghi lại tại đây.

changelogrelease-notesversion-history

CHANGELOG — SL Migrations

Tất cả các thay đổi đáng chú ý của plugin SL Migrations được ghi lại tại đây. Theo định dạng Keep a Changelog.


[1.8.6] - 2026-06-30 (Installer UX — sửa lỗi 404 cuối quá trình restore)

🐛 Sửa lỗi (installer)

  • Installer báo lỗi 404 / "ajax error" dù restore đã thành cônginstaller/slm-installer/ctrls/classes/class.ctrl.s3.funcs.php (cleanupFiles()): bản trước hard-delete artifact + rrmdir(DUPX_INIT) xóa nguyên folder slm-installer/ (gồm main.installer.php + CSRF token) ngay tại chunk STEP_CLEANUP_TMP_FILES của step 3. Nhưng sau đó vẫn còn STEP_SET_FILE_PERMS, STEP_FINAL_REPORT_NOTICESstep 4 final report — mỗi cái là một POST riêng tới main.installer.php → server trả "Primary script unknown" (404) → UI hiện lỗi đỏ dù data đã restore đủ.
    • Vá: bỏ block tự hủy sớm. Việc xóa file installer được dời sang giai đoạn không bị race: MigrationMng::autoCleanFileAfterInstall() chạy ở lần đăng nhập admin đầu tiên (PARAM_AUTO_CLEAN_INSTALLER_FILES / cleanInstallerFiles, mặc định BẬT), xóa installer.php + slm-installer/ + các artifact an toàn khi không còn bước nào cần. Đánh dấu // NOTE (SLM 1.8.6).

[1.8.5] - 2026-06-25 (Restore Fixes — đồng bộ version với SLM-PATCHES.md)

🐛 Sửa lỗi (restore)

  • Version check luôn failinstaller/slm-installer/src/Core/Security.php (~241): version_dup (1.8.x) !== DUPX_VERSION (placeholder 1.0.0) luôn đúng → báo "version of archive different from PHP scripts". Vá: thêm && false bỏ check (fork tự đóng gói). Đánh dấu // SLM-FIX.
  • DB password không persist khi validate.../Core/Params/Descriptors/ParamDescDatabase.php (~181): PARAM_DB_PASS 'persistence' => false → request validate DB đọc pass rỗng → "Access denied … using password: NO". Vá: 'persistence' => true. Đánh dấu // SLM-FIX.

2 patch đã có sẵn trong code (ghi ở SLM-PATCHES.md) nhưng trước đây chưa bump version/CHANGELOG — entry này đồng bộ lại. Password gate (getSecurityType()) giữ nguyên.

[1.8.4] - 2026-06-01 (Branding · Asset Rebrand)

🎨 Branding — thay thế toàn bộ asset visual còn sót từ upstream

  • Email logo (assets/img/email-logo.png) — thay bằng logo SL Migrations: icon hai ô vuông xanh + mũi tên + chữ "SL Migrations / BACKUP · RESTORE". Logo mới render bằng PHP GD + DejaVu Bold TTF, kích thước 280×64 px.
  • Brand preview (assets/img/dpro-brand.png) — thay bằng ảnh demo installer SL Migrations 860×229 px (dark header, step wizard, form fields đúng style plugin).

Sau bản này không còn asset nào mang visual identity của upstream trong plugin.


[1.8.3] - 2026-05-25 (WP 7.0 Compat · Merge Security+Performance Audit)

🐛 Fix — Download buttons broken trên WordPress 7.0

  • HTML attribute escapingwp_json_encode() bên trong onclick="..." sinh ra dấu " không escape → SyntaxError JS. Wrap esc_attr(wp_json_encode(...)) tại:
    • slm-modern/views/_row.php — nút Installer, Archive, Both
    • slm-modern/views/detail.php — rewrite nút download dùng onclick thay vì data-action (không có JS handler)
    • template/admin_pages/packages/row_parts/download_buttons.php — legacy list view

🔀 Merge — Security Audit + Performance Pass (v1.8.2 web dev build)

Merge toàn bộ security audit (6 finding), performance (7 fix), CSS minify, và validation từ bản 1.8.2 build ngày 2026-05-20 của web developer. Chi tiết xem mục [1.8.2] bên dưới.

📋 Compat

  • Tested up to: WordPress 7.0 (PHP 8.2.29)

[1.8.2] - 2026-05-20 (Security Audit Pass · Performance Pass · CSS Minify)

🔒 Security audit pass — 6 finding

Audit hai tier (security + performance) trước release. Toàn bộ MUST + SHOULD finding security đã được fix:

MUST FIX (Tier 2 HIGH):

SHOULD FIX (Tier 3 MEDIUM):

  • Latent SQLi find_hash_key classes/package/class.pack.php:3038 — concat $hash trực tiếp vào query. Hiện không có user input nhưng future-proof: chuyển sang $wpdb->prepare(... %s).
  • Self-XSS Recovery widget views/tools/recovery/widget/recovery-widget-selector.php:91-93$package['name'] echo unescaped → wrap esc_html() + cast (int) cho id.
  • Privilege-escalation quick_fix ctrls/class.web.services.php:650-672 — endpoint cap CAP_BASIC cho phép set ANY property của Global_Entity qua $_POST['setup']['global'][key] → user CAP_BASIC có thể đổi package_mysqldump_path, build_mode, etc. Fix: whitelist 5 key (ajax_protocol, basic_auth_*) cho phép quick_fix.

⚡ Performance — 7 fix

Tier 1 ROI cao nhất:

  • is_pack_running 5 query → 1 ctrls/class.web.services.php:1420-1490 — endpoint poller cũ chạy 5 query riêng (mỗi status range 1 query, fetch full 11-col row blob mỗi lần). Consolidate thành 1 get_row_by_status() không filter + partition theo status trong PHP. Tiết kiệm 4 round-trip DB mỗi tick poll.
  • Poll interval 2s → adaptive 5s–10s assets/js/slm-progress-poller.js — đổi từ setInterval(2000) sang setTimeout chain với progressive backoff: signature từ status+progress+uploads.percent, không đổi giữa 2 tick → tăng delay ×1.5 đến max 10s; có chuyển động → reset 5s. Build 10 phút giảm ~300 tick xuống ~120 tick (60% fewer requests cho phase static).
  • Cache scan.json read src/Compat/slm-progress-api.php:165-200 — file vài MB, được đọc + JSON parse mỗi tick poll. Cache wp_cache_set keyed md5(path:mtime) namespace sl-migrations TTL 600s. Site có Redis (như VPS này) thì hit 100% sau tick đầu. Tiết kiệm 1 file_get_contents (vài MB) + 1 json_decode mỗi tick.
  • Bootstrap init gate src/Core/Bootstrap.php:80-93ControllersManager + RESTManager chỉ instantiate khi context admin/AJAX/cron/CLI/REST. Frontend pageview WordPress không còn class-load + register hooks của plugin. Frontend admin-area cold pageload giảm vài chục instance class.
  • Autoloader memoize src/Utils/Autoloader.php:139-192customLegacyMapping() được autoload chain gọi hàng nghìn lần/request, build mảng 50 entries mỗi lần. Memoize static sau call đầu tiên.
  • getTableCollationList + getTableEngineList duplicate query classes/class.db.php:278-329getTableEngineList execute query 2 lần ($wpdb->query() rồi $wpdb->get_col($query)). getTableCollationList dùng 2-step query+get_col() vô lý (SELECT trả num_rows không dùng). Cả 2 chuyển sang $wpdb->get_col($query) một bước, tiết kiệm 1 round-trip mỗi chunk table khi backup scan.
  • CSS bundle minify — 10 file CSS chính (style.css, slm.css, slm-overrides.css, packages.css, import.css, info-page.css, admin-notifications.css, admin-notices.css, global_admin_style.css, style-ctrl.css) minify in-place qua clean-css-cli@5 -O2. Tổng giảm 199KB → 145KB (27% nhỏ hơn) — tiết kiệm bandwidth cho mọi admin page plugin.

🔁 Re-audit pass 2 — fix 5 finding mới

Sau khi apply 13 fix lần 1, em re-audit để verify và phát hiện thêm:

  • is_pack_running keys mismatch (release blocker) ctrls/class.web.services.php:1441,1444 — sau khi consolidate 5 query, code push vào cac_ids / err_ids nhưng array init là cancel_ids / error_ids → JS client nhận empty array → mất status. Fix: đổi tên về cancel_ids / error_ids.
  • quick_fix whitelist typo ctrls/class.web.services.php:653-660 — viết ajax_protocol_custom nhưng property thực là custom_ajax_url (class.global.entity.php:182) → quick-fix dialog "Custom AJAX URL" silently fail. Fix: đổi sang custom_ajax_url.
  • OAuth token plaintext leak vào trace log src/Utils/OAuth/TokenEntity.php:108wp_json_encode($this) dump nguyên access_token + refresh_token plaintext khi refresh fail. Trace log readable bởi user CAP_BASIC qua wp_ajax_sl_migrations_get_trace_log → token exposure. Fix: chỉ log storage_type + expired status.
  • Missing CAP_CREATE check cho create_from_temp slm-modern/ModernPackagesController.php:107 + views/packages/main/packages.php:33 — nonce OK nhưng cap không check → user CAP_BASIC có thể tạo package từ temp data. Fix: thêm CapMng::can(CapMng::CAP_CREATE) gate.
  • renameInstallerFile chạy mỗi frontend pageview src/Core/Bootstrap.php:75 — hook init priority 20 chạy ExpireOptions::getUpdate (transient lookup + DB write) trên mọi pageview frontend dù không cần. Fix: wrap vào $isContextual gate cùng với ControllersManager/RESTManager.

Files changed

Security:

  • src/Ajax/FileTransfer/ImportUpload.php — basename path traversal
  • slm-modern/ModernPackagesController.php — CSRF nonce + CAP_CREATE
  • views/packages/main/packages.php — CSRF nonce + CAP_CREATE
  • views/packages/main/s2.scan1.base.php — wp_nonce_field
  • src/Package/Import/PackageImporter.php — không log archive password
  • classes/entities/class.package.template.entity.php — không log installer password
  • src/Utils/Crypt/CryptBlowfish.php — không log plaintext khi crypt fail
  • src/Utils/OAuth/TokenEntity.php — không dump OAuth token plaintext
  • classes/package/class.pack.phpfind_hash_key prepare
  • views/tools/recovery/widget/recovery-widget-selector.php — esc_html package name
  • ctrls/class.web.services.php — quick_fix whitelist + custom_ajax_url

Performance:

  • ctrls/class.web.services.phpis_pack_running 1 query + đúng response keys
  • assets/js/slm-progress-poller.js — adaptive poll backoff
  • src/Compat/slm-progress-api.php — cache scan.json
  • src/Core/Bootstrap.php — context gate + renameInstallerFile
  • src/Utils/Autoloader.php — memoize mapping
  • classes/class.db.php — bỏ duplicate query
  • 10 file assets/css/*.css minified (27% smaller)

🐛 Fix nghiêm trọng — mất SFTP credentials & reset Default storage retention sau mỗi lần update

Triệu chứng: Sau khi update plugin, một số web bị mất hoàn toàn credentials SFTP (server/username/password trống), và mọi web đều bị reset Default storage: max_packages từ giá trị anh set (vd 1) thành 20, và option "tự xoá gói cũ" (purge_packages) bị bỏ check.

Root cause: src/Core/Upgrade/UpgradeFunctions.php:258-265 — hàm storeDupSecureKey() so $currentVersion với '4.5.0' (di sản Duplicator Pro upstream). Plugin đã rebrand sang scheme 1.x.xversion_compare('1.x.x', '4.5.0', '<=') luôn TRUE → mỗi lần update rơi vào nhánh:

CryptBlowfish::createWpConfigSecureKey(true, true); // OVERWRITE=true, legacy MD5

→ Ghi đè SL_MIGRATIONS_AUTH_KEY trong wp-config bằng MD5 hash legacy → toàn bộ config đã encrypt trong DB (SLM_PRO_Storage_Entity.config) decrypt fail → AbstractStorageEntity::__wakeup fallback về getDefaultConfig():

Fix: Gỡ toàn bộ legacy migration của Duplicator Pro 4.x khỏi src/Core/Upgrade/UpgradeFunctions.php — plugin đã standalone, không còn user nào chạy upstream:

  • storeDupSecureKey() — bỏ nhánh overwrite legacy MD5, chỉ ensure key tồn tại
  • updateStorages() — gỡ hẳn (gọi updateFromGlobal() reset max_packages=20, purge_packages=false mỗi lần update → đây là root cause của reset Default storage trên mọi web)
  • updateTemplates(), updatePackageComponents(), initEmailSummaryRecipients() — gỡ hẳn (cùng pattern bug version-compare 4.5.x)
  • renameLegacyTablesName() — gỡ hẳn (rename table sl_migrations_* → tên mới, fork 1.x đã dùng tên mới ngay từ đầu)
  • moveDataToSecureGlobal(), moveStorageSettingsToDynamicEntity() — gỡ hẳn (migrate global → secure global / dynamic entity, đã chạy xong từ lâu)
  • Constants LAST_VERSION_*, FIRST_VERSION_WITH_NEW_TABLES, LEGACY_DB_* — xoá
  • DefaultLocalStorage::updateFromGlobal() — xoá luôn (orphan, không còn ai gọi)

performUpgrade() giờ chỉ làm 2 việc: init DB schema + ensure SL_MIGRATIONS_AUTH_KEY tồn tại. Mỗi lần update plugin không còn đụng vào encrypted config trong DB.

Trước đây footer trên các trang page_sl-migrationsleft: 160px (khi sidebar expanded) / left: 36px (khi folded) + media query mobile left: 0. Đổi thành cố định left: 0 cho mọi trạng thái sidebar → footer full-width nhất quán.

🔒 Security — Handlebars 4.0.10 → 4.7.8

Bundle assets/js/handlebars.min.js đang ở 4.0.10 — dính CVE-2019-19919 (RCE qua prototype-pollution trong template compiler) và một số RCE/XSS chain khác trên 4.0.x–4.4.x. Upstream Duplicator Pro đã upgrade trong 4.5.25 với note "JavaScript security vulnerabilities (Handlebars RCE/prototype-pollution)".

Replace bằng Handlebars 4.7.8 (bản patch security cuối của dòng 4.x). API registerHelper + compile đang dùng ở assets/js/javascript.php, views/packages/main/s2.scan3.archive.php, views/tools/diagnostics/inc.validator.php backward-compatible.

🛡️ Storage form validation — chống crash storage list

Trước đây nếu user nhấn Save Provider với field server / username trống, storage entity vẫn được save với config rỗng → trang Storage list sau đó render getLocationString() trên config trống → 500 / unusable page (cùng triệu chứng DupPro 4.5.24.4 fix).

Validate server-side trong FTPStorage::updateFromHttpRequest, SFTPStorage::updateFromHttpRequest:

  • FTP: server + username không được trống
  • SFTP: server + username không được trống, phải có ít nhất 1 trong password / private_key (đã lưu sẵn hoặc vừa nhập)

Block save + trả message tiếng Việt rõ ràng nếu thiếu.

🐛 Defensive fixes

  • MySQL NULL table stats (class.pack.database.php:229) — DATA_LENGTH + INDEX_LENGTH trên InnoDB có thể trả NULL khi statistics chưa được tính → cộng NULL ra NULL → fatal khi cast int trong scan loop. Bọc COALESCE(..., 0) cho cả 2 cột.
  • Trace log download fpassthru disabled (ctrls/class.web.services.php:1058) — fpassthru bị disable trên một số shared host qua disable_functions. Fallback đọc theo chunk 8KB nếu fpassthru không tồn tại hoặc trả false.

Files changed

  • assets/js/handlebars.min.js — Handlebars 4.7.8 (security)
  • addons/ftpaddon/src/Models/FTPStorage.php — validate server + username
  • addons/ftpaddon/src/Models/SFTPStorage.php — validate server + username + (password OR private_key)
  • classes/package/class.pack.database.php — COALESCE NULL table stats
  • ctrls/class.web.services.php — fallback chunk read khi fpassthru disabled
  • src/Core/Upgrade/UpgradeFunctions.php — rewrite gọn còn 3 method (performUpgrade, updateDatabase, storeDupSecureKey)
  • src/Core/MigrationMng.php — call updateDatabase() không param
  • src/Models/Storages/Local/DefaultLocalStorage.php — xoá updateFromGlobal() orphan
  • slm-modern/modern.php — đơn giản hoá inline critical CSS footer

[1.8.0] - 2026-05-18 (Modern Recovery Point UI · Modal Escape Fix · Import Min Version)

🔥 Recovery Point UI hoàn chỉnh trong Modern UI

Trước đây Set Recovery Point chỉ có ở menu Hamburger của UI legacy → modern UI thiếu hẳn action. Phiên bản này hoàn thiện trải nghiệm:

Backend AJAX endpoints (cùng nonce slm_modern):

  • slm_modern_recovery_status — đọc state hiện tại (read-only)
  • slm_modern_recovery_set — wrap RecoveryPackage::setRecoveablePackage()
  • slm_modern_recovery_reset — wrap SLM_PRO_CTRL_recovery::actionResetRecoveryPoint()

Frontend:

  • Tools → Recovery tab: panel modern "Recovery Point" ABOVE legacy với status dot live, box "Recovery Point hiện tại" (tên + link khôi phục + nút Reset), dropdown chọn package + nút "Set as Recovery Point"
  • Packages list: icon shield 🛡️ trong row actions (chỉ hiện khi isPackageIdRecoveable() = true và package chưa phải current Recovery)
  • Package detail page: nút "Set as Recovery Point" trong "Hành động"; badge "Recovery Point" cạnh status pill khi đã set

Visual identity:

  • Badge "Recovery Point" gradient xanh dương #3b82f6 → #1d4ed8 với box-shadow ring
  • Row trong Packages list được highlight với inset 3px 0 0 #2563eb (border trái xanh) + gradient background nhẹ
  • Status dot 3 trạng thái: xanh dương (set) / cam (unset) / đỏ (error)
  • Heading "Recovery Point" (modern) + "Disaster Recovery" (legacy) sync cùng font-size: 18px, weight: 700, icon 20×20; icon shield xanh dương, icon đám cháy xanh lá

🛡️ Bảo vệ Recovery Point khỏi mất dữ liệu

Trước đây cron retention AbstractStorageEntity::purgeOldPackages() chỉ sort theo modified time + cắt N gói cũ nhất theo max_packages. Nếu gói đang là Recovery Point cũ hơn các gói mới → bị xoá vĩnh viễn → URL khôi phục hỏng → mất khả năng khôi phục khi cần.

Fix: purgeOldPackages() lookup RecoveryPackage::getRecoverPackageId() → load Package object → archive_name → so khớp với từng file trong loop. File khớp được skip (log "Skipping purge: package is current Recovery Point").

Đồng thời block xoá thủ công:

  • Client-side: nút Delete trong row + detail bị disabled với tooltip "Không thể xoá — đang là Recovery Point. Reset Recovery Point trước."
  • Server-side guard: ajaxDelete reject HTTP 409 + message tiếng Việt nếu request có ID là current Recovery Point (chặn bypass UI).

🐛 Fix IMPORT_ENABLE_MIN_VERSION quá cao → mọi package bị reject

PackageImporter::IMPORT_ENABLE_MIN_VERSION cố định '4.0.0' (di sản từ Duplicator Pro upstream). sl-migrations rebrand sang scheme 1.xversion_compare('1.7.x', '4.0.0', '<') = trueRecoveryStatus::isRecoveable() reject mọi packagegetRecoverablesPackages() luôn trả về mảng rỗng → không bao giờ set được Recovery Point.

Hạ ngưỡng xuống '1.0.0' (cùng IMPORT_SUB_SITE_IN_MULTISITE_MIN_VERSIONIMPORT_BRIDGE_MIN_VERSION).

Click tên package trong widget Dashboard → trang detail báo "Gói không tồn tại". Root cause: PackagesPageController::getPackageDetailsUrl() build URL với key id, nhưng slm-modern/views/detail.php chỉ đọc $_GET['package_id'].

Fix: detail.php accept cả 2 keys — ưu tiên package_id (modern), fallback id (legacy widget).

🐛 Fix modal &quot; thô (UI global)

SLM.modal.confirm({body: ...}) hiển thị &quot; literal thay vì dấu nháy do double-escape: escapeHtml() chuyển "&quot;, sau đó wrap thành textNode (browser không decode entity trong textNode).

Fix: slm-ui.js:73 dùng el('div', {text: opts.body}) (textContent path) thay vì escapeHtml(body) wrap textNode. Bug này ảnh hưởng MỌI modal trong plugin, không chỉ Recovery.

🧹 Cache version sync

SLM_MODERN_VER đổi từ hardcode '1.7.3' sang SL_MIGRATIONS_VERSION → mỗi lần bump plugin version, modern.css + modern.js auto cache-bust trong browser. Trước đây user phải hard refresh hoặc đợi browser cache TTL.

Files changed

  • slm-modern/: ModernPackagesController.php, ModernToolsController.php, views/_row.php, views/detail.php, modern.js, modern.css, modern.php
  • src/Models/Storages/AbstractStorageEntity.php — purge skip Recovery Point
  • src/Package/Import/PackageImporter.php — lower IMPORT_*_MIN_VERSION
  • assets/js/slm-ui.js — fix modal escape bug

Triệu chứng: Click tên package trong widget Dashboard → trang detail báo "Gói không tồn tại — Có thể gói đã bị xóa hoặc ID không hợp lệ".

Root cause: Mismatch query key giữa URL generator legacy (getPackageDetailsUrl() truyền id={ID}) và modern detail view (detail.php chỉ đọc $_GET['package_id']). ID = 0 → SLM_PRO_Package::get_by_id(0) trả về null → "Không tìm thấy".

Fix: slm-modern/views/detail.php đọc cả 2 keys — ưu tiên package_id (modern), fallback id (legacy widget + các nơi khác chưa migrate sang URL pattern mới).

✨ Modern Recovery Point — set/reset từ UI mới

Trước đây tính năng "Set Recovery Point" chỉ truy cập được qua menu Hamburger ở row package trong UI legacy. Modern UI hoàn toàn thiếu action này → user phải vào tab Recovery của Tools, đọc hướng dẫn, rồi sang page Packages cũ để thao tác.

Bổ sung:

  1. Nút shield trong row actions (Packages list — modern) — chỉ hiện khi package đủ điều kiện (có cả Archive + Installer). Click → modal confirm → AJAX set → toast.

  2. Panel "Recovery Point" trong tab Tools → Recovery (modern) — render ABOVE legacy content, gồm:

    • Status dot (đã/chưa thiết lập) tự load qua slm_modern_recovery_status
    • Khối "Recovery Point hiện tại" + link khôi phục + nút Reset (hiện khi đã set)
    • Dropdown chọn package + nút "Set as Recovery Point"
    • Empty state nếu chưa có package đủ điều kiện

Backend wrapper trong ModernPackagesController (file đã có nonce pattern slm_modern):

  • wp_ajax_slm_modern_recovery_status — đọc state, không destructive
  • wp_ajax_slm_modern_recovery_set — wrap RecoveryPackage::setRecoveablePackage()
  • wp_ajax_slm_modern_recovery_reset — wrap SLM_PRO_CTRL_recovery::actionResetRecoveryPoint()

Tất cả endpoints dùng cùng 1 nonce slm_modern đã có sẵn — không cần thêm nonce key mới ra global config.


🐛 Fixes

  • Timezone hiển thị sai trên Packages list: cột Created show giờ UTC thay vì giờ Việt Nam (vd schedule chạy 12:00 ICT nhưng hiển thị 05:00). Root cause: dùng date_i18n() — legacy API coi timestamp đã pre-offset, hiển thị raw UTC. Fix: chuyển sang wp_date() (modern API tôn trọng WP timezone) + parse string với suffix UTC để strtotime hiểu đúng. (ModernPackagesController.php:217-230)

  • Sticky footer + FOUC trên Packages/Schedules/Storage/Tools: nhiều iteration để đạt:

    • Footer pin xuống đáy viewport trên trang content ngắn
    • Không scroll thừa
    • Không flash layout (logo "nhảy" từ giữa lên top khi CSS load)
    • Footer căn lề đúng với mép phải sidebar (160px default / 36px folded / 0 mobile)
    • Back-to-top button (#backtotop của plugin wp-extra) nổi trên dải footer thay vì bị che

    Approach cuối: position: fixed cho #wpfooter (inline vào <head> qua hook admin_head priority 1) + padding-bottom: 60px cho #wpbody-content. Bỏ approach flex trên #wpwrap vì trong khi WP core CSS chưa load, sidebar #adminmenuwrap trở thành flex item in-flow → đẩy content xuống dưới → flash.

✨ Enhancements

  • Plugin row meta links (Bootstrap.php metaLinks): thêm 2 link README + Changelog vào dòng meta dưới row plugin (kế bên "Phiên bản · Bởi Steven Lam · Ghé thăm trang web · Help"). Tất cả trỏ về tài liệu trên phatdev.net (target="_blank"):
    • README → phatdev.net/docs/sl-migrations/getting-started
    • Changelog → phatdev.net/docs/sl-migrations/changelog (article được seed từ file CHANGELOG.md)
    • Help → phatdev.net/docs/sl-migrations
    • Ghé thăm trang web → phatdev.net/sl-migrations (landing page)
  • Tools → Support tab: thêm card thứ 3 "Tài liệu plugin" với link README + Changelog (target blank, mở phatdev.net trong tab mới).
  • Landing page mới: /sl-migrations trên phatdev.net — marketing page hoàn chỉnh chuẩn sale-ready cho lúc anh mở bán plugin sau này. Gồm hero (gradient text + CTA), 6 features card, 3 use case (Agency/DevOps/Production), tech specs, pricing section (Free internal), bottom CTA, footer.
  • Doc seed script mới (seed-docs-sl-migrations-changelog.mjs): idempotent script đẩy nội dung CHANGELOG.md vào DB docs của phatdev.net (table docs_articles), auto-extract version + date mới nhất.

🔧 Technical

  • SL_MIGRATIONS_VERSION, SLM_ASSETS_VER, SLM_MODERN_VER1.7.3
  • Plugin URI đổi từ https://phatdev.net/https://phatdev.net/sl-migrations
  • Inline critical CSS trong <head> qua admin_head priority 1 — không thêm HTTP request, áp dụng từ frame đầu tiên

[1.7.2] - 2026-05-18 (Run Now Redirect · Import Settings Hardening · Layout & Docs)

🐛 Fixes

  • Layout — sticky footer: trên các trang Packages/Schedules/Storage/Tools khi nội dung ngắn, #wpbody-content bị ép min-height: calc(100vh - 100px) cộng thêm #wpadminbar (32px) + #wpfooter (~52px) → tổng vượt 100vh → scroll dọc thừa, footer bị đẩy dưới viewport. Fix: dùng flex column trên #wpbody (min-height: calc(100vh - 32px) desktop / 46px mobile) với flex: 1 0 auto cho content và flex: 0 0 auto cho footer → footer luôn pin xuống đáy viewport, content tự grow lấp khoảng giữa, không sinh scroll.
  • Back-to-top button overlap: #backtotop của plugin wp-extra được inject inline bottom: 15px, sau khi pin footer xuống đáy thì nút này bị footer che. Fix: override bottom: 70px !important chỉ trong scope body[class*="page_sl-migrations"] để nút float ngay trên dải footer.
  • Schedules — Run Now: dialog kẹt ở "Running Schedule, Please Wait..." và không tự nhảy qua tab Packages khi click. Root cause: .done() không fire trong tình huống PHP-FPM/Nginx connection reset (HTTP/2 reuse). Fix: chuyển sang fire-and-forget pattern — dispatch AJAX rồi redirect sau 300ms, browser vẫn gửi xong request trước khi navigate. Bỏ luôn .always() + safety-timer dài 12s ở fix cũ.
  • Import Settings — cross-site .dup files: import file .dup từ Duplicator Pro / SL-Migrations site khác có thể bị mất config khi storage là subclass (SFTP, S3, ...) vì ReflectionClass(self::class) chỉ lấy property của AbstractStorageEntity, bỏ sót property của subclass. Fix: dùng static::class để late-static-binding lấy đúng class runtime.
  • Import Settings — robustness: MigrateSettings::importStorages/Templates/Schedules/Settings không có try/catch, 1 entity malformed kéo đổ toàn bộ import. Fix: bọc từng item trong try/catch + validate is_array($data) + check isset($data['id']) trước khi map id.
  • AbstractStorageEntity::__wakeup(): isset(null[$key]) ngầm khi CryptBlowfish::decrypt() thất bại — silently bỏ qua, config về default mà không log. Fix: explicit is_array($config) guard + fallback [].
  • AbstractStorageEntity::settingsImport(): validate $data là array, ép config luôn là array (nếu source export sai cấu trúc), try/catch quanh setValue() để 1 prop lỗi không phá entity.

✨ Enhancements

  • MigrateSettings::decodeImportPayload() helper mới: thử decrypt format chuẩn (CryptCustom('test') — tương thích với Duplicator Pro export); nếu fail thì fallback sang plain JSON. Nâng tỷ lệ đọc được file .dup từ các phiên bản / nguồn khác nhau.

🔧 Technical

  • SL_MIGRATIONS_VERSION, SLM_ASSETS_VER1.7.2
  • Files changed: template/admin_pages/schedules/schedule_list.php, src/Utils/Settings/MigrateSettings.php, src/Models/Storages/AbstractStorageEntity.php

✅ Verified

  • Smoke test trên 2 file .dup thật (dpro-export từ site reviewlaptop.vn và tandoanh.vn): decrypt OK, parse OK, SFTP credentials đầy đủ (server / port / username / password / private_key / timeout / chunking) được import qua subclass SFTPStorage.

[1.7.1] - 2026-05-17 (CSS Fixes · Template Edit Page)

🐛 Fixes

  • Template Edit — Filters section: .section-title { height: 20px; padding: 10px } với WP admin box-sizing: border-box tạo content area = 0px → checkbox "Enable" bị clip, filter-links bị overlap. Fix: override height: auto; min-height: 44px; align-items: center.
  • Template Edit — Installer → Branding row: <select>min-height: 40px trong khi nút "Preview" có min-height: 38px → chiều cao không khớp. Fix: đồng bộ cả hai về height: 38px.
  • Filter textareas: slm-overrides.css global textarea { min-height: 100px } khiến #filter-paths#filter-exts quá cao gây layout overflow. Fix: scope riêng min-height: 60px; height: 80px.

🔧 Technical

  • SLM_ASSETS_VER, SLM_MODERN_VER, SL_MIGRATIONS_VERSION1.7.1
  • CSS fixes trong assets/css/slm-overrides.css

[1.7.0] - 2026-05-14 (Language Policy · Modern UI · Table Layouts)

🌐 Chính sách ngôn ngữ

  • Short labels / tab / button chuyển hết về English (Packages, Schedules, Storage, Tools, Settings, Info, Apply, Save Schedule, Activated, Refresh, Reset, ...)
  • Vietnamese chỉ giữ cho mô tả dài, tooltip, error message, warning, confirmation prompt
  • Pronoun toàn bộ user-facing strings: chỉ dùng "bạn" — bỏ "anh" và "ai" ("những ai", "cho ai")
  • translations-vi.php gọn từ ~996 dòng còn ~110 dòng, giữ 40 entries giải thích dài

🎨 UI Redesign

  • Modern UI hero titles toàn bộ về English: Packages / Import / Schedules / Storage / Tools / Settings
  • Disaster Recovery card layout hoàn chỉnh: header đỏ khi chưa thiết lập, xanh lá khi đã set, icon house-fire trong vòng tròn, mô tả dùng "bạn"
  • Storage table: cột chia 10% / 30% / 60% (checkbox / Name / Type)
  • Schedule table: checkbox 10% + 6 cột còn lại 15% mỗi cột (90÷6)
  • Packages table: 10% checkbox + 22% Name + 14% Status + 18% Download + 16% Created + 10% Size + 5% Restore + 5% Details
  • Modern UI Packages: thêm cột Download với 3 nút icon (Installer / Archive / Both Files) + fix Created field hiển thị đúng d/m/Y H:i
  • Table styling đồng bộ trên cả 3 list tables: header xám nhẹ + body trắng + alternate #fafbfd + hover xanh nhạt
  • "No Schedules Found / Create New Schedule" canh giữa thay vì bám trái
  • Bulk Actions select đồng bộ chiều cao 44px với nút Apply / Add New (max-height + zero vertical padding để override browser default <select> rendering)
  • Trace log widget vị trí chính xác right: 60px; bottom: 12px, 4 nút (View / Clear / Download / Turn Off) trên 1 hàng

✨ Quality of Life

  • Ẩn panel "Screen Options" trên tab Packages (modern UI không có column toggle nên panel đó chỉ làm rối)
  • Form wrapper #dup-storage-form, #form-sl_migrations, #dup-schedule-form ép display: block; width: 100% để table stretch full card width
  • Table table-layout: fixed + width: 100% ở 3 list tables → column % được honored chính xác
  • Hidden columns (dup-note-column, dup-storages-column, dup-age-column) display: none để table không bị "vướng"

🔧 Technical

  • SLM_ASSETS_VER1.7.0, SLM_MODERN_VER1.7.0 (đồng bộ với plugin version)
  • ModernPackagesController::normalisePackage() ưu tiên getCreated() method, thêm 6 download fields (installer_url, archive_url, installer_exists, archive_exists, archive_filename, archive_format)
  • add_filter('screen_options_show_screen', ...) ẩn screen options trên toplevel_page_sl-migrations

[1.6.0] - 2026-05-14 (Info Tab · Disaster Recovery · UI Polish)

✨ Thêm mới

  • Tab Thông tin (Info): Trang giới thiệu plugin với hero gradient xanh, 6 cards tính năng nổi bật, parser CHANGELOG.md tự động, bảng thông tin hệ thống (WP/PHP/MariaDB version, bộ nhớ, múi giờ, version plugin)
  • 3 nút download riêng biệt trên packages list: Tải PHP Installer · Tải Archive (DAF/ZIP) · Tải cả 2 file (thay cho dropdown menu)
  • Cột Tải về (Download) di chuyển ngay sau cột Trạng thái thay vì cuối bảng

🎨 UI Redesign

  • Disaster Recovery wrap thành card trắng bo tròn, header đỏ khi chưa thiết lập / xanh lá khi đã set, icon house-fire trong vòng tròn
  • Spinner ajax-loader đổi từ logo Duplicator xoay sang icon vòng tròn Font Awesome (fa-circle-notch fa-spin)
  • Đồng bộ style nút: plain .button, .button.action, .button-primary, .button-secondary đều đi theo style "big pill" 9px×18px, min-height 38px
  • Compact trace log widget: nút Xem/Xóa/Tải/Tắt thu nhỏ vừa vặn trong card, padding container thoáng hơn
  • Storage list table cột checkbox thu gọn 40px (trước đó bị rule td:first-child{width:240px} của bảng Server Info ăn vào, đẩy cột Name xa lề trái)
  • Schedule edit form: select Package Template và 3 nút icon (✎ ➕ ↻) đều cao 36px, select Start Time 00:00 canh giữa vertical
  • Ẩn cột Note + Storages + Age trên packages list để Trạng thái sát Tên gói

🌐 Dịch tiếng Việt

  • Thêm ~50 chuỗi dịch: Disaster Recovery (Khôi phục sự cố, Tuổi sao lưu, Bản khôi phục sự cố...), Schedule form (Kích hoạt, Bật lịch trình này, Lưu lịch trình, Mẫu gói sao lưu, Thời gian bắt đầu...), Packages download buttons
  • Refactor multi-line _e() calls thành inline Vietnamese để tránh whitespace mismatch trong gettext overlay

🔧 Branding

  • Đổi Author header: Phat LamSteven Lam trên sl-migrations.php và README.md
  • Clean legacy copyright: 371 file rewrite từ @copyright (c) 2022,@copyright (c) 2022-2026, Steven Lam
  • Loại bỏ Snap Creek refs: thay @author <admin@snapcreek.com>@author Steven Lam <phatlam.dev@gmail.com>, đổi snapcreek.comphatdev.net trên toàn bộ installer + library files

🛠️ Technical

  • SLM_ASSETS_VER bumped to 1.6.0 để bust cache trình duyệt
  • Mới: src/Controllers/InfoPageController.php (menuPos=90, CAP_BASIC) · views/info/main.php · assets/css/info-page.css
  • Mới: INFO_SUBMENU_SLUG = 'sl-migrations-info' trong ControllersManager

[1.0.0] - 2026-05-13 (Initial Release)

✨ Phát hành bản đầu tiên

  • Sao lưu toàn site WordPress (files + database) thành một archive duy nhất kèm installer PHP
  • Khôi phục 1-click trên bất kỳ host WordPress tương thích nào
  • Sao lưu định kỳ theo lịch (hàng giờ/ngày/tuần/tháng)
  • Lưu trữ remote qua FTP / SFTP an toàn
  • Lưu trữ local mặc định tại wp-content/backups-slm/
  • Plugin namespace SLMigrations\ (PSR-4) với composer-scoped vendor namespace VendorSLMigrations\
  • Constants SL_MIGRATIONS_* / SLM_PRO_*; hooks sl_migrations_*; text domain sl-migrations
  • Installer engine tại installer/slm-installer/

🔒 Security

  • Không có lưu lượng mạng ra ngoài trong quá trình vận hành bình thường
  • Không kích hoạt license, không telemetry, không kiểm tra cập nhật từ xa
  • Plugin Update URI: false — WordPress sẽ không gọi về máy chủ để cập nhật

🎨 Branding

  • Áp dụng design system TanDoanh Plugins (bảng màu xanh-trung tính, card bo tròn, shadow nhẹ)
  • Logo và admin CSS overrides đi kèm plugin

📝 Notes

  • Chỉ hỗ trợ WordPress single-site (Network: false)
  • Cloud storage (Google Drive / OneDrive / Dropbox / S3) chưa có trong bản này
  • Sẽ hỗ trợ cloud trong tương lai qua self-hosted OAuth flow (operator tự cung cấp Client ID / Secret)