Changelog
Tất cả các thay đổi đáng chú ý của plugin SL Migrations được ghi lại tại đây.
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ông —
installer/slm-installer/ctrls/classes/class.ctrl.s3.funcs.php(cleanupFiles()): bản trước hard-delete artifact +rrmdir(DUPX_INIT)xóa nguyên folderslm-installer/(gồmmain.installer.php+ CSRF token) ngay tại chunkSTEP_CLEANUP_TMP_FILEScủa step 3. Nhưng sau đó vẫn cònSTEP_SET_FILE_PERMS,STEP_FINAL_REPORT_NOTICESvà step 4 final report — mỗi cái là một POST riêng tớimain.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óainstaller.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).
- 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:
[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 fail —
installer/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&& falsebỏ 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 escaping —
wp_json_encode()bên trongonclick="..."sinh ra dấu"không escape → SyntaxError JS. Wrapesc_attr(wp_json_encode(...))tại:slm-modern/views/_row.php— nút Installer, Archive, Bothslm-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):
- Path traversal trên upload import src/Ajax/FileTransfer/ImportUpload.php:117-126 —
$_FILES["file"]["name"]chỉ qua regex\.(zip|daf)$+sanitizeNSCharsNewlineTabs(không strip../). Admin có thể ghi.zipra ngoàiSL_MIGRATIONS_PATH_IMPORTS. Fix:basename()trước khi ghép path. - CSRF state-change actions slm-modern/ModernPackagesController.php:96-159 + views/packages/main/packages.php:25-58 —
create_from_temp,action=stop-build,action=clear-messageschấp nhận GET/POST không nonce → dụ admin click link để force_delete package. Fix: thêmwp_nonce_field('slm_modern_legacy_actions')vào 3 form (s2.scan1.base.php× 2 +packages.php) +wp_verify_nonceở cả 2 đường vào (modern controller + legacy view). - Password leak vào trace log — 3 chỗ log plaintext credential:
- src/Package/Import/PackageImporter.php:309 — archive password → log
(length=N)thay vì raw - classes/entities/class.package.template.entity.php:251 — installer password → tương tự
- src/Utils/Crypt/CryptBlowfish.php:189,243 — log raw
$string(plaintext/ciphertext) khi encrypt/decrypt fail → chỉ log length + error message
- src/Package/Import/PackageImporter.php:309 — archive password → log
SHOULD FIX (Tier 3 MEDIUM):
- Latent SQLi
find_hash_keyclasses/package/class.pack.php:3038 — concat$hashtrự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 → wrapesc_html()+ cast(int)choid. - Privilege-escalation
quick_fixctrls/class.web.services.php:650-672 — endpoint capCAP_BASICcho phép set ANY property củaGlobal_Entityqua$_POST['setup']['global'][key]→ user CAP_BASIC có thể đổipackage_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_running5 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 1get_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)sangsetTimeoutchain 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.jsonread src/Compat/slm-progress-api.php:165-200 — file vài MB, được đọc + JSON parse mỗi tick poll. Cachewp_cache_setkeyedmd5(path:mtime)namespacesl-migrationsTTL 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-93 —
ControllersManager+RESTManagerchỉ 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-192 —
customLegacyMapping()đượ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+getTableEngineListduplicate query classes/class.db.php:278-329 —getTableEngineListexecute query 2 lần ($wpdb->query()rồi$wpdb->get_col($query)).getTableCollationListdùng 2-step query+get_col() vô lý (SELECT trảnum_rowskhô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 quaclean-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_runningkeys mismatch (release blocker) ctrls/class.web.services.php:1441,1444 — sau khi consolidate 5 query, code push vàocac_ids/err_idsnhư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_fixwhitelist typo ctrls/class.web.services.php:653-660 — viếtajax_protocol_customnhưng property thực làcustom_ajax_url(class.global.entity.php:182) → quick-fix dialog "Custom AJAX URL" silently fail. Fix: đổi sangcustom_ajax_url.- OAuth token plaintext leak vào trace log src/Utils/OAuth/TokenEntity.php:108 —
wp_json_encode($this)dump nguyênaccess_token+refresh_tokenplaintext khi refresh fail. Trace log readable bởi user CAP_BASIC quawp_ajax_sl_migrations_get_trace_log→ token exposure. Fix: chỉ logstorage_type+expiredstatus. - Missing CAP_CREATE check cho
create_from_tempslm-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êmCapMng::can(CapMng::CAP_CREATE)gate. renameInstallerFilechạy mỗi frontend pageview src/Core/Bootstrap.php:75 — hookinitpriority 20 chạyExpireOptions::getUpdate(transient lookup + DB write) trên mọi pageview frontend dù không cần. Fix: wrap vào$isContextualgate cùng với ControllersManager/RESTManager.
Files changed
Security:
src/Ajax/FileTransfer/ImportUpload.php— basename path traversalslm-modern/ModernPackagesController.php— CSRF nonce + CAP_CREATEviews/packages/main/packages.php— CSRF nonce + CAP_CREATEviews/packages/main/s2.scan1.base.php— wp_nonce_fieldsrc/Package/Import/PackageImporter.php— không log archive passwordclasses/entities/class.package.template.entity.php— không log installer passwordsrc/Utils/Crypt/CryptBlowfish.php— không log plaintext khi crypt failsrc/Utils/OAuth/TokenEntity.php— không dump OAuth token plaintextclasses/package/class.pack.php—find_hash_keyprepareviews/tools/recovery/widget/recovery-widget-selector.php— esc_html package namectrls/class.web.services.php— quick_fix whitelist + custom_ajax_url
Performance:
ctrls/class.web.services.php—is_pack_running1 query + đúng response keysassets/js/slm-progress-poller.js— adaptive poll backoffsrc/Compat/slm-progress-api.php— cache scan.jsonsrc/Core/Bootstrap.php— context gate + renameInstallerFilesrc/Utils/Autoloader.php— memoize mappingclasses/class.db.php— bỏ duplicate query- 10 file
assets/css/*.cssminified (27% smaller)
[1.8.1] - 2026-05-19 (Encryption Key Hotfix · Legacy DupPro Cleanup · Footer)
🐛 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.x → version_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():
- Default storage (Local/DefaultLocalStorage.php:49-50):
max_packages=20,purge_packages=false← site nào cũng bị vì site nào cũng có Default storage entity. - SFTP storage (addons/ftpaddon/src/Models/SFTPStorage.php:37-54):
password='',username='',server=''← chỉ site có cấu hình SFTP từ trước mới bị.
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ạiupdateStorages()— gỡ hẳn (gọiupdateFromGlobal()resetmax_packages=20,purge_packages=falsemỗ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 tablesl_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.
🎨 Footer modern UI căn từ mép trái
Trước đây footer trên các trang page_sl-migrations có left: 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+usernamekhông được trống - SFTP:
server+usernamekhông được trống, và phải có ít nhất 1 trongpassword/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_LENGTHtrê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ọcCOALESCE(..., 0)cho cả 2 cột. - Trace log download
fpassthrudisabled (ctrls/class.web.services.php:1058) — fpassthru bị disable trên một số shared host quadisable_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 + usernameaddons/ftpaddon/src/Models/SFTPStorage.php— validate server + username + (password OR private_key)classes/package/class.pack.database.php— COALESCE NULL table statsctrls/class.web.services.php— fallback chunk read khi fpassthru disabledsrc/Core/Upgrade/UpgradeFunctions.php— rewrite gọn còn 3 method (performUpgrade,updateDatabase,storeDupSecureKey)src/Core/MigrationMng.php— callupdateDatabase()không paramsrc/Models/Storages/Local/DefaultLocalStorage.php— xoáupdateFromGlobal()orphanslm-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— wrapRecoveryPackage::setRecoveablePackage()slm_modern_recovery_reset— wrapSLM_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 → #1d4ed8vớ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ị
disabledvới tooltip "Không thể xoá — đang là Recovery Point. Reset Recovery Point trước." - Server-side guard:
ajaxDeletereject 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.x → version_compare('1.7.x', '4.0.0', '<') = true → RecoveryStatus::isRecoveable() reject mọi package → getRecoverablesPackages() 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_VERSION và IMPORT_BRIDGE_MIN_VERSION).
🐛 Fix Dashboard widget link 404 "Gói không tồn tại"
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 " thô (UI global)
SLM.modal.confirm({body: ...}) hiển thị " literal thay vì dấu nháy do double-escape: escapeHtml() chuyển " → ", 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.phpsrc/Models/Storages/AbstractStorageEntity.php— purge skip Recovery Pointsrc/Package/Import/PackageImporter.php— lowerIMPORT_*_MIN_VERSIONassets/js/slm-ui.js— fix modal escape bug
[1.7.4] - 2026-05-18 (Dashboard Widget Link Fix · Modern Recovery Set/Reset)
🐛 Fix — Dashboard widget "Recently Packages" link 404
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:
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.
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
- Status dot (đã/chưa thiết lập) tự load qua
Backend wrapper trong ModernPackagesController (file đã có nonce pattern slm_modern):
wp_ajax_slm_modern_recovery_status— đọc state, không destructivewp_ajax_slm_modern_recovery_set— wrapRecoveryPackage::setRecoveablePackage()wp_ajax_slm_modern_recovery_reset— wrapSLM_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.
[1.7.3] - 2026-05-18 (Timezone Fix · Sticky Footer · Plugin Row Meta · Landing Page)
🐛 Fixes
Timezone hiển thị sai trên Packages list: cột
Createdshow 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ùngdate_i18n()— legacy API coi timestamp đã pre-offset, hiển thị raw UTC. Fix: chuyển sangwp_date()(modern API tôn trọng WP timezone) + parse string với suffixUTCđể 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: fixedcho#wpfooter(inline vào<head>qua hookadmin_headpriority 1) +padding-bottom: 60pxcho#wpbody-content. Bỏ approach flex trên#wpwrapvì 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)
- README →
- 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-migrationstrê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_VER→1.7.3- Plugin URI đổi từ
https://phatdev.net/→https://phatdev.net/sl-migrations - Inline critical CSS trong
<head>quaadmin_headpriority 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-contentbị épmin-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ớiflex: 1 0 autocho content vàflex: 0 0 autocho 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:
#backtotopcủa pluginwp-extrađược inject inlinebottom: 15px, sau khi pin footer xuống đáy thì nút này bị footer che. Fix: overridebottom: 70px !importantchỉ trong scopebody[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
.dupfiles: import file.duptừ 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ủaAbstractStorageEntity, bỏ sót property của subclass. Fix: dùngstatic::classđể late-static-binding lấy đúng class runtime. - Import Settings — robustness:
MigrateSettings::importStorages/Templates/Schedules/Settingskhông có try/catch, 1 entity malformed kéo đổ toàn bộ import. Fix: bọc từng item trong try/catch + validateis_array($data)+ checkisset($data['id'])trước khi map id. AbstractStorageEntity::__wakeup():isset(null[$key])ngầm khiCryptBlowfish::decrypt()thất bại — silently bỏ qua, config về default mà không log. Fix: explicitis_array($config)guard + fallback[].AbstractStorageEntity::settingsImport(): validate$datalà array, épconfigluôn là array (nếu source export sai cấu trúc), try/catch quanhsetValue()để 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.duptừ các phiên bản / nguồn khác nhau.
🔧 Technical
SL_MIGRATIONS_VERSION,SLM_ASSETS_VER→1.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
.dupthậ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 subclassSFTPStorage.
[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 adminbox-sizing: border-boxtạo content area = 0px → checkbox "Enable" bị clip, filter-links bị overlap. Fix: overrideheight: auto; min-height: 44px; align-items: center. - Template Edit — Installer → Branding row:
<select>cómin-height: 40pxtrong 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.cssglobaltextarea { min-height: 100px }khiến#filter-pathsvà#filter-extsquá cao gây layout overflow. Fix: scope riêngmin-height: 60px; height: 80px.
🔧 Technical
SLM_ASSETS_VER,SLM_MODERN_VER,SL_MIGRATIONS_VERSION→1.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.phpgọ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épdisplay: 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_VER→1.7.0,SLM_MODERN_VER→1.7.0(đồng bộ với plugin version)ModernPackagesController::normalisePackage()ưu tiêngetCreated()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êntoplevel_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:00canh 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 Lam→Steven Lamtrê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>, đổisnapcreek.com→phatdev.nettrên toàn bộ installer + library files
🛠️ Technical
SLM_ASSETS_VERbumped to1.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'trongControllersManager
[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 namespaceVendorSLMigrations\ - Constants
SL_MIGRATIONS_*/SLM_PRO_*; hookssl_migrations_*; text domainsl-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)