What was fixed
- •Updater errors: file and
version.json write failures, patch and change preview not supported, repository unreachable and connection successful.
- •Update screen: server response error, "N files changed · N commits", done, applying, job registered, timed out and rollback complete.
- •Job queued notices: the message that an update, patch or rollback job has been queued.
- •The privileged worker (cron) now leaves its progress messages and its success and failure notifications in the right language.
Changelog screen (`/changelog`)
- •Relative times such as "5 months ago" only existed in Korean and English; they now come in 13 languages.
The empty-list message, the "AI translation" badge, the "no translation — shown in KO" note and "showing only the latest N" are translated as well.
- •The screen language is now the language the site actually uses, not just the session value (
?lang=, session, cookie, default language).
Before, a first visit fell back to Korean, so Korean labels leaked into Japanese and English sites.
- •Long identifiers and paths (text without spaces) no longer overflow the card and slide underneath the install button on the right.
Good to know
- •The privileged worker has no logged-in user and therefore no language. It writes its messages in the site's default language.
(Before, it never started the translation system, so keys such as updater.write_failed could be printed as they are. The worker now starts it.)
- •Server logs (
error_log) and developer debug logs are read by operators, so they were left as they are.
- •On an installed site, the updater that runs an update is the old one already installed, so these improvements first show up in the update after the move to 2.8.2.
v2.8.1
2026-10-12 · 7 days ago
Better multilingual support (app name, search data, `?lang=`) · updates now protect a site's own core changes
Multilingual
- •The app name and description in "add to home screen" and the install prompt now appear in the viewer's language. Before, the stored value (for example Japanese) was shown as it was.
Translations go under keys such as pwa.front.name in rzx_translations. manifest.php now only hands over to the official output.
- •Structured data (JSON-LD): translations of the settings values are looked at first. We fixed the second breadcrumb item that also contained the site name, and "Home" that appeared in English in every language.
- •Statistics widget: the number and the unit (
suffix) now take per-language values just like the label. A single value works exactly as before.
- •The offline screen and the last-resort message of the service worker now come in Korean, Japanese and English.
The service worker cache version went from v5 to v6, so devices that already installed the app get the new screen.
- •"No search results" in message search was hard-coded in Korean; it now comes in 13 languages.
Search engines and AI
- •We now output
<meta property="og:locale"> and the alternate languages (og:locale:alternate). When a page is shared on social media, it tells which language the page is in.
- •Public system pages drawn by code are now included in
sitemap.xml (they were missing).
- •The first line of
llms.txt (the site description) now appears in the viewer's language.
- •The per-page robots setting (the admin screen had the input, but nothing ever read it) is now actually applied.
Fixed problems
- •The
?lang= value is accepted only after checking it against the list of supported languages. Before, an unsupported value stayed in the cookie for a year,
and opening that language later could switch a visitor to that screen against their will. The language selector follows the same rule.
- •Widget builder: the problem where translations for languages that have no input field on screen were erased every time you saved.
- •Community message search: names are stored encrypted, so no name returned any result. The search now decrypts first and then matches.
Updates now protect a site's own core changes
- •If you list paths in
storage/custom/update-preserve.txt, one per line (# is a comment, folders are allowed), updates will not overwrite those files.
Use it to protect things you put directly into core files, such as a site-specific login rule. Absolute paths and .. are not accepted,
and if the file cannot be read, the update does not stop; it carries on with the default preserve list.
- •Good to know: the side that runs the update must already have this feature to benefit from it. It applies from the update after you move to 2.8.1.
What changed
The payment module already had one file per provider, but "which providers exist" was written into the code in several places.
Adding a provider meant changing the payment manager, the settings screen and the webhook.
- •Drop a folder (
gateway.json plus the implementation file) into rzxlib/Modules/Payment/Gateways/ and the core recognises it by itself.
The provider choice, the key fields and the webhook address on the admin settings screen are generated from that specification.
- •A site still picks one provider on the settings screen (as before). This is not a feature for using several providers at the same time.
- •How to add a provider is written up in
docs/PAYMENT_GATEWAYS.md.
Square
- •Card payments, refunds and lookups, saved cards (for recurring charges) and webhooks (with signature verification) are supported.
- •The card input form goes through cardholder authentication (SCA) and shows the reason on screen when it fails. 13 languages.
- •The admin settings screen has a new "Connection test" button. In test mode it runs a small payment, a lookup and a refund
to confirm that your keys are right. No real money moves, and it does not run in live mode.
- •Until you choose Square, nothing changes for any site.
Existing providers (PAY.JP and Stripe) stay as they are
- •We did not touch the implementation files of the two providers. Old settings (single key, v2, v3) are still read as before.
- •Even if the specification files cannot be read for some reason, a safety net makes PAY.JP and Stripe be created the old way.
- •Using the real settings of live sites and many synthetic settings, we compared the old and the new code and confirmed that they pick the same provider, the same mode and the same keys
(scripts/test-payment-parity.php).
Reading payment settings per site
When one database holds a separate payment setting for each site, the payment module now reads its own site's setting.
Before, it was not defined which site's setting would be picked. If a site has no setting of its own, it behaves as before.
Good to know
- •A shared webhook address was added (
/api/webhook-payment.php?gateway=…). The existing PAY.JP and Stripe webhook addresses are unchanged.
- •The provider list on the settings screen keeps its old order, and providers that come in through a specification are added after them.
What was the problem
The sitemap (sitemap.xml) and the AI guide (llms.txt) only contained what the core knows about.
For a plugin to add its own pages, the only way was to edit core files directly, and that edit
disappeared with every update. On top of that, the core guessed how to read the plugin table, so it went quietly wrong in places.
- •Real-estate listings were published only at a separate address (
/properties-sitemap.xml); unless something pointed to that address,
search engines could not find it.
- •Market items had never appeared in the sitemap, because of a query that looked for a column that does not exist.
The error was swallowed, so nobody noticed.
- •Some sites published shop addresses in the wrong form, so search engines indexed addresses that lead to pages that do not exist.
Plugins now publish their own pages
- •If a plugin writes
seo in its plugin.json, the core reads it and merges it into the sitemap and the AI guide.
It works like widgets, layouts and intranet modules, and the core wins when addresses clash.
- •Addresses for several languages and the language markers are added by the core. A plugin only has to give its addresses.
- •One broken plugin no longer brings down the whole sitemap. Only that plugin's share is left out.
- •Addresses of disabled plugins drop out automatically; the pages of a disabled plugin cannot be opened anyway.
Chinese alternate addresses were being ignored
The sitemap wrote the language marker as zh_CN, but the standard is zh-CN, so Google ignored that line entirely.
The pages themselves were written correctly, so the same site was saying two different things in two places.
The alternate addresses for Simplified and Traditional Chinese are now recognised properly.
AI guide per language
- •
llms.txt used to exist in the default language only. It now produces language editions such as ?lang=en.
Item names and descriptions come out in that language, and the head of the guide says which edition it is.
- •On multilingual sites, AI answer engines can now read this site to users in each language area.
Employees can see their own HR card (intranet)
- •The HR card that only HR staff could see can now be viewed by the employee too. It is reached from the profile menu at the top.
- •It is view only. A flow where you request a correction and HR staff approve it is in preparation.
- •Evaluations are not visible to the employee. The same goes for awards and disciplinary actions. For attached documents only the list is shown.
- •You cannot open someone else's card by editing the address bar; whose card it is is decided only by the login information.
- •You can also check who has viewed your own HR information.
Use the board screens inside a different shell
Every link on a board screen is built from a single value ($boardUrl), but the core always overwrote that value.
So anyone using a board inside an admin screen, like the intranet, had to swap text inside the already-rendered page character by character.
If the core changed the shape of a link even slightly, it would go quietly wrong.
- •If the caller sets the base address beforehand, that value is used. If not, nothing changes from before.
- •The gear (board settings) link is also decided in one place. Before, the list, FAQ, Q&A and link skins each built their own.
Layouts can live inside a plugin
- •Layouts are looked up first in the core
skins/layouts/, and if missing in the plugin's plugins/{id}/skins/layouts/.
This is the same rule as for widgets and intranet modules, and the core wins when names clash.
- •Plugin layouts also appear in the admin layout picker.
- •Until now, a plugin-only layout had to be placed in the core folder. An update does not delete it,
but if the core later ships a layout with the same name it gets overwritten, and moving the plugin to another site meant carrying the folder over by hand.
Plugins declare intranet modules themselves
The list for the intranet screens (left menu and entry routes) existed only in a single core settings file.
For a plugin to add its own module, a core file had to be edited, and that change disappeared on update.
- •If a plugin writes
intranet_module in its plugin.json, the core reads it and merges it in. It works like menus and system pages.
- •The value can be an array written directly, or a file path such as
"data/intranet-module.php".
- •The core wins when names clash. A plugin cannot hijack a core module to change its default behaviour.
- •All five places in the core that read this list now use the new method. Even if an update overwrites the settings file, plugin modules stay.
Japanese era names (和暦) in date input
- •On Japanese screens, choosing an era (令和, 平成, 昭和, 大正, 明治) and entering the year number fills in the Gregorian year automatically.
There is no need to convert a value you remember by era, such as a date of birth, every time.
- •The day-of-week display field is left as it is.
Board layout setting not reflected on screen
- •Even when you chose a layout per board in the admin, the screen showed the site's default layout.
The place where it was saved and the place where it was read were different.
- •Fix: when a board is saved, the setting that the screen reads is written as well.
Existing boards look exactly as they do now until they are saved again (the look does not suddenly change).
Posts were not saved when you wrote them
- •Saving a post skipped the title column, so on sites where that column still exists saving failed.
The screen only showed "Unexpected end of JSON input".
- •Fix: if the column exists, it is filled in as well. Sites that dropped the column behave as before.
Board search
- •Search looked at the old column, so on sites that dropped it, the whole list would not open the moment you entered a search term.
- •Sites that kept the column had a problem too. Editing a post did not update the column, so search only matched the text from before the edit.
- •Fix: search now uses the translation table. It now finds the language you are viewing and translated content as well.
Posts not visible in lists, widgets and My Page
- •The practice-area tab widget and the board grid widget always showed an empty list (the error was being swallowed inside).
- •"My comments" on My Page left out board comments altogether.
- •The link board skin always had an empty title.
- •Fix: all of them now read the title and body from the translation table. Thumbnails are also found again from the body.
AI translation and new installs
- •We fixed AI translation of posts, which failed for every translation job while reading the original text.
- •We fixed the sample posts added at a new install, whose original-language title and body were not put into the translation table, so the title looked empty.
Other
- •We fixed the case where the list would not open when the board sort was set to "by recommendations" (it referred to a column that does not exist).
Other people's text went straight into the dialog
The shared result dialog in the admin screens pasted menu titles, skin names and server messages into the page as-is.
Putting something like <script> into a name could make it run when another administrator opened that screen (stored XSS).
- •Fixed: every piece of text in the dialog (heading, body, button, confirmation line) is now converted so it appears as plain characters before being inserted.
- •The notice shown when the database connection fails now uses a translated message and passes through the same conversion.
Dialog buttons appeared in Korean in every language
- •"Confirm" and "Cancel" were hard-coded in Korean inside the JavaScript. They now appear in the language being viewed.
The server supplies the wording for the current language and the script reads those values.
Admin wording in 13 languages
Korean strings hard-coded into the design (skin), menu and page-settings screens were replaced with translation keys and filled in for 13 languages.
- •Skins: results of rename, duplicate and delete; the confirmation that "skin files and settings are permanently deleted"; image upload results
- •Menus: the notice that a system page is only removed from the menu; the confirmation that "the linked page (board) is deleted too"
- •Page settings: file type, save failure and duplicate URL notices
Private posts leaked through the list
A post marked private still showed its title, cover image and excerpt in the list. Only the body was hidden.
- •Fixed: in lists too, only the author, an administrator, or someone who entered the password sees the title and preview.
- •Peeking at the content through the attachment URL is blocked as well; attachments now apply the same check.
- •Previous/next post titles on the reading screen are hidden too.
- •Widgets that pull lists (board cards, grids, tabs and so on) now exclude private posts from the query itself.
The first screen took too long to appear
On phones the first characters took a long time to show. Fonts were the cause.
- •Font CSS (Google Fonts 152KB + Pretendard 29KB) was blocking rendering.
The page now paints with system fonts first and swaps in web fonts as they arrive.
- •Pretendard is loaded only on Korean screens. Japanese screens put a Japanese font first, so Pretendard was downloaded in fragments and thrown away.
- •Two small stylesheets (text scale, board body) are inlined instead of being fetched as files.
- •On a first visit the page cache (service worker) made the browser read the document twice — that is gone.
- •Width calculation for the horizontal scroll menu is deferred until the page has loaded (it used to freeze the page for 0.5 s).
Letting search crawlers and AI follow language pages
- •Language links now point to real URLs (
?lang=…). They used to be JavaScript links that crawlers could not follow.
Admin screens
- •The page list showed the same page once per language; those rows are now merged into one.
- •Editing only a menu title used to wipe the translations for other languages. Translations are now cleared only when the linked page changes.
- •Scheduling a blog post in the past used to be saved as is; it now shows a notice and leaves the status unchanged (13 languages).
Notifications and wording
- •The subject of community message notifications was hard-coded in Korean. It now goes out in the recipient's language.
- •The link text on the cookie notice bar was too dark to read; it is brighter now.
Housekeeping
- •Removed admin view folders that had been duplicated by mistake (
admin/site/site, admin/components/components).
The originals are the ones actually in use. The distribution build excludes these folders as well.
Private boards were listed in the sitemap
Addresses of boards that only administrators can see (such as intake inboxes) were included in the sitemap.
Search crawlers got only 403 (Forbidden) from those addresses, and the existence of private boards was exposed.
- •Fix: only boards that non-members can view are listed. The criterion is boards whose list and post read permissions are set to "All".
- •Secret posts, and boards set to be excluded from search (noindex) in board settings, are also left out.
- •A page with the same name as a board is judged by the board, because the board claims the address first.
- •Pages in multiple languages are also listed in the sitemap.
- •Pages you do not want listed on a particular site can be entered in the
sitemap_exclude setting. llms.txt follows the same setting.
Sent emails went to spam or bounced
- •Emails were missing the send time (Date) and the unique message ID (Message-ID), so receiving servers were likely to flag them as spam.
Both are now included, and the body is sent as base64. This also prevents long lines from being cut and bodies from being truncated by a "." at the start of a line.
- •When the recipient address was on the same domain as the sender (e.g. a copy sent to the company address), that domain's mailbox rejected it as spoofing.
Only in that case, the email is now sent from the SMTP account address, and replies still go to the original address.
Stronger SEO and AI search settings
- •Added operator information fields (structured data Organization) to the admin SEO settings. Items without a value are not output.
- •Per-page SEO settings are now actually used, and a representative address (canonical) is set when the same content can be reached at several addresses.
- •Added a Bing site verification code field. ChatGPT search uses Bing's index.
- •Descriptions are trimmed to fit the display width of search results, cutting at the end of a sentence rather than in the middle.
Language display cleanup
- •The 404 screen was always shown in Korean. It now appears in the language being viewed (Japanese, Korean or English; other languages fall back to English).
- •Fixed "just now" in the notification bell, which was hard-coded in Korean.
- •Japanese screens now use Japanese fonts first. The per-language font helper is always loaded, so screens that bypass the layout (such as share pages) no longer stop because of fonts.
- •The "Preferred language" list in My Page no longer shows languages that have no text.
- •The language of the screen being viewed at sign-up is saved as the preferred language (it used to be empty). Password reset emails are also sent in that language.
Input and login
- •Dates such as date of birth are now entered in three boxes: year, month and day. Previously the display varied with the browser language.
- •Fixed reCAPTCHA verification being skipped on login and password recovery with some skins.
Payments
- •Test keys and live keys are stored separately for each payment gateway. Test mode is just a selection, so keys are no longer lost when switching.
Widgets
- •The hero widget's button can now open in a new window.
- •Repeating items in the widget builder can also be written per language (globe button).
Build
- •Configuration files (.env) and their backups are never included in the distribution, whatever their names.
- •Backup files left while editing (
.bak, including those with a date after the name) are not included either.
- •Scripts used only for headquarters operations (site sync, hosting, mail server, terms updates, etc.) and crons used only by plugins that are not in the distribution have been removed from the distribution. Scripts that customer sites need, such as the update, install and translation workers, are still included.
- •After the zip is built it is checked once more, and publishing stops if any configuration or backup file is found.
- •Test scripts no longer write passwords in the file; they read them from the settings.
The release name and date did not change after updating
Only the number went up, and the title of the previous release stayed as it was.
The admin screen showed things like "2.7.9 · Japanese fonts · contract renewal notice…", with the number and the name out of step.
From the screen alone you could not tell what you had received.
- •Cause:
version.json is a file that is deliberately not overwritten, to protect each customer's own settings.
So the updater fills it in itself, but it swapped in only the number and the time and left the title and release date alone.
- •Fix: the title and release date of the new release are now read from the package that was received and applied too.
For partial updates that have no package, they are read from the distribution list (manifest.json).
Per-customer settings (github and so on) are preserved as they are.
Note — even after upgrading to this release, the screen may still show the old name. The one that runs the update is
the updater of the release that is installed right now.
It will display correctly from the release after the one that receives 2.7.10.
Build check
The widget builder freeze in 2.7.8 (fixed in 2.7.9) passed the regression tests untouched.
That was because the existing checks only looked at "does the file exist · does the string appear".
- •Now the script that goes into the builder is actually generated and syntax checked (
node --check).
- •If any code puts a multi-line file straight into a JS string, the build is blocked, pointing at the line number.
- •We tried it with a file that reproduces 2.7.8 and confirmed that the check really does catch it.
Symptoms
- •Not a single widget shows in the palette
- •Widgets you had already placed are not drawn on the canvas — videos, images and the like seem to have disappeared
- •Saving does not work
Cause
In 2.7.8 we gathered the font declarations into one place, partials/fonts.php. But the spot that builds the builder preview was
inside a single line of a JS string, and the 33-line font declaration went into it as is.
The moment the line broke, the string was cut off and the whole builder script died before it was even read.
Because it is the kind of failure that kills the entire file, it looks not like "some features are odd" but like the builder doing nothing at all.
2.7.5 and earlier do not have this problem.
How we fixed it
We kept the font definitions in that one place, fonts.php, and only when inserting them into the builder we fold the output onto a single line and handle the quotation marks.
There is still only one place to edit fonts.
Default fonts now based on Japanese
Until now the default font was based on Korean. Our main users are in Japan, yet the screens looked awkward,
and changing the language did not change the font, so Japanese text was being set in a Korean font.
- •The defaults are now Mincho for headings and Pretendard JP for body text.
- •On the language settings screen you can specify a font for each language. If you do not, that language gets a suitable default.
- •The same rule applies to all admin, member and front screens and to every skin (default, modern, minimal, shop).
Contract renewal and free
There were cases where a contract expiry was only noticed after it had passed. Now you are told in advance as the renewal date approaches.
- •Upcoming renewals and the end of free provision are raised as notifications before the date arrives.
- •It runs from a single cron line (
cron.d-voscms-contract-notice), and the install guide now explains how to register it.
Card payment
Payments that went through Japanese card-issuer authentication (3-D Secure) were passing authentication but then having the charge declined.
- •PAY.jp blocks a token created for authentication from being used as it is. After authentication passes, the server has to make a finishing call
before the token can be used for a charge, and that one step was missing.
- •We also opened a way to check in advance whether the customer information still exists on the payment provider's side — this prevents the failure of attempting a charge with a deleted customer.
Notification bell and refund
- •In the header notification bell, text such as "No notifications" and "minutes ago" that was hard-coded in Korean now changes per language.
- •We cleaned up the remaining fixed wording in the refund policy notice.
Accounting
Korean-won transactions were getting mixed into the Japanese corporation's books, so the totals did not match.
- •Vouchers in a currency other than the book currency (yen) do not generate journal entries. The record is kept but does not go into the books.
Distribution cleanup
- •Backup files created during editing were shipping in the distribution (names like
filename.php.bak.number).
The exclusion rule only filtered .bak and missed names with a number appended. We now also block .bak., .orig and *~.
- •We removed the THEVOS AI service sales materials (price list, bundle calculation, payment reconciliation cron) from the distribution.
Everything that reads them was already removed, so in the distribution they were files nobody used.
"DB migration required"
039_ai_knowledge_base creates the tables for the NaitoWork X store-consultation AI,
yet it shipped in the distribution as is. On top of that, the last line of this file was
ALTER TABLE rzx_shop_inquiries ADD COLUMN IF NOT EXISTS ai_meta ...
rzx_shop_inquiries is a table created by the vos-store plugin.
The distribution has no vos-store — the core tried to modify a table of a plugin that does not exist.
So on every new install: 4 tables are created and it fails on the last line → since it never reached the end,
it is not recorded as "applied" → the banner stays up permanently, and pressing it fails again at the same spot.
We removed AI consultation (RAG) from the distribution entirely:
039 · rzxlib/Core/AI/{Knowledge,Embedding,Generate} ·
scripts/{ai-kb-ingest,ai-rag-test,cron-ai-consult,hanesbot-ingest}.php
AI translation (rzxlib/Core/AI/Translation) stays. It is the engine of the paid vos-ai plugin
and follows the same rule as accounting and hosting: "the engine lives in the core, and the plugin opens the feature."
"1 plugin is unlicensed (vos
The core holds a list of "bundled plugins that work without a license", and it was out of date.
What the code knew as bundled | vos-autoinstall · vos-marketplace |
What actually ships as bundled | vos-autoinstall · vos-community |
vos-marketplace dropped out of the distribution and vos-community took its place,
but only this list was never updated. We bundled it and then raised a warning because we did not know it was bundled.
So it cannot happen again
The cause is that the bundled list is written in two places —
the build script's exclusion list and LicenseStatus::BUNDLE_PLUGINS.
We added a cross-check step to the build. If the two disagree, no zip is created and the build stops.
It is not something people should have to remember.
The write button was missing on boards
In Q&A and FAQ, even people who were allowed to write had no button. There were two causes.
- •Admins were blocked — when checking write permission, we asked "is this a member?" first and only then looked at admins.
Admins are not in users, so they were stopped at the first gate. We reordered it so admins pass first.
- •With no posts at all, the button vanished too — the button lived inside the list,
so when the page switched to the "no posts" view the button went with it. The first person to write could not write.
We moved the button out of the list.
- •FAQ even looked at
admin_id directly — it ignored the board's write permission setting.
Board titles and subtitles showed in another language
A Korean screen showing only the subtitle in Japanese.
- •Translations were never read. The title and subtitle are saved in the skin settings as a single string,
while the translations sit separately in rzx_translations. The screen just printed the saved string.
- •There was no board distinction. The translation key was
skin_config.subtitle_text, so Q&A and FAQ
shared the same slot. Editing one changed the other.
We split it into board.{board number}.{item}.
- •The body was drawn before the locale was set. The core builds the body first and attaches the header afterward,
but the language was decided in the header. We moved the decision to the body side so it happens first.
Core migrations were missed at install
install-core.php did not look at database/migrations at all.
A freshly installed site was missing a few tables from the start, and it crashed later when that feature was turned on.
Migrations that were silently skipped (continued from 2.7.4)
2.7.4 handled single-line comments (--), but multi-line comments (/ /) remained.
A semicolon inside such a block tied the file into one chunk, so not a single line ran
and it was still recorded as "applied". We confirmed real damage — one install was missing 9 tables entirely.
A -- or /* inside a string is not a comment. We strip comments while counting quotation marks.
Other
- •Shop header — added "All categories" at the top of the category pulldown.
People who wanted to go in and browse had nowhere to go.
- •Widget builder — split repeater (
repeater) editing into a separate file.
- •Intranet sidebar — the accounting label is now looked up in all three variants (salon, shopping mall, general business).
Looking at only one made the menu show the raw key whenever a different accounting was in use.
- •My Page — dashboard cleanup.
- •Paid plugin report — the license check now also sends paid plugins that are switched on.
Ones that are merely installed but not switched on are not sent — money attaches only to the ones that are on.
Headquarters
config/system-pages.php went into the release package whole, so the admin "System pages" of a customer's installation
got six menus that you can click but that have nothing behind them.
📥 downloads | Release package downloads — only headquarters distributes them |
📋 changelog | It is the headquarters' product history |
📧 contact | Customers build it themselves with a widget (it is not a system page) |
💼 recruit · 💃 casts | Only for the night-work portal (NaitoWork X) |
🎟 coupons | The marketplace plugin provides it |
- •Added build step
[4e/8] (strip-system-pages.php). After removing, it checks the syntax with php -l —
if this file breaks, the whole admin screen dies.
- •The system pages that remain in the release package are eight: home · terms · privacy · data-policy ·
refund-policy · hosting-terms · tokushoho · funds-settlement
Plugin system pages
- •Added the
site.pages.blog text to 13 languages.
Even if a plugin registers a system page, if the core has no text for it, site.pages.blog shows up on screen as it is.
- •(The fix to
vos-blog's own plugin.json goes out through the marketplace — it is a plugin that is not in the release package)
Sites that are already installed
If the admin "System pages" shows an item you do not use, delete that block from config/system-pages.php.
The page content (rzx_page_contents) is not deleted — it only disappears from the list.
① Migrations that start with a comment were skipped entirely
- •A comment in
DatabaseMigrator::splitSql() said "split after removing comments", but
it actually only split on semicolons.
- •If the first line of a SQL file is
-- description, the whole file becomes a single fragment,
gets caught by str_starts_with($stmt, '--') in the execution part, and not a single statement runs.
- •When 0 statements run, there are 0 errors, so it is treated as
success and recorded as applied.
Once recorded, it never runs again, so the table stays missing for good.
- •The damage covered every file that starts with
--. Measured: of 9 files, 6 ran 0 statements and 2 ran partially.
- •It now removes
-- and # line comments and / / blocks before splitting.
A -- inside a string is left alone (if a value such as 'a -- b' were cut, data would break).
② A fresh install did not run the incremental migrations at all
- •The install script ran only
migrations/core and migrations/migrations
and missed the top-level database/migrations/*.sql. The updater, on the other hand, tracks only the top level.
- •So on a fresh install, 034 to 042 were missing from the start and stayed empty until the next release came.
- •At install it now runs the top level too, and records it as applied (so the first update does not run the same ones again).
Sites that are already installed
Please press Run migrations once under Admin → Settings → Update.
Entries recorded wrongly will not run again, so if a table is empty you can check with the following.
SELECT migration FROM rzx_migrations; -- recorded
SHOW TABLES LIKE 'rzx_home_notices'; -- actual
What was taken out of the release package
- •
vos-cp (service control panel) and vos-shopping (online store) were riding along in the release package.
Both are newly made plugins, and they simply were not on the exclusion list.
- •
vos-cp is a panel for THEVOS's own services. There is no reason for it to be in a customer's installation
- •
vos-shopping is still in development (v0.1.0), and the rule is that plugins are bought from the marketplace
- •
vos-review was taken out too — for the same reason (the owner's instruction).
- •Only two plugins go into the release package:
vos-community and vos-autoinstall.
Everything else is obtained from the marketplace.
What was added to the core
- •A two-tier shopping header (
skins/layouts/shop) — an all-categories pull-down, search, a cart preview, and
an avatar menu after login. We put "All categories" at the very top of the category pull-down so there is a way back
- •Earned points, store credit and member grade are shown on My Page. The names use the values you have set
- •Plugin widget support — if a plugin has a
widgets/ folder, the core builder recognizes it automatically
- •A shared postal-code address search component — the order form and member information use the same one
- •A notification bell in the admin top bar
- •Cleanup of the admin console menu groups (
config/intranet-modules.php)
- •Widget builder repeater field support
What was fixed
- •The problem where multilingual input fields overwrote the original data
- •A bug in recording the store-credit ledger
- •The problem where the release update turned hosting pages into 404
What to check on installed copies
On sites that installed from an old release and have only received updates, folders of plugins that are not used remain
(an update does not delete plugin folders). Anything shown as not installed under Admin → Plugins
can be deleted from plugins/ together with its folder.
Changed
- •Unified routing of service application URLs —
/service/hosting, /service/mail and /service/domain → /service/order via 302 (until dedicated application pages are ready, they guide visitors to the integrated application page, and can be replaced by dedicated pages later).
Notes
- •The core fixes of 2.7.1 (multilingual hreflang, preventing silent updater failures, merging the pricing
/pricing into /service/order) were already correctly included in the 2.7.1 package. 2.7.2 is a rebuild that adds the service application routing cleanup on top of it.
- •The release package includes only the pure core, as a rule (paid and vertical plugins excluded). The headquarters sites (voscms.com and thevos.jp) are not targets of the release self-update; they are deployed from source/git.
SEO
- •Multilingual hreflang — a self-referencing canonical per page + automatic hreflang for supported languages, per-language indexing with
?lang=xx, 301 normalization of the default-language ?lang to the bare URL, and an extended sitemap.
Fixed
- •Silent updater failures fixed — failures to write core files or version.json are now handled as exceptions. When applying fails because of insufficient permissions and similar, you now get an explicit error and an automatic rollback instead of a false "SUCCESS".
- •Guarded against the 500 crash on the pricing page where a multilingual field was passed to htmlspecialchars as an array.
Changed
- •Extended the reCAPTCHA v3 per-form toggle (blog_comment and others).
- •The settings "Owner console notifications" tab is shown only when vos-store is active.
- •Merged the pricing page
/pricing into /service/order (301), and the "Unlimited system" plan is admin-only.
Package contents
- •The release package is trimmed to a pure core — only 3 core plugins (
vos-autoinstall, vos-community, vos-review) are bundled. The vertical plugins (night-work / salon / POS), widgets and the skin (story) are all excluded. General-purpose features such as age verification and the cookie banner live in the core (rzxlib), so they are included.
Added/Changed (core widgets)
- •location-map — supports keyless embedding (
map_embed), removing the dependence on an API key, and links the map language to the locale
- •practice-tabs (practice-area tabs) — a new bundled widget
- •Global page width setting (
widget_page_width) — unifies the width of widget/document pages
Site-specific widgets that are not bundled (bundled:false) (hero-slideshow, overlap-cards, office-intro, page-hero, content-head, consult-cta, board-grid, clients-logos, the board profile skin) are not included in the core release package.
Removed
- •The prepaid widgets (
prepaid-charge, prepaid-pass, prepaid-purchase) — excluded from the release because vos-prepaid is not bundled
Added (settings and core)
- •The age verification gate was promoted to a core general setting
- •Background opacity control for the cookie consent banner
- •Per-language logo images
- •Bank account information in the business details + a bank transfer notice
- •An "Accepted cards" notice on the payment screen (brand icons) + admin setting
Fixed
- •Localization of page titles and subtitles into the viewer's language (page-scoped db_trans), and content_width not being applied
- •Many widget/admin/page bug fixes and 13-language additions
Added
- •Automatic cleanup of obsolete files — an update that overwrites with the full-package zip cannot delete files removed from the source, so remnants (orphans) stayed in installed copies (for example, the monolithic
settings.php that was split and removed in a refactor remained in old installs). We added an explicit list-based mechanism to clean them up.
- •
config/obsolete-files.php — a cumulative list of paths to delete (installs that skipped several versions are cleaned in one go).
- •Right after the updater's
applyUpdate(), cleanupObsoleteFiles() safely unlinks the files on the list. It never touches preserved targets (.env, storage and so on), path escapes, or directories.
- •First registered:
resources/views/admin/settings.php.
Fixed
- •The dashboard 'System settings' link — the quick-action link pointed to the legacy path
/settings, which had been split off and removed, so it went to a wrong (fallback) page. Corrected to the settings entry point /settings/general. (Missed in the 2.6.22 build.)
Fixed
- •The dashboard 'DB migration required' banner — CSRF token invalid — the retry button did not send a CSRF token to
/update-api.php (run_migrations), so it was rejected with 419. It now ensures $_SESSION['csrf_token'] and sends it in both the header and the body.
- •The banner did not disappear even after migrations ran —
DatabaseMigrator::recordMigration assumed a batch column and a patch_ prefix, so on the installed-type schema (migration/version/executed_at) the record INSERT failed and the prefix also made file-name matching go wrong. Fixed to record the file name as it is, matching the actual columns. (This was the cause of migrations leaving only an "OK" log and not being applied during the 2.6.20 update.)
Fixed
- •Raw keys in the sidebar of core pages such as admin permission management — because the intranet layout was activated only from the route_prefixes in
config/intranet-modules.php, the core page /staff/admins on a site without the matching plugin (for example, without vos-salon installed) was rendered with the salon intranet sidebar and exposed the salon.intranet. and salon.section. keys as raw text. Fixed.
- •Added a shared helper
resolve_intranet_module() — the intranet is activated only if the module's lang resources (the plugin) are actually installed; if not, it falls back to the core admin-sidebar.
- •The duplicated detection logic in
_head.php / admin-sidebar.php was unified into the helper. Behavior is unchanged on sites with the plugin installed (salon, store and so on).
Fixed
- •Point level save error — fixed the failure with
Unknown column 'group_id' when saving a level in the admin point module. The cause was a schema conflict of point_levels/member_points between core/004 and migrations/019(v190) (the CREATE TABLE IF NOT EXISTS in 019 could not add the new columns to an existing table).
- •migration
034_point_system_schema_reconcile (top level, applied automatically on update):
- •
point_levels — added group_id, gave name a default, and a unique index on level (so upsert works).
- •
member_points — added point/balance/total_accumulated/level.
- •All additive (no data loss), with no effect on existing columns or consumers (such as vos-prepaid).
Added
- •3 purchase widgets — an integrated one (
prepaid-purchase, choose what to show: everything / top-up only / pass tickets only) + a top-up-only one (prepaid-charge) + a pass-ticket-only one (prepaid-pass). One shared component purchase-widget.php drives all 3 widgets (zero duplication), branching with the $ppOnly filter.
- •Product background image — drag-and-drop upload (click upload too) in the admin product add/edit modal, with the background applied to My Page and widget cards. Migration 003
image_url.
- •Multilingual product name input + AI translation — reuses the shared
multilang-modal (globe button), saved on top of rzx_translations, and shown in the visitor's language with prepaid_name(). AI translation reuses the core /api/translations endpoint.
- •Card holder name input field added to the payment form (passed to PAY.jp
createToken).
- •Customer selection for manual top-up — switched from typing a user_id directly to autocomplete search by name/phone/email (
customer_search action). The customer status list also shows the customer's name + email instead of a user_id hash.
- •Active toggle — the product active checkbox was changed to a toggle switch.
Changed
- •4-column purchase box — the product card grid is responsive 2→3→4 (
lg:grid-cols-4), and the fixed widget width max-w-3xl was removed → it now fits the width of the placement container.
Fixed
- •Widget builder palette and mega menu height — the builder row height is fixed at
calc(100vh - 88px), so the left categories and flyout automatically take the full screen height and the overlapping of mega menu items is resolved.
Added
- •
api/broadcast.php link validation — if the scheme is missing, https:// is prepended automatically (example.com → https://example.com); if it does not pass filter_var FILTER_VALIDATE_URL, a 400 with a field: 'link' error is returned; anything over 500 characters is rejected.
- •Real-time client-side validation —
type="url", inputmode="url", and immediately as you type:
- •Missing scheme → emerald outline + ✓ + "Added automatically when sending: …"
- •No dot (.) in the host or a broken format → red outline + ⚠ + "This is not a valid URL format"
- •Valid → emerald outline + ✓ + "This is a valid URL"
- •The client also completes it before sending → the preview and the actual sent body are guaranteed to match.
Added
- •Section headers: ✉ Compose message / 📡 Sending channels — visual grouping + descriptive text.
- •Character counters (title
0/255, body 0/2000) — automatic colors: amber at 70% or more, red at 90% or more.
- •Message preview box (dotted border) — renders in real time how the title, body and link combined will look in LINE + a total character counter
n/5000 (red above 4500).
- •Helper text for each field:
- •Title: "Shown in both the member inbox and the LINE notification"
- •Body: "If you write a URL in the body, it is shortened and click-tracked automatically"
- •Link: "Added automatically at the end of the body after a blank line. If you also write it in the body, it will appear twice, so be careful"
- •Status icon at the right of the link input (
absolute) — ✓ / ⚠ shown immediately.
Added
🔗 URL validation rules (a new 5th section):
- •It must start with
http:// / https:// → turned into a click-tracking token
- •A missing scheme is completed automatically
- •Hosts without a dot or with a broken format are rejected
- •500-character limit
- •Example box:
✓ https://voscms.com/event / ✓ example.com/page → auto-completed / ✗ asdfasdf (no dot in the host)
Notes
- •The production (
voscms-com) and salon are synchronized. The API, UI and i18n all take effect immediately.
- •13-language i18n:
community.broadcast.guide_url_, section_message, section_channels*, subject_hint, body_hint, link_hint, link_{invalid,autofixed,valid}, preview_{title,empty}.
Added
- •Layout change of
/theadmin/community/broadcast — the former single card is split into lg:grid-cols-3. The main sending form takes the left 2 columns (lg:col-span-2), and a new guide box takes the right 1 column (<aside>, sticky top-4).
- •6 sections of guide content:
1. 📅 What is scheduled sending? — automatic sending at a future time; use it for nights, weekends and events
2. 🛠 How to use it — 4 steps (choose the audience → check LINE → check schedule → send), with a numbered badge UI
3. ⏱ Sending accuracy — an explanation of the 5-minute cron interval + an example box (10:00 → 10:00 to 10:05)
4. 🆚 Immediate vs scheduled — the difference in behavior between the two modes in card form
5. 📋 Checking the queue — a shortcut button to /history?status=queued
6. ⚠ Cautions — at least 30 seconds in the future, in-app/push cannot be scheduled, it does not work on hosting without cron, cancelling from the UI is not supported
- •Indigo gradient background (
from-indigo-50 to-blue-50) — the guide box is visually distinct from the main card while blending in softly.
- •13-language i18n — about 20
community.broadcast.guide_* keys (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW).
Added
- •Added a "Preferred language" card to
/mypage/settings — a select with 14 options (auto / 13 languages). On save it UPDATEs rzx_users.locale and applies $_SESSION['locale'] immediately.
- •Auth::loginUser hook — at login, if
$user['locale'] exists and the session locale is empty, it fills it in automatically. If the user changed it manually during the session, that value is kept with priority.
- •13-language i18n —
auth.settings.language.{title,description,label,auto,hint,save,success,error_invalid,error_save} (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW).
- •From the next page after the change, the UI is shown in the member's language, and if an admin has set
messages_by_locale on an automatic sending rule, the matching body is sent to that member through LINE.
Added
3 new cards at the top of /theadmin/community/broadcast/history (after the existing 4 cards):
1. Overall click-through rate (CTR) — SUM(rzx_broadcast_clicks.click_count) / SUM(rzx_line_broadcasts.recipient_count WHERE status='sent') × 100. Large emerald text.
2. A/B test (last 30 days) — for sends with variant IS NOT NULL, recipients, clicks and CTR per variant. The variant with the higher CTR is highlighted in emerald.
3. Distribution by locale (last 30 days) — only for sends with source='auto_rule' AND messages_by_locale IS NOT NULL, the locale is matched by comparing the keys of the rule's messages_by_locale JSON with the first 30 characters of the broadcast body → recipients/clicks/CTR per locale. Sorted with ksort.
- •13-language i18n:
community.broadcast.stat_{ctr_title,ctr_breakdown,variant_title,variant_hint,locale_title,locale_hint,empty}.
Notes
- •The
voscms-com and salon trees are synchronized. There are no new DB columns (they were already all included in the Step 4 migrations 008/009/010).
- •The locale matching logic compares the first 30 characters of the body — if the body matches after variable substitution, it is counted in the same locale group. If the body is too short or is entirely filled by variable substitution, the matching rate may drop (a locale column could be added directly to the broadcast row in the future).
Added
- •migration 010 —
rzx_auto_broadcast_rules.messages_by_locale JSON NULL + rzx_users.locale VARCHAR(10) NULL + an idx_locale index.
- •AutoBroadcaster::lineUserIdsWithLocale — looks up pairs of LINE userId and member locale. Environments without the
users.locale column fall back to lineUserIds.
- •AutoBroadcaster::pickLocaleMessage — priority: the member's user.locale → the site default locale (rzx_settings.locale) → the first key of the JSON (if all miss, null → fall back to the A body).
- •AutoBroadcaster::fire — if
messages_by_locale exists, it uses the locale-matched body per member; if not, the A/B variant; and if that does not exist either, the single A. When locale, variant and default give the same body, they are sent as one group by multicast (efficiency).
- •Admin rule modal — "Multilingual routing enabled" and a direct JSON input, expanded through
<details>. Shown pretty-printed. invalid_locale_json validation.
- •13-language i18n —
community.automation.locale_{toggle,placeholder,hint} + fields.messages_by_locale + error.invalid_locale_json (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW).
Notes
- •Steps 1 to 4 of the LINE sending infrastructure are complete (2.6.9 to 2.6.15):
1. Step 1 — CTA · utility stack · manual sending · audience by grade · multi-select (7872c4b, 7c94a55, c4a3325)
2. Step 2 — Automatic classification of member groups (b24979c)
3. Step 3 — Event-based automatic sending rules (60d47cd) + scheduled-send cron (8cf02ad)
4. Step 4 — Click tracking (79c99a84) + A/B testing (86e19b0b) + multilingual routing (this one)
- •Migration 010 was applied to production (
voscms.com). users.locale is a new column — all existing members are NULL (the site default locale is used → the same behavior as before).
- •A My Page UI where members set their own preferred locale is the next milestone.
- •Recommended cron registrations:
/5 /usr/bin/php8.3 /var/www/voscms-com/scripts/process-scheduled-broadcasts.php, 0 3 /usr/bin/php8.3 /var/www/voscms-com/scripts/recalculate-member-grades.php.
v2.6.14
2026-05-30 · 5 months ago
A/B testing (Step 4-B)
Added
- •migration 009 —
rzx_auto_broadcast_rules.message_template_b TEXT NULL, rzx_line_broadcasts.variant CHAR(1) NULL, rzx_broadcast_clicks.variant CHAR(1) NULL + an index (source_rule_id, variant).
- •AutoBroadcaster::fire — if
message_template_b is not empty, LINE userIds are split 50:50 into A/B by crc32 mod 2. For each variant, the broadcast history is INSERTed beforehand (logQueued) → token wrap → multicast → finalize. The same user always receives the same variant on repeated sends (hash consistency).
- •Admin rule modal — an "A/B test enabled" option and the variant B input, expanded through
<details>. A simple single send is the default, and A/B is an explicit opt-in.
- •ClickTracker — propagates the variant meta. If the broadcast_clicks.variant column exists, it is filled on INSERT. If not, a fallback applies (compatible with environments where migration 009 has not been applied).
- •logQueued fallback — also works in environments without the variant column (when migration 009 has not been applied).
- •13-language i18n —
community.automation.ab_{toggle,placeholder,hint} (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW).
Notes
- •The 3 ALTER statements of migration 009 were run separately on production (
voscms.com). Existing rules have message_template_b=NULL (the single-send behavior is kept).
- •Edit a rule → enter variant B under the "A/B test enabled" expander → just save, and from the next occurrence A/B distribution happens automatically.
Added
- •migration 008 (
rzx_broadcast_clicks) — token (8-character base62, UNIQUE), broadcast_id, source_rule_id, original_url (2048), click_count, last_clicked_at, last_user_agent. The same (broadcast_id, original_url) combination reuses the token.
- •
ClickTracker class — wrap($pdo, $siteId, $body, $meta) extracts http/https URLs from the body with a regular expression → INSERT or reuse → replaces them with /t/{token}. consume($pdo, $token, $ua) turns a token into the original URL and increments click_count.
- •Route
/t/{token} (index.php:330) — enters the regular-expression router. ClickTracker::consume → 302 redirect to the original URL, or 404 if there is none. URL token validation ([A-Za-z0-9]{4,16}).
- •Integration with automatic sending rules —
AutoBroadcaster::fire() works in the order logQueued → get broadcast_id → ClickTracker::wrap → multicast → finalizeBroadcast just before sending. The link between token and broadcast_id is guaranteed.
- •Integration with manual sending — in
api/broadcast.php, wrap right after the text for immediate sending is assembled. In scripts/process-scheduled-broadcasts.php, wrap just before queue sending (the broadcast_id is known).
- •Tracking domain setting —
rzx_settings.click_track_domain takes priority, otherwise APP_URL. A short domain (for example s.voscms.com) is possible.
Notes
- •Migration 008 has been applied to production (
voscms.com).
- •Wrap/consume integration test: 2 URLs in the body → 2 eight-character tokens INSERTed → on consume, the original URL is returned and click_count increments correctly.
Added
- •migration 007 (
rzx_line_broadcasts): the columns scheduled_at DATETIME NULL and processed_at DATETIME NULL plus a (status, scheduled_at) index. scheduled_at NULL = send immediately (as before), a future time = queued.
- •
scripts/process-scheduled-broadcasts.php — the cron entry point. It picks up 50 items with status='queued' AND scheduled_at <= NOW() AND processed_at IS NULL → locks them immediately with processed_at = NOW() (transaction) → resolves the audience (all / grade_N, same as broadcast.php) → LineMessenger multicast in units of 500 → updates the status with the result. Recommended cron interval: /5 *.
- •broadcast.php UI: a
[Schedule sending] checkbox + a datetime-local input. When checked, the default is the current time + 1 hour (the user's local time). If unchecked or left blank, it is sent immediately as before. Only the LINE channel can be scheduled (in-app and push stay immediate).
- •api/broadcast.php: handles the
scheduled_at POST parameter. For a future time (> NOW + 30s) with send_line, it only INSERTs rzx_line_broadcasts (status='queued', scheduled_at) without calling the LINE API, and returns scheduled: true, scheduled_at in the response.
- •13-language i18n:
community.broadcast.schedule_{label,enable,hint,success} (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW).
Fixed
- •Corrected the INSERT columns to match the actual schema of
rzx_line_broadcasts (title, body, audience varchar(20), recipient_count, status enum(queued|sent|failed)). Before, the INSERT used column names that do not exist (message_text, recipients_count, sent_count, failed_count, audience_value, partial), and even the fallback failed, so the history of automatic sending was missing.
Notes
- •Migration 007 has been applied to production (
voscms.com). A dry-run confirmed that pickup was 0 items.
- •The production (
voscms-com) and salon trees are synchronized. .env excluded.
- •Registering the cron is up to the hosting operations side (if needed,
/5 * /usr/bin/php8.3 /var/www/voscms-com/scripts/process-scheduled-broadcasts.php).
Added
- •Automatic sending rule table (
rzx_auto_broadcast_rules) — migration 005_auto_broadcast_rules.sql. Rules isolated per site_id, with the columns trigger_type / trigger_filter (JSON) / audience (all|grade_X|custom) / message_template / is_enabled / fired_count / last_fired_at.
- •AutoBroadcaster dispatcher (
rzxlib/Core/Notification/AutoBroadcaster.php) — static entry point AutoBroadcaster::fire($triggerType, $ctx). Match active rules → substitute template variables ({{title}}, {{url}}, {{author}}, {{board_name}}, {{site_name}}) → resolve the audience (same logic as broadcast.php; for the is_default grade, NULL members are included automatically) → look up LINE userIds through rzx_user_social_accounts → multicast in units of 500 → record the history in rzx_line_broadcasts → update the rule statistics.
- •Automatic sending hook for new board posts (api-posts.php:184) — right after a post INSERT (excluding private posts), it calls
AutoBroadcaster::fire('board_post', [post_id, board_id, title, url, author, board_name]). Even if sending fails, the post response is not affected.
- •Admin rule management page (
/theadmin/community/automation) — a card grid UI. Rule CRUD + on/off toggle + a color badge per trigger (board_post=blue, review_created=amber, reservation_created=green). The trigger / filter (JSON) / target grade / message template are edited in a modal.
- •Source tracking columns for rzx_line_broadcasts — migration
006_line_broadcasts_add_source.sql. source ('manual' | 'auto_rule') + source_rule_id (INT) + an index. The history of automatic sending can be told apart from manual sending in the same table.
- •plugin.json routes and migration registration — the
community/automation route, and migrations 004/005/006 stated after 003.
- •13-language i18n — all
community.automation.* keys (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW). Trigger labels, fields, hints, success/error messages and default templates.
Notes
- •Migrations 005/006 have been applied to production (
voscms.com). No rules have been registered yet.
- •The production (
voscms-com) and salon trees are synchronized. .env excluded.
- •Step 3-B (scheduled sending
scheduled_at + cron) is the next milestone.
Added
- •Automatic assignment of the default grade at sign-up (
rzxlib/Core/Auth/Auth.php::register()) — right after sign-up, it looks up the grade with is_default=1 in rzx_member_grades and fills the new member's grade_id immediately. On sites without a member_grades table (legacy or unconfigured), it silently skips via try-catch. Existing NULL members are filled by a separate recalculation script or button.
- •CLI grade recalculation script (
scripts/recalculate-member-grades.php) — based on the sum of rzx_payments payments plus the number of rzx_reservations reservations (only when the table exists), it goes through grades in sort_order DESC order and matches the highest grade that can be satisfied. Grades with no rules set (min_spent=0 AND min_reservations=0) are skipped automatically — only grades with explicit rules are candidates for promotion. Members who do not meet the conditions fall back to the default.
- •Options:
--dry-run (simulation without any actual UPDATE), --user-id=UUID (only a specific member).
- •cron example:
0 3 * /usr/bin/php8.3 /var/www/voscms/scripts/recalculate-member-grades.php
- •Admin "Recalculate now" button (
/theadmin/members/groups) — added an emerald-toned button to the action bar. On click: confirmation modal → AJAX action=recalculate_grades → result notification (target/promoted/demoted/new) → automatic refresh. It includes the same logic as the script inline, so it works even in environments without PHP CLI permission.
- •13-language i18n — new
members.groups.recalculate* keys (ko/en/ja/de/es/fr/id/mn/ru/tr/vi/zh_CN/zh_TW): recalculate, recalculate_hint, confirm_recalculate, recalculate_stat.{total,upgraded,downgraded,fallback}, success.recalculated, error.{no_grades,no_default_grade}.
Notes
- •On production (
voscms.com), verified --dry-run → actual run → re-run after adding rules. All 5 members with grade_id NULL were assigned Normal (default) correctly. VIP/Gold/Silver have no rules, so they stay at 0 members until rules are added (intended).
- •The production (
voscms-com) and salon trees are synchronized. .env excluded.
Added
- •LINE friend-add CTA component for members (
resources/views/components/line-friend-cta.php) — 5 variants (card / button / bar / side-tab / bubble) × 6 positions (bottom-right/bottom-left/bottom-center/top-right/middle-right/middle-left) × 3 sizes, custom colors, display delay, and a 24-hour "remember that I closed it" (localStorage).
- •Configurable settings UI — a new "LINE friend-add CTA" section at the bottom of the LINE channel card in
/theadmin/settings/message-api. Basic ID, enable toggle, variant radio, position select, size, color picker, and delay input.
- •Shown automatically in 5 layouts — automatically included from
skins/layouts/{default,modern,minimal,modern-copy-260413}/footer.php. It is skipped on payment, service application and install flow pages.
Added
- •New component
resources/views/components/utility-floating-stack.php — the cameron_plugin design pattern from thevos.jp (an 80×80 square stack on the right edge + 1px gap + an accent color per icon). Position, size and color can be adjusted by the operator.
- •Both modes coexist —
utility_stack_mode = separate (default, each CTA on its own) / unified (PWA + LINE shown together as one stack). In unified mode, the automatic display of the 5 LINE CTA variants is disabled and the automatic triggers of the PWA install modal (the setTimeout/DOMContentLoaded banner) are disabled; the modal opens only by clicking the PWA icon in the stack.
- •Natural future expansion — with an item registration pattern, other things (a Kakao channel, WhatsApp, newsletter subscription, a dark mode toggle and so on) can join the stack just by adding settings keys.
- •Guaranteed automatic line wrapping — inline CSS
word-break: break-all; overflow-wrap: anywhere; max-width: 72px works around the width limit of Tailwind flex children. CJK labels also wrap naturally inside the box.
- •13-language i18n — the
line_cta. group (22 keys) and the utility_stack. group (12 keys) in resources/lang/{13 locale}/messaging.php. For Japanese and Chinese, spaces at semantic boundaries (such as "アプリとして インストール") guarantee natural line-break positions.
Added
- •
LineMessenger::broadcast(messages) + broadcastText(text) — sends to all friends at once (up to 5 message objects; LINE's sending quota is deducted by the number of recipients × the number of messages).
- •
LineMessenger::multicast(userIds[], messages) — sends to up to 500 specified userIds (infrastructure for group-based sending).
- •
/theadmin/community/broadcast UI improvements — added a "Also send to LINE friends" checkbox (green banner). Independently of push sending, it automatically sends to LINE friends by multicast/broadcast.
- •Sending by member grade —
rzx_member_grades is loaded dynamically and radio options per grade appear automatically (color dot + slug + live count). The audience format is grade_<id>.
- •Members with
grade_id IS NULL are automatically included in the is_default=1 grade — even on sites where no grade is assigned automatically at sign-up, NULL members are naturally included in the default grade's count and sending.
- •
rzx_line_broadcasts sending history table — site_id isolation, method (broadcast/multicast), audience, title/body, recipient_count, line_request_id, api_status/error, status. Rows are INSERTed automatically when sending.
- •Broadcast for
audience=all, multicast otherwise, chosen automatically — collects the LINE userIds of target users whose user_social_accounts.line_friend=1 and sends in chunks of 500 per call.
- •Unified width — in broadcast.php,
max-w-3xl → max-w-7xl (matching other admin pages).
- •13-language i18n — added the keys
send_line_label/desc and audience_grade_group.
Changed
- •
resources/views/admin/dashboard.php — the header comment, the pageTitle fallback and the RezlyX v... update notification banner were all corrected to "VosCMS".
- •
resources/views/admin/partials/admin-topbar.php — corrected the header comment.
Fixed
- •
storage/.update_cache held an old current: "2.6.4" cache, so even after updating to v2.6.8 the dashboard still showed the "new update available" banner — after the cache is deleted, it is recalculated automatically on the next request.
- •Accumulated dev → production changes were not synchronized — besides this session, many files such as install-core.php (patches for defects #1 to #7) and version.json had never been synchronized to production. Synchronized from dev to production in one go with rsync (342 files changed + 196 new, 6.3MB, excluding user data,
.env, storage/, uploads/, vendor/ and .git/).
Documentation
- •
docs/LINE_FRIEND_CTA_DESIGN_GUIDE.md — added Part 3 (actual implementation of the integrated Utility Floating Stack), Part 4 (actual implementation of Phase 1 sending operations), Part 5 (external user scenarios — value analysis of multi-select sending) and Part 6 (the roadmap for Phases 2 to 4). From about 631 to 920 lines.
Regression Test
- •Verifies the existence of
utility-floating-stack.php and the 5 line-friend-cta variant partial files.
- •Verifies the existence of the
LineMessenger::broadcast/multicast methods.
- •Verifies the existence of the migration file
plugins/vos-community/migrations/004_line_broadcasts.sql.
- •Verifies the existence of the 13-language keys
messaging.utility_stack. and community.broadcast.send_line_.
- •Automatically verifies that no RezlyX remnants remain in dashboard.php and admin-topbar.php.
Fixed
- •vos-market license table consolidation — 6 API files (
install, purchase, issue, issue-reply, review, resolve-keys) were querying the wrong {$pfx}vos_licenses table, causing 500 "Server error" on every payment. All license verification now routed to the single master vcs_licenses (managed by vos-license-server).
- •
sync.php removed unverified master-registration security hole — Previous code UPSERTed into vos_licenses without verifying vos_key, allowing anyone to auto-register arbitrary license keys to master via a simple POST. Tracking is now handled by the existing rzx_mkt_sync_reports (per-item).
- •
migrations/007 deprecated — The misdesigned migration that created rzx_vos_licenses is now a no-op (SELECT 1), with reason and change history in comments.
Added
- •4 stat cards — Tracked sites / Sites needing attention (violations) / Multi-domain licenses / Stale (30+ days). Click card = switch to the corresponding tab.
- •Violations tab (item-level row display) — See at a glance which paid widget is installed without a license on which domain. Price, version, vos_key, and last seen are shown.
- •Multi-domain tab — Cases where a single license key is reported from multiple domains (potential license violation).
- •Stale tab — Sites with no sync signal for 30+ days (suspected network block / code tampering).
- •Refined violation definition —
unknown_product (assumed user-developed) and free (price=0) items are excluded from violation judgment. Only paid marketplace items in unlicensed state count as violations.
- •Owner-domain whitelist — Operator's own domains stored as JSON array in
settings.mkt_owner_domains are auto-excluded from violation/multi-domain/stale counts (full overview still visible in the all tab).
- •13-language i18n — Sidebar, cards, tabs, and help text are all properly translated.
Changed
- •
settings/system/updates — Installed-version / latest-version card display "RezlyX vX.Y.Z" corrected to "VosCMS vX.Y.Z".
- •
updates/ajax.php — DB name fallback 'rezlyx' → 'voscms', User-Agent 'RezlyX-Updater/1.0' → 'VosCMS-Updater/1.0'.
Regression Test
- •Auto-verifies no
vos_licenses SQL leftover in vos-market 6 files
- •Detects regression of INSERT security hole in
sync.php
- •Verifies
migrations/007 no-op state is maintained
- •Auto-verifies no RezlyX leftover in 3 updates screen files
Fixed
- •Automatic seeding of
site_url — the install wizard wrote the siteUrl entered by the user only to APP_URL in .env and not to rzx_settings.site_url in the DB, so every feature of a newly installed site that depends on site_url (automatic installation, SEO, mail links and so on) broke. Fixed by adding site_url to the $settings array in install-core.php.
- •Automatic registration in
rzx_site_domains — the install wizard did not add the current install domain to the site_domains table, so SiteRouter could not recognize the domain. The installed domain is now automatically INSERTed as the site_id=1 primary.
- •Automatic writing of
MARKET_API_URL in .env — a new install had no market API address in .env, so InstallerService could not find where to call. It is now written automatically with the default https://market.voscms.com/api/market (single license master operation).
- •Typo in the table name of the market install API —
vos-market/views/api/item/install.php queried a nonexistent table rzx_vos_licenses when verifying a license, so every automatic installation returned a "Server error" 500 response. Fixed to the real table vcs_licenses (the license master, fixed without a prefix).
- •Automatic permission setup for the widgets/plugins/themes directories — after install, the PHP-FPM user could not write to these directories, so automatic installation failed silently. The install wizard now automatically grants setgid and group write (the PHP execution group is detected automatically to cope with varied external environments).
- •Multiple fallbacks in
install-handler.php — in a multisite environment, the site_url lookup used LIMIT 1 without a site_id filter and was unreliable. A three-step fallback of SiteRouter → site_id=1 fallback → $_SERVER['HTTP_HOST'] guarantees domain recognition in any environment.
Added
- •
InstallerService::installTheme() — reads the theme.json of a type=theme product and recursively installs the plugins, widgets and skins in requires. Includes 13-language validation and idempotency (already installed items are skipped).
- •
runPostInstall — right after a theme is installed, it automatically runs the post_install/settings.json / boards.json / pages.json seeds, setting up pages, widget placement and default settings all in one go.
- •13-language enforcement (
REQUIRED_LOCALES) — multilingual objects (name, description, page title, widget config.title and so on) pass only if they contain all 13 keys. A warning is shown for missing locales.
- •Single
vcs_licenses master — all license issuing and verification is managed through the production market (market.voscms.com). Dev and test environments use the same master.
Added
- •Automatic verification of these six defects — 13 items in total, including the install wizard seeding logic, directory permissions, the market API, license server reachability, the InstallerService theme branch and the install-handler multiple fallbacks.
- •Automatic hook in build-voscms.sh — called automatically at the end of the build. If even one FAIL occurs, the exit code is non-zero and the release is blocked. If the same defect recurs in a later build, it is caught immediately.
Documentation
- •
docs/theme-package/THEME_PACKAGE_SPEC.md — defines the VosCMS theme package standard (theme.json). It states the Salon integrated theme example, the detailed post_install schema and the 13-language rule.
Fixed
- •Update backups: keep only one per version —
Backup::createFull() now removes the existing backup of the same version before creating a new one. This resolves the problem where repeating an update or rollback on the same version piled up duplicates of that version in the backup list (only the latest state just before the update is kept).
Fixed
- •Restored the missing
update-api.php in the release package — the build script misclassified update-api.php, the update backend, as a "dev file" and removed it from the release. As a result, the Update tab of every installed release was unusable and showed "File not found". It is no longer excluded from the build and is now included in the release correctly.
- •Fixed login session not being recognized (Unauthorized) —
update-api.php was looking at the default session save path (/var/lib/php/sessions); it now uses the same storage/sessions as index.php. This resolves the Unauthorized error that appeared even when logged in as an administrator.
Changed
- •Core updates use only our own release manifest as the single channel — removed the leftover GitHub tag, token and diagnostics code from
update-api.php (blocking the path where check overwrote the manifest result with a GitHub tag). The marketplace has no Core (plugins only).
- •Stronger security — added CSRF verification and a master (owner) permission check to
update-api.php.
- •Update preserve list cleaned up —
skins and config were removed from the preserve list. Because applyUpdate is copy-only, the core skins and config in the release zip are refreshed, while customer custom files that are not in the zip are preserved.
Changed
- •Removed the duplicate "Check for updates" button — the latest version is now checked automatically when the page opens.
- •Fixed the backup list disappearing on a temporary error → the section is kept and shown as empty.
- •On errors, a "You are currently using the latest version" notice is shown instead of raw messages such as
Unauthorized.
Added
- •13-language
um section added — all locales of vos-community/lang/{locale}.php (ko·en·ja·de·es·fr·id·mn·ru·tr·vi·zh_CN·zh_TW): follow·following·message·block·unblock·report·report_about·report_reason/detail/placeholder·reason_spam/harassment/inappropriate/other·report_submit/sent/failed·msg_title/placeholder/send/sent/failed·block/unblock_confirm·block/unblock_done·cancel·failed·network_error
- •Full i18n of
user-mention-menu.php — all hardcoded Korean in the PHP HTML replaced with __('community.um.*'); JS runtime strings emitted as a PHP object via UM_LANG = <?= json_encode(...) ?>, with the umT(key, name) helper substituting the :name placeholder
- •Mention menu allowed for guests — reworked the guard structure in
community-overlays.php: admin/API paths fully blocked, while user-mention-menu is included even for guests (follow/report prompt for login); page-action and username guards remain login-only
Changed
- •
boardPreloadUsers(array $rows) added (board/_init.php) — bulk-loads the user_id list in a single IN query, shared via the $GLOBALS['_boardUserCache'] cache
- •
boardAuthorMention() extended — added data-user-username·data-user-avatar attributes (links the story URL /story/{username} precisely, shows the avatar image). Prefers a preload-cache HIT, falls back to a lazy single lookup on miss
- •
board/list.php·board/read.php·skins/board/qna/_list-full.php — call boardPreloadUsers() before rendering to prevent N+1 queries
Fixed
- •Display parser extended (
resources/views/system/changelog/index.php) — added rendering for code fences (``), tables (|...|), markdown links (text), #### subheadings, and horizontal rules (---`). Resolves earlier versions (2.4.1·2.3.8·2.3.7 etc.) that used this syntax being shown as raw markdown
- •Per-version locale fallback — changed from fetching the whole list in a single language to a per-version basis (
current locale → en → ko). Every version now appears in any language, and versions not yet translated show a fallback-language badge
- •Block boundary alignment (
rzxlib/Core/Changelog/ChangelogParser.php) — prevented a dateless ## [Unreleased] header from being absorbed into the preceding version (e.g. 1.5.0) by splitting blocks at every ## [
Added
- •Migration 056
dist_downloads table (site_id·version·ip·user_agent·referrer·utm_*·is_bot·created_at)
- •Download endpoint
api/dist-download.php — instead of a static /download/*.zip direct link, requests go through it to record per-version / daily / referral (referrer·UTM) stats and then 302-redirect. Bot UAs are counted separately; version whitelist (path-traversal safe)
- •Admin stats page
resources/views/admin/dist-downloads.php (master only, 13 languages) — summary cards (total/today/7d/30d), per-version bars, daily trend (30d), top-10 referrers, UTM. Self-contained SVG/CSS charts (no external library)
- •Changed the
download-hero widget's download link to the counting endpoint
- •Added the "Download Stats" admin menu (config/admin-menu.php, master only)
Added
- •
hero-cta-text — top text (typing) area. Defaults match the live hero-cta2 content (13 languages); user settings take precedence
- •
hero-cta-button — download button (auto-detects the latest version + counts downloads, no config) plus an optional marketplace button (one button if left empty)
- •Setting-form labels and defaults in 13 languages (builder
getLabel object support)
- •Unified the download button color/background with hero-cta2 (#2563eb)
Added
- •Drag a palette card onto the canvas to add a widget (SortableJS
pull:'clone' group + real block swap in the canvas onAdd). Works alongside the existing click-to-add and ↑↓/handle reordering
- •The flyout stays open during drag + canvas drop-zone highlight (
wb-drop-active)
Changed
- •Removed the page wrapper gap between widgets (
type-widget.php py-2 → 0)
- •Stabilized synchronous comment AI translation — nginx
fastcgi_read_timeout 150s (board vhost) · PHP-FPM request_terminate_timeout 150 · set_time_limit 150, OllamaProvider keep_alive 30 min (model stays resident, removing cold loads)
- •Added copy buttons for the Webhook URL and Redirect URI on the Message API and Social Login tabs (13 languages, with a non-secure-context fallback)
Added
- •Migration 036 Added
withholding_tax · consumption_tax · tax_basis columns to partner_earnings / payouts
- •Migration 037 Added
fx_rate · fx_amount · fx_currency columns to payouts (USD-converted remittance records)
- •Migration 038 Added
vcs_developers.paypal_email column
- •Helper
rzxlib/Core/Helpers/MarketTax::calculate() — Japan standard deduction model
· Sale price is tax-inclusive (消費税込), market remits consumption tax directly to NTA
· freelance → 10.21% withholding deducted / invoice_eligible → input tax credit processing / no_invoice → no consumption tax remittance
· Unclassified / not entered → forced to freelance (conservative: underpayment can be refunded, overpayment cannot be recovered)
- •Backfill script
scripts/market-tax-backfill.php — recalculates existing earnings/payouts (protects status='paid', supports --dry-run)
- •Settlement 5-split display: Revenue → Commission → Consumption tax deduction → Withholding (freelance) → Net payout
- •
rzx_mkt_settings new keys: min_payout_amount · consumption_tax_rate · default_commission_rate (settings take priority everywhere, hardcoded fallbacks removed)
Added
- •
/theadmin/market/partners/payouts settlement modal — prompt() × 2 + alert() → single modal
· vcs_developers JOIN auto-prefills bank/branch/account holder/account number/business type/T-number (PII enc: auto decrypt)
· 5-split auto-calculation + Japan tax law basis chips (freelance_withholding etc.) + article references (§204, Invoice System)
· In-page toast notification, modal closes and auto-reloads
- •Auto USD conversion for accounts outside operating country (Migration 037)
· Frankfurter API auto exchange rate (storage/cache 24h TTL) + admin manual override (bank TTS)
· Automatically selects method=paypal
· For foreign nationals: bank account card hidden, PayPal email card shown (based on bank_country == site_country)
- •Developer settlement amount specification (
/mypage/developer/earnings)
· Full balance → amount input + full amount button + minimum amount validation
· Revenue history 5-split columns, settlement info box (applicable tax law per business type shown)
- •USD conversion and exchange rate shown in payout history (both admin and developer screens)
- •Replaced revenue trend chart with SVG bar chart (fixes div bar visibility issues)
Added
- •
tax_review_status-based lock policy (developer profile)
· not_submitted / rejected → editable
· pending / approved → edit locked (padlock + notice)
· rejected → editable + "will re-enter review queue on save" hint
· Server-side protection HTTP 423 Locked (blocks UI bypass)
- •
update.php partial update safety — dynamic UPDATE only for transmitted fields (previously: full overwrite risked NULL-ing untransmitted fields)
- •Official verification site links (admin verification screen + developer profile)
· 🇯🇵 NTA Corporate Number Publication Site (https://www.houjin-bangou.nta.go.jp/)
· 🇯🇵 NTA Qualified Invoice Issuer Publication Site (https://www.invoice-kohyo.nta.go.jp/regno-search/)
· 🇰🇷 Korea Hometax Business Registration Status Inquiry
· One-click copy button next to business number / T-number
Added
- •New registration (
/mypage/developer) — nationality selection moved to top of form, operating country comparison notice
· country == site_country → 🏠 Domestic form (business type / bank account)
· country != site_country → 🌐 Foreign national form (auto freelance + PayPal email required)
· Alpine.js isDomestic getter, OPERATING_COUNTRY global constant
- •Profile edit (
/mypage/developer/profile) — same structure ([feedback_admin_user_parity] rule)
· Foreign nationals: business section hidden, settlement info = PayPal email only
· Nationality locked (contact admin to change), mode chip shown at top
- •Register API server-side enforcement — foreign national
paypal_email required, forced freelance, business/account inputs ignored
Added
- •
page.php token auto-replacement — applied uniformly to all system pages
· {{COMPANY_NAME}} {{COMPANY_ADDRESS}} {{COMPANY_PHONE}} {{COMPANY_EMAIL}} {{COMPANY_REG_NUMBER}} {{COMPANY_REPRESENTATIVE}}
· {{SITE_NAME}} {{SITE_URL}} {{DOMAIN}}
· Auto-mapped from admin business_ or site_ settings
· Custom page_title / page_subtitle also go through token replacement
- •
{{DOMAIN}} priority — official_domain (official domain for terms/policies) → site_url host
· Even on dev server vos.21ces.com, terms body retains voscms.com display
Added
- •
rzx_page_contents terms / privacy (13 rows each) fully replaced — RezlyX booking terms → VosCMS free software terms
- •Terms 18 articles: Purpose · Definitions · Publication & Revision · Rules Outside Terms · Contract Formation · Privacy · Notices · Company Obligations · User Obligations · Service Provision · Changes · Usage Restrictions · License (free license) · Data Ownership & Retention · Post Copyright · Disclaimer (AS-IS) · Liability · Dispute Resolution
- •Privacy Policy 10 articles: Processing Purpose · Items · Retention Period · Third-Party Provision & Consignment · Data Subject Rights · Security Measures · Cookies · Privacy Officer · Rights & Remedy (PPC) · Amendments
- •Japanese law + exclusive jurisdiction of Fukuoka District Court, citing APPI §33·§34·§35
- •Company information uses tokens → single admin edit auto-syncs terms and policy
- •Japanese version designated as the authoritative text, official translations in all 13 languages
- •Company information card — only 3 items: corporate name · address · phone (removed representative name, avoids registration amendment burden on terms revision)
- •Preservation scripts:
scripts/voscms-terms-update.php · scripts/voscms-privacy-update.php
Fixed
- •
menus-api.php in /theadmin/site/menus was 99 lines behind vos, causing missing validation and translation on menu save/edit
- •Synced menus-api.php from vos → market:
· mn_link_target_exists() — shortcut (link) menus saved only after verifying target page (system page · rzx_page_contents) exists
· Menu ID reuse protection — stale rzx_translations.menu_item.{id}.* for new menu IDs automatically cleaned up
· Translation INSERT method INSERT IGNORE → INSERT … ON DUPLICATE KEY UPDATE (allows overwrite)
· On title/url change: old translation clean slate + auto re-registers translations when system page (config/system-pages.php) matched
- •Added
admin/site/menus*.php 4 files to sync-to-market.sh INCLUDES
Fixed
- •Missing
rzx_contact_messages table → added migration 041_create_contact_messages.sql (IF NOT EXISTS, vos idempotent no-op confirmed)
- •Fixed double layout in
contact-messages.php — _head.php already outputs sidebar+topbar but view was including them again, causing double <body> + double main. Cleaned up to vos pattern (header=_head once, footer=_foot once) (market one-off patch)
Added
- •Migration 040
040_create_developer_platform.sql — bulk creates 7 tables: vcs_developers + vcs_developer_earnings/payouts/activity/notes + vcs_review_queue + rzx_mp_items (IF NOT EXISTS, final schema after 030~038 ALTERs applied)
· vos.21ces.com's vcs_developers existed only in dump without a separate CREATE TABLE migration — fixes issue where 030 ALTER was failing on subsequent sites
- •Synced
mypage/profile-sidebar.php — old rzx_mkt_partners query → new vcs_developers query (developer activity integrated in mypage)
- •market
index.php route patch (manual, avoids full sync):
· /developer/ → /mypage/developer/ 301 redirect (login/dashboard/my-items/submit/earnings/profile/logout mapped)
· Routes for mypage/developer/{register|items|items/new|earnings|profile} + mypage/developer/items/{N} + mypage/developer/items/{N}/edit
- •Added migration 040 + profile-sidebar.php to
sync-to-market.sh INCLUDES
- •market DB auto-backed up (
storage/backups/market-YYYYMMDD-HHMMSS/market_vos.sql) then migration 040 applied
Added
- •market.21ces.com operates under a marketplace (3-party) model — separate terms from VosCMS free software terms
- •Terms 18 articles (reflecting marketplace operating rules):
· Article 8 Purchase contract formed between buyer and developer, company acts as payment/delivery intermediary
· Article 9 Digital goods refund 4 exceptions (before download · description mismatch 7 days · malware confirmed · system error) — satisfies Japan Act on Specified Commercial Transactions §15-2 disclosure requirements
· Article 10 Settlement — commission % not specified (refers to "current rate posted separately", allows rate adjustment without terms revision procedure)
· Article 10 Japan Income Tax Act §204 withholding 10.21% / T-number holders' consumption tax paid directly to NTA by market / accounts outside operating country via USD-PayPal remittance
· Article 14 Developer's individual terms take precedence — except for marketplace operational matters (payment/settlement/refund procedures), individual terms override these terms
· Article 17 Liability limit: total commissions/settlements for preceding 6 months
· Article 18 Japanese law + exclusive jurisdiction of Fukuoka District Court
- •Privacy Policy 12 articles (3-party marketplace perspective):
· Processing items — 6 categories: common / buyer / developer (common) / Japan resident / overseas resident / shipping
· Retention periods — member 1 year · transaction 7 years · settlement/withholding 7 years · identity verification 5 years · logs 6 months · inquiries 3 years
· Business consignment — Stripe · KOMOJU · PayPal · Frankfurter · SendGrid · NTA specified
· International transfer — USA (PayPal/Stripe/SendGrid) · EU (exchange rates) · other countries (regional PG/banks) specified
- •Scripts:
scripts/market-terms-update.php · scripts/market-privacy-update.php (UPSERT — INSERT if no row after UPDATE)
- •market DB applied (rzx_page_contents terms/privacy 13 rows each), page-i18n multilingual title/subtitle applied together
Added
- •Fixed missing skin setting mapping (
page_title · page_subtitle) — settings screen input was not appearing on page
- •Newly created missing
_page-title-bg.php partial (resolves previous nginx error log include warnings)
- •Added color option to
title_bg_type + title_bg_color field — automatically applied as color fallback when image/video absent
- •Removed white card box fallback when
content_bg=transparent (fixes max-w-5xl content looking narrow inside card)
- •
--page-content-width CSS variable auto-output — external auto-include components like "About this page" action bar (vos-community) align to same width as page content_width
Fixed
- •Settlement page PDOException — removed reference to non-existent
p.currency column in rzx_mkt_partners
- •Revenue trend chart invisible issue — div bar % height was rendering near 0px in some environments → replaced with SVG viewBox-based chart (minimum 4px visibility ensured)
Added
- •Developer my page item detail screen (
/mypage/developer/items/{id}) — statistics, release history, download trends
- •Developer item edit screen (
/mypage/developer/items/{id}/edit) — same 3-tab UI as items-new, pre-fills existing values, new ZIP upload follows a separate flow
- •New API:
/api/developer/item-update — updates name(JSON), short/desc(JSON), tags(JSON), price, license, repo/demo URL, dependencies, minimum version, icon, screenshots
- •New API:
/api/developer/request-payout — payout request
- •New API:
/api/developer/update-bank — update own settlement account
- •New API:
/api/developer/issue-reply — post a Q&A reply (developer's own response channel)
- •Admin developer detail screen (
/theadmin/developers/show) — business/account/document card + document review (approval/rejection) mode
- •Email verification for registration and login (
/verify-email) enhanced + company field saved in Auth::register
- •13-language official translation keys added (
show_edit_btn, items_edit_*, items_back_detail, api_updated, etc.)
Added
- •Review, issue, and Q&A submission forms exposed directly within widgets (no separate page navigation needed)
- •Only buyers can submit — non-buyers see the form but receive a notice at submission time
- •Q&A replies support threading (replies to replies) — DB migration 035 (
parent_reply_id)
- •New APIs:
/plugins/vos-market/views/api/item/post-issue.php, post-reply.php, post-review.php
- •Core
rzxlib/Core/Helpers/Notifications.php — notification helper (paired with DB migration 034)
Added
- •Full 13-language support applied to all screens:
/theadmin/market/licenses, sync, orders, settings, submissions, partners/payouts, items
- •Marketplace settings screen — "Partner" label unified to "Developer" (display text only; variable names kept for compatibility)
- •Marketplace item name input uses the shared multilingual input modal —
name_json edited simultaneously in 13 languages
- •
plugins/vos-market/lang/* — new plugin-level language pool
- •
resources/lang/*/marketplace.php — all 13 languages translated in native language (compliant with i18n full-translation rule)
Added
- •
033_developer_notes_activity.sql — per-developer notes and activity log
- •
034_notifications.sql — rzx_notifications table (user notifications)
- •
035_issue_reply_thread.sql — rzx_mkt_issue_replies.parent_reply_id column added (threaded replies)
Fixed
- •
/Market?item={slug} slug missing — corrected in items-show by fetching from dedicated single-column query
- •
icon_url column name in the item update API corrected to the actual column name icon
- •Marketplace settings page blank screen — applied missing migration 005 for
rzx_mkt_settings
Added
- •VARCHAR(12)
users.username + UsernameValidator (6+ characters, special character restrictions)
- •Gate modal on first visit after login (AJAX
/api/username-set)
- •Full translations for 13 languages (English fallback removed)
Added
- •
/profile/{uuid} → /story/{username} 301 redirect
- •Context menu label changed from "View Profile" to "View Story"
Added
- •vos-story plugin extracted from core — dedicated to
/story/{username}
- •Facebook-style header + 5 fixed tabs (All / Info / Photos / Followers / Following)
- •Post CRUD + SNS feed (
vos_story_posts, body_format = text/html)
- •TEXT/EDIT toggle + Summernote integration
- •Image attachments (multiple) + Photos tab grid
- •Like + comment system
- •Story skin system (
skins/story/default) + per-user token (/mypage/story)
- •Admin policy page (
/admin/vos-story/policy)
- •Page action bar: i18n + position (end of main content) + content width token
- •2:1 grid (content area only) — right side: ads + friends (Followers/Following tabs)
- •Header action buttons — icon-only ([My Story Settings] / [Edit Profile] / [Follow])
Added
- •D-1: 4-menu sidebar (Dashboard / Story / Account / Messages)
- •D-2-A: New dashboard — 8 status cards + 8-tab nav + account info tab
- •D-2-B: Scraps / Inbox / Friends tabs (each with "View All" link)
- •D-2-C: Saved / My Posts / My Comments tabs (unified board + Story + page)
- •D-2-D: Auto-login (remember tokens) management
Added
- •Common helper
rzx_mirror_external_images() — cURL + SSRF protection + GD resize
- •E-1: Scrap snapshot capture + external image absorption
- •E-2: Absorbed on Story post creation (story_attachments source column)
- •E-3: Absorbed on board post creation
- •E-4: Absorbed on page content creation
- •Image marking:
data-source-original, data-source-mirrored
Added
- •Hosting management sidebar — final [Settings] entry leading to the 4 tabs of the service request page (general/domain/hosting/addons)
- •Refund policy → System Settings → General → separated card (isolated from tax settings)
- •
rzx_settings.refund_policy single-key storage
- •
settings.refund.* keys for 13 languages
Added
- •Migration 009:
page_contents.created_by / updated_by columns
- •Automatically recorded on all page edit screens (admin/customer/system)
Added
- •Post count — recognizes the separate
rzx_translations structure for board posts (board_post.{id}.title)
Added
- •Admin settings for per-hosting storage quota / warning threshold / policy (block/notify/none)
- •My Page usage donut chart
- •
scripts/db-quota-monitor.php background monitor
Added
- •VosCMS reinstall button — admin hosting tab
- •Hosting [Status] change action + log
- •Removed "Server Connection Info" edit form (prevents unnecessary exposure)
Fixed
- •Scrap
add_scrap network error — vos-story API file require conflict
- •Scrap card — outline + delete/edit icons (trash)
- •Post count and list mismatch (missing status filter)
- •Removed nick_name matching (prevents misidentification of NULL user_id seed data)
- •My Page Story menu position — top → main (plugin auto-recognition)
- •Story menu icon in core format (full
<path> tag)
- •Add-on tab settings SELECT missing key column (causing two sections to be hidden)
- •hosting_status action 500 — helper function definition location
- •Page action bar — content width token applied
- •Bug where existing posts disappear when a new post is published (immediate feed refresh)
Added
- •Hosting detail → Domain tab →
+ Add Domain button → modal
- •Options: free subdomain / new registration / connect existing domain
- •WHOIS search (
/api/domain-check.php reused) + per-TLD price display
- •Payment: saved card (reusing hosting PAY.JP customer) or new card (PAY.JP Elements)
- •10% consumption tax auto-split (subtotal / tax / total)
- •On payment completion, append to hosting sub
metadata.added_domains[] (no separate sub created)
- •Auto-refund safety net — on DB processing failure, PAY.JP charge auto-refunded
Added
- •Start date~expiry + Check button (DNS A/CNAME → match our zone or Cloudflare IP range)
- •Auto-renewal toggle + Renewal request button (per domain)
- •registrar_pending / manual_attach_pending: Check button → "Pending" badge
- •"Added Domain" purple badge
Added
- •Hosting management sidebar: 2nd item added new
- •Unified list: type='domain' sub + hosting sub.added_domains[] all displayed in time order
- •Stats cards (total/pending/completed) + filters (status/option/search)
- •Actions: [Mark Registered] / [Process Cancellation] (on cancel: PAY.JP refund + refund row INSERT)
- •Username auto-decryption
Added
- •Hosting management sidebar: 3rd item added new
- •
rzx_payments-based transaction ledger — revenue/refund/net revenue/tax stats cards
- •Japan consumption tax return auto-calculation (inclusive pricing → base = total × 100/110, tax = total × 10/110)
- •Last 12-month trend table + transaction ledger table + filters/pagination
Added
- •My page add-on tab: "Available to apply" section (business mail auto-excluded)
- •Per-price actions:
- •Free (install support): admin notification + order_logs
- •Quote (customization): "Request Quote"
- •Fixed recurring (1-year technical support): card payment modal (
_addon-pay-modal.php)
- •On payment completion, addon sub auto-created + payments entry
- •Already-active sub label match: apply button auto-disabled
- •install_info row right-side [Go to Admin / Go to Site] buttons
Added
- •DB schema:
uk_payment_order UNIQUE removed → uk_payment_key (charge_id) UNIQUE replaced
- •Multiple payment rows per order allowed (hosting + domain + add-on accumulated)
- •On refund: paid row unchanged, separate refund row INSERT (status='refund', accounting integrity)
- •Japan 7-year retention obligation + PAY.JP merchant dispute response standard met
Added
- •composer require minishlink/web-push
- •VAPID-based admin/supervisor push send helper
- •Expired endpoint auto-cleanup + rzx_push_messages auto-logged
- •Call points: pay_domain (domain payment complete), service-order (domain registration request), request_addon
Added
- •Our zone subdomain auto-match (wildcard CNAME recognized)
- •Cloudflare IPv4 15 CIDR range matching (on proxy response)
- •"already_refunded" response treated as success (when PAY.JP has already refunded)
Added
- •Subquery sums
rzx_payments totals (paid − refunded)
- •Hosting base payment + domain add + add-on payment accumulated display
Added
- •
admin_cancel_addon — sub status='cancelled' + PG refund + refund row INSERT
- •PG routing: based on payment row
gateway column (safe even after PG changes)
Removed
- •Per Japan 7-year retention obligation + PAY.JP dispute response policy, all admin UI "delete" removed
- •Trash icon + adminDeleteAddon JS + admin_delete_addon API + 4 labels in 13 languages all removed
- •"Cancel" only — data rows never DELETEd
Fixed
- •Translator placeholder colon-prefix conflict —
[':count' => N] → ['count' => N] bulk-fixed
- •PayjpGateway::refund()
metadata[refund_reason] rejected by PAY.JP → changed to refund_reason
- •service-manage.php admin permission check —
role === 'admin' → role IN ('admin','supervisor') fixed across 5 actions
- •Service order domain tab: refund → accounting card shows negative → SQL logic corrected (status='paid' / 'refund' split sum)
Added
- •ssh.21ces.com Cloudflare Tunnel registered (Public Hostname: SSH → localhost:22)
- •External PC:
cloudflared access ssh --hostname ssh.21ces.com or ~/.ssh/config ProxyCommand
- •Server side: PermitRootLogin no, thevos key registered
Added
HostingProvisioner.php installVoscms() now calls install-core.php sequentially at step 3 → step 4 after zip deploy (previously only step 2):
- •New
runInstallCoreSteps() — session bypass using _db POST fallback + .installed marker verification
- •New
callInstallCore() — HTTP loopback (port 80, leveraging Cloudflare edge SSL; handles missing origin certificate when rate-limited)
- •New
appendHostingEnv() — re-injects HOSTING_* variables after install-core.php overwrites .env
- •Automatically removes welcome.html upon successful installation (so VosCMS index.php takes precedence)
- •Payment flow service-order.php:567-576 — on successful install, automatically records
install_completed_at and install_admin_url in addon metadata (triggers My Page tab display)
Added
admin/service-orders/detail.php:175-260 + partials/addon.php:
- •Two buttons in the "Install Support" addon under the add-on services tab:
- •VosCMS Install (purple) — shown when
install_info exists and install_completed_at is absent
- •Admin Page (green) — opens admin URL in a new tab after installation completes
- •API action
admin_run_voscms_install — directly calls HostingProvisioner.installVoscms(), with DB credentials from metadata.server.db or customer .env as fallback
- •On successful install: addon metadata +
voscms_installed order_log entry recorded
Added
customer/mypage/service-partials/hosting.php:
- •🟢 Site Backup button —
service-manage.php?action=request_backup (new)
- •mysqldump → DB dump + zip(public_html, .env, sql) packaging
- •Signed download URL (HMAC, 10-minute expiry) — api/backup-download.php new
- •Automatic cleanup of backups older than 7 days
- •🟣 Open phpMyAdmin — opens
https://pma.voscms.com/?lang=<user_locale> in a new tab
- •nginx vhost: pma.voscms.com.conf new (auto-routed via Cloudflare Tunnel
*.voscms.com wildcard)
- •User logs in with their own DB credentials
- •DB connection details display (host / DB name / user / password toggle)
- •13-language i18n:
btn_site_backup, btn_open_phpmyadmin, confirm_site_backup, backup_in_progress, backup_ready, f_password, password_notice, f_protocol
Added
customer/mypage/service-partials/_setup-monitor.php:
- •Hosting setup → VosCMS auto-install step separated
- •
system_imported orders (hosting registrations for owner-operated sites such as vos.21ces.com) are activated immediately — monitoring box is not shown
- •13-language new i18n:
setup_step_voscms_install
Changed
rsync exclude additions:
- •5 internal plugins (must not be included in hosting customer sites):
- •
vos-developer, vos-hosting, vos-license-server, vos-market, vos-salon
- •7 reservation migration SQL files (hosting customers do not have the reservation system installed):
- •
001_create_reservation_tables.sql, 011_seed_service_translations.sql, 012_seed_service_translations_p2.sql, 013_add_source_to_reservations.sql, 014_add_payment_status_to_reservations.sql, 015_create_reservation_core.sql (new), 016_alter_reservation_services.sql, 024_reservations_bundle_columns.sql
dev files removal additions:
- •
admin/ directory (offline.html leftover) — empty directory causes nginx directory indexing 403; only root /offline.html is referenced by sw.js, so admin/ is redundant
- •Default encoding →
obfuscate.php (previous ionCube EVAL caused runtime hang; ionCube is used only when USE_IONCUBE=1)
Changed
001_create_base_tables.sql used to create both base and reservation tables in a single file — now separated:
- •Modified:
001_create_base_tables.sql — core 5 tables only (rzx_admins, rzx_users, rzx_member_grades, rzx_settings, rzx_migrations) + core seeds (member_grades defaults, settings defaults)
- •New:
015_create_reservation_core.sql — 9 reservation tables (rzx_categories, rzx_services, rzx_service_options, rzx_reservations, rzx_payments, rzx_point_transactions, rzx_business_hours, rzx_holidays, rzx_staff_positions, rzx_staff) + reservation seeds (business_hours, point_*)
During hosting dist builds, 015 is excluded → rzx_reservations table is not created → admin/dashboard.php:25-27 $hasReservations = false → calendar is automatically hidden.
Sites using the reservation system (voscms.com HQ, RezlyX, etc.) apply both SQL files → no change.
Changed
config/templates/nginx-vhost.conf.tpl + HostingProvisioner addHttpsToVhost():
- •Before: port 80 (HTTPS redirect) + port 443 (actual content) → caused infinite redirect loop with Cloudflare Tunnel + 80→443 redirect
- •After: unified single server block serving both 80/443, same content, no redirect
- •
fastcgi_read_timeout 60s → 300s (prevents nginx timeout during large INSERT operations)
Changed
- •
provision-hosting.php (CLI backfill tool): automatically updates hosting subscription metadata (hosting_provisioned, server.{ftp,db,env}) — previously only _autoProvisionHosting in service-order.php ran
- •Automatically removes 5 internal plugins +
admin/ directory
Added
scripts/install-cli.php new:
- •Backfill for automatic install-core.php invocation after hosting provisioning
- •DB credentials: subscription metadata takes priority, falls back to customer docroot
.env
- •Usage:
sudo -u www-data php scripts/install-cli.php --order=<ORDER>
- •On success: automatically records
install_completed_at in addon metadata + voscms_installed action in order_logs
Fixed
- •Missing
_autoProvisionHosting() call in the card payment branch — added
- •
install_info.admin_pw in payment metadata was displayed in plaintext → unified toggle (👁) pattern on both My Page and admin sides
Migration Notes
- •External hosting customers: no action needed. Post
voscms-2.4.3.zip build, the auto-install flow uses a clean zip.
- •HQ (voscms.com): no change (both reservation SQL files are applied).
- •Existing hosting customers (dwhahn, etc.): recommend clean re-installation via deprovision + reprovision (apply new zip after separation).
- •manual cleanup:
/etc/nginx/sites-available/pma.voscms.com.conf newly added — deploy directly to HQ system (outside rsync).
Added
Called immediately after payment completion in service-order.php:
- •New function
_autoProvisionHosting($pdo, $prefix, $orderId, $orderNumber) — runs outside the payment transaction (DB integrity isolated; order itself is unaffected if it fails)
- •
domain_option = 'free' is set up immediately (Cloudflare subdomain — DNS auto-registration is already complete)
- •
new (waiting for NS change after domain purchase) / existing (waiting for NS change for owned domain) are deferred — admin triggers separately at domain activation
- •If the 'install' addon has
install_info, voscms files are deployed and .env is auto-generated (automating the install-core.php step-by-step session flow (steps 2–5) requires redesigning the entire voscms install pipeline — deferred to Phase 5; user accesses https://<domain>/install.php after payment → proceeds through wizard with DB info pre-injected into .env)
- •ftp/db/env info is automatically saved to hosting subscription
metadata.server → displayed automatically in the My Page hosting tab (no UI changes needed)
- •order_logs actions:
hosting_provisioned / hosting_provision_skipped / hosting_provision_deferred / hosting_provision_failed
Call order: payment commit → _autoProvisionHosting() → _autoProvisionMailDomain()
Added
$actionLabels in the admin service order detail page (detail.php:498-528) expanded from 8 to 31 entries. Fixes raw action codes (e.g. mail_account_added) showing in the activity log.
Newly mapped actions:
- •Mail:
mail_account_added/deleted, mail_address_changed, mail_provisioned/_skipped/_failed, mail_domain_migrated/activated, bizmail_upgrade_request
- •Hosting:
hosting_provisioned, hosting_provision_skipped/deferred/failed, hosting_deprovisioned
- •Add-ons:
admin_add_storage_addon, admin_delete_addon, storage_addon_paid/requested
- •System:
customer_notified, setup_email_sent, server_info_updated, bulk_extended, manual_capacity_period_fix
Color semantics per action: green (completed/positive), amber (pending/warning), red (failed/deleted), blue (info/notification), violet (admin/special), zinc (neutral).
23 i18n keys — services.admin_orders.act_* — added in ko/ja/en (operator page). The other 10 languages fall back to English.
Changed
During hosting auto-provisioning, VosCMS auto-installation only performs file deployment + .env auto-generation. Automating the install-core.php step-by-step session flow (steps 2–5) requires redesigning the entire voscms install pipeline — deferred to Phase 5. After payment, the user accesses https://<domain>/install.php → proceeds through the wizard (DB info is pre-injected into .env).
installVoscms() return value changed — now returns install_url + note message.
Added
HostingProvisioner.php — Main class for automatic setup on hosting order. 9-step transaction (rollback in reverse order on failure):
- •Linux user creation (
vos_<order_number>, within 32-char limit, preserving case)
- •Directory structure:
/var/www/customers/<order>/{public_html,logs,tmp} (chmod 750/2775/750/1770)
- •Disk quota (
setquota -u <user> <soft> <hard>, hard = soft × 1.1)
- •Directory conflict check: rejects if existing nginx vhost /
reserved_subdomains conflict
- •1.9TB SSD
/var/www mount (ext4 + noatime + usrquota,grpquota)
- •Central sudoers (
/etc/sudoers.d/voscms-hosting-provisioner) — www-data runs root commands (useradd/setquota/nginx etc.) NOPASSWD
- •Per-command argument pattern restriction (
vos_, /var/www/customers/, *.conf) — passes visudo validation
CLI tool scripts/provision-hosting.php:
php provision-hosting.php SVC260428AE7476 hotel.com 1GB
php provision-hosting.php --order SVC260428AE7476 # auto DB lookup
php provision-hosting.php --deprovision SVC260428AE7476 hotel.com
php provision-hosting.php --install-voscms <order> <domain> <capacity>
Templates config/templates/:
- •
nginx-vhost.conf.tpl — HTTP + ACME challenge location + per-user FPM socket
- •
welcome.html.tpl — activation welcome page
Added
Each hosting runs PHP under its own user (prevents cross-site compromise) + SSL auto-issued even behind Cloudflare proxy.
config/templates/php-fpm-pool.conf.tpl:
- •
user = vos_<order> / group = www-data — UID isolation
- •
listen = /var/run/php/<order>.sock — per-user socket
- •
pm = ondemand — memory efficient for low-traffic hosting
- •
php_admin_value[open_basedir] — access limited to site directory + /tmp + /usr/share/php
- •
disable_functions — exec, passthru, shell_exec, system, proc_open, popen, pcntl_exec
- •
session.save_path / upload_tmp_dir — isolated to user directory
- •env: HOSTING_ORDER / HOSTING_USER / HOSTING_DOMAIN
Let's Encrypt SSL — Cloudflare DNS-01 challenge:
- •
python3-certbot-dns-cloudflare plugin
- •
/etc/letsencrypt/cloudflare/credentials.ini (root 0600, CLOUDFLARE_API_TOKEN from .env)
- •Issues regardless of Cloudflare proxy / external reachability
- •HTTPS server block auto-added (TLS 1.2/1.3, strong cipher, HSTS, X-Frame-Options, X-Content-Type-Options)
- •HTTP → HTTPS 301 auto-redirect
- •
certbot.timer auto-renews twice daily (already active)
Added
DB + user isolation per hosting order.
- •
hosting_admin MySQL account (created once, ALL PRIVILEGES + GRANT OPTION) — for DB management only
- •DB name/user =
vos_<order> (same as Linux username)
- •Password: 32-hex random (
bin2hex(random_bytes(16)))
- •
GRANT ALL PRIVILEGES ON vos_<order>.* TO vos_<order>@localhost — access to own DB only
- •Isolation verification: other hosting DB / voscms_prod access returns Access denied 1044
VosCMS auto-install (installVoscms() — triggered by --install-voscms or when addon has install_info):
- •Extracts latest
voscms-dist/voscms-X.Y.Z.zip → public_html
- •Auto-generates
.env: APP_KEY=base64:<32bytes> / DB info / HOSTING_ORDER injected
- •Permissions: vos_:www-data, .env chmod 640
- •Calls
install-core.php headlessly (currently 301 redirect issue, to be resolved in Phase 4)
Verified
provision DEMO260429FULL demo07.21ces.com 1GB --install-voscms:
- •✅ User
vos_DEMO260429FULL (UID isolation, www-data group)
- •✅ Disk quota 1GB soft / 1.05GB hard
- •✅ PHP-FPM pool own socket
- •✅ nginx vhost (HTTP + HTTPS, HSTS)
- •✅ Let's Encrypt SSL (
/etc/letsencrypt/live/demo07.21ces.com/)
- •✅ MySQL DB
vos_DEMO260429FULL + user (isolation verified)
- •✅ VosCMS 2.3.9 files + .env deployed
deprovision: All resources cleanly removed (user/home/DB/FPM pool/nginx/SSL).
Added
Instead of the industry-standard anniversary billing (signup date to same date one year later), billing now uses calendar month-end cutoff (next month 1st through end of expiry month). Users who sign up in the same month share the same expiry date, simplifying cron batch processing and accounting close. Follows the Japanese hosting standard (月末締め翌月1日請求) pattern.
- •DB: added
rzx_subscriptions.billing_start DATETIME NULL column (normal billing start date)
- •Helpers:
_proratedFirstAmount (monthly unit ÷ 30 × remaining days), _calendarBillingStart (1st of next month), _calendarExpires (last day 23:59:59)
- •Order form:
hosting_prorated automatically added to hosting payment items (e.g. joining 4/2 with 12-month ¥1,000/mo → first month ¥967 + normal 12 months ¥12,000 = ¥12,967)
- •Domains keep anniversary billing (1-year registration cycle); hosting, mail, maintenance, and add-ons use calendar billing
- •
-1 second → -1 day handling in service-order.php also unified
- •Add-on services (extra web storage) also use the same proration + normal N-month sum, auto-synced to hosting expiry date
Added
Set up Postfix + Dovecot + Rspamd + Roundcube + OpenDKIM + Let's Encrypt on ConoHa VPS (133.117.72.149). mail-tester 10/10, FCRDNS verified. Domain auto-setup on order.
- •Automatic DNS registration (
CloudflareDns.php) — A / MX / SPF / DKIM / DMARC 5 records in one batch
- •DKIM key generation — mx1 SSH
rspamadm dkim_keygen (dedicated ed25519 key storage/keys/mailsync_ed25519, www-data owned, 0700)
- •3-mode provisioning (
MailDomainProvisioner.php): free (subdomain instant) / new (domain purchased, admin activation) / existing (NS change guidance). existing_pending / new_pending / active mode machine.
- •Password: SHA512-CRYPT (
{SHA512-CRYPT}$6$...) — Dovecot-compatible one-way hash. Migration script for old enc: (encrypt function) passwords: scripts/migrate-mail-passwords.php
- •mx1 sync cron (
scripts/mail-sync-to-mx1.php) — voscms_prod → mx1 mail_lookup virtual_users every 5 minutes. Immediate trigger (triggerMailSyncToMx1()) also added for background sync right after mail actions
- •Cron auto-registration:
/etc/cron.d/voscms-mail-sync
- •Notifications: admin (provisioning required) / customer (mail ready) HTML mail templates (
MailNotifier.php)
Added
Managing base mail / business mail bundled with hosting. Fixed duplicate display issue where the same address was stored in both mail subscription + hosting metadata (single source of truth: mail subscription).
- •Webmail button — Roundcube
?_user=address GET parameter for username prefill, target="_blank". URL priority: metadata.mail_server.webmail_url → WEBMAIL_URL env → default https://mail.voscms.com/
- •Email change button — for typo correction. Domain is fixed; only the local part changes. All mail + hosting metadata for the same order is synced
- •Dedup logic (
mail.php) — _seenAddresses displays each address only once, mail subscription takes priority
- •service-detail.php virtual hosting → mail conversion only runs when no real mail subscription exists (backward compatibility)
- •service-order.php no longer saves hosting metadata.mail_accounts for new signups (mail subscription is the single source)
Added
Add-on storage purchase with payment in the my page add-on services tab. PAY.JP customer registration + chargeCustomer.
- •Modal: storage selection (1 GB / 3 GB / 5 GB) + card info input (payjp.js v2 Elements)
- •Register card / pay with different card / return to registered card toggle
- •Expired/declined card auto-detection (
expired_card, card_declined, invalid_* etc. PAY.JP error codes) → card form auto-opens + guidance banner
- •Cardholder name input field added (
addon-card-holder) — stored as customer metadata, not passed to token (PAY.JP v2 Elements does not support name option)
- •Portal pattern:
document.body.appendChild() to escape transform-constrained containers and cover the full viewport (z-[9999])
- •If charge fails after creating a new customer, auto-refund + customer deletion (orphan prevention)
- •Hosting metadata.extra_storage[] accumulated, hosting tab storage gauge shows combined total
Added
Structured 14 articles (definitions, application, fees, auto-renewal, cancellation, service, backup, prohibited use, suspension, maintenance, privacy, liability, changes, jurisdiction) referencing StarServer and other Japanese hosting provider standard terms. Calendar billing + first-month proration + unused-month refund on cancellation policy specified.
- •System page registration:
config/system-pages.php hosting-terms slug, 🖥️ indigo
- •Page editor mapping:
pages-document.php — pageMetaMap['hosting-terms']
- •DB seed: ja (main) / ko / en — 3 languages (rzx_page_contents)
- •i18n:
site.pages.hosting_terms + hosting_terms_edit (editor_title/desc)
Added
- •4 tabs always visible (detail.php) — hosting/domain/mail/addon. Empty state message (empty_*) shown when no data
- •Add-on services tab —
+ Add Storage button (admin free-grant modal, capacity selection instantly creates active addon subscription, billing_amount=0, expires=hosting expiry date) + per-item trash/delete button (soft delete status=cancelled, removes from hosting metadata.extra_storage[])
- •List — applicant display improvement (index.php) — orders by system admins with
supervisor/admin role are shown as user.name + purple subtitle Superadmin: [email protected] format (bypasses decryption failure of old encrypted applicant_name like FKzGx9SE7xG/2)
- •Order creation button (
+ New Order) — UI only, functionality coming later
- •i18n 13 languages:
role_supervisor, role_admin, btn_create_order, empty_, btn_add_storage_addon, btn_delete_addon, modal_add_storage_, confirm_, alert_ and 20+ more keys
Added
- •
subdomain-check.php — DB (reserved_subdomains) priority + Cloudflare API fallback. JS checkSubdomain() calls server and reflects result
- •service-order.php server-side re-validation immediately before payment — prevents client-side bypass
- •
reserved_subdomains auto-import — 11 existing records from Cloudflare zone (vos/bestatech/bt/dev/ec430/ec431/salon/ssh/test/thevos/ai.21ces.com)
- •Menu INSERT IGNORE bug fix (menus-api.php):
- •
add_menu_item: clean up stale translations immediately after INSERT + overwrite with INSERT ... ON DUPLICATE KEY UPDATE
- •
update_menu_item: detect title/url changes, delete all 13 old translations + re-register if system page
- •
rename_menu_item: clean up old translations on title change
- •Fixed regression where old menu (id=38, "VosCMS Introduction") multilingual translations were pointing to the new hosting terms menu
Added
Built the flow: service order → payment complete → automatic mail domain provisioning → customer notification.
- •Added
_autoProvisionMailDomain() call in service-order.php — runs outside the payment transaction (DB integrity isolation)
- •DB:
rzx_reserved_subdomains (zone, subdomain, record_type, reserved_by, reason) — availability cache
- •DKIM key auto-generated, stored in metadata.mail_provision as mode/origin/domain/zone/subdomain/provisioned_at
- •Provisioning mode UI branching (service-detail.php):
pending / new_pending / existing_pending — hides tabs and shows monitoring only (_setup-monitor.php); tabs only visible when active
Fixed
PHP's $arr['key'] ?? [] returns a temporary value, so &$ref does not affect the original array. change_mail_password / change_mail_address were returning success responses but the metadata UPDATE had no effect — password and address changes were silently failing.
- •Changed to index-based direct update pattern:
foreach ($arr as $i => $item) { $arr[$i]['k'] = ... }
- •New mail password storage in service-order.php also changed from
encrypt() (two-way) to mail_password_hash() (SHA512-CRYPT)
Fixed
- •Payment summary bright fluorescent green → cleaner colour:
text-green-400 → text-green-600 dark:text-green-400 (7 locations)
- •Subdomain availability response message i18n key missing (
order.php PHP→JS i18n mapping + :fqdn placeholder unified)
- •Expiry date calculation corrected:
+N months -1 second (same date) → +N months -1 day (exactly 1 day before) → finally unified to calendar method
Database
- •Added
rzx_subscriptions.billing_start DATETIME NULL (dev + prod)
- •
rzx_reserved_subdomains table (Cloudflare zone collision cache)
- •
rzx_page_contents seeded with hosting-terms (ja/ko/en)
Added
All text across the 6 areas (domain, hosting, add-ons, applicant, payment, summary) and the payment completion page has been translated into 13 languages. Client-side alert/modal text is injected from PHP via the var I18N = {...} + t(key, replace) helper pattern.
- •Location: plugins/vos-hosting/lang/{13 locales}/services.php —
services.order.* 200+ keys
- •My page service detail:
services.detail.* 70+ keys, service-detail.php
- •Admin order management:
services.admin_orders.* 115+ keys, admin/service-orders/
- •Korean labels stored in DB (hosting plans, maintenance, add-ons) are mapped by settings ID via the
_localizeLabel($sub) closure and db_trans() — consistent handling on both my page and admin side
Added
When the payment button is clicked, the order details (domain/plan/add-ons/applicant/terms) are validated and a summary modal is displayed → user confirms → payment proceeds. Prevents mistakes such as registering an empty subdomain.
- •order.js —
showOrderConfirmModal(), closeOrderConfirmModal(), confirmAndPay()
- •Enhanced removal of remaining modal layers after payment completion (DOM deletion + body/documentElement overflow reset)
Added
When "Installation Support" (_id='install') is checked in add-ons, a form appears for admin ID/email/password/site name. Password is masked + eye icon toggle + "change after installation" notice. Values are stored as install_info in subscription metadata, displayed on both my page and admin side.
- •Input: _addons.php
- •Display: my page/admin partials/addon.php (blue background box + password toggle)
Added
The memo that was tied to the add-ons tab has been moved to the order (rzx_orders.admin_notes) level. It is always displayed as a 7-line textarea above the activity log on the right, regardless of whether add-ons exist. AJAX update_order_memo action added.
Added
A section field (top/main/bottom, default main) was introduced to visually separate core menus from plugin menus. A divider is automatically inserted between zones, and if there are no displayable menus in a zone, the entire zone including the divider is skipped.
Admin sidebar:
- •top: Dashboard / User Management / Site Management / Auto-installer (
vos-autoinstall)
- •main: VosCMS License (
vos-license-manager) / Hosting Management (vos-hosting)
- •bottom: Plugins / Settings
- •Changes: config/admin-menu.php, admin-sidebar.php
My page sidebar:
- •top: Dashboard / Profile / Messages
- •main: Services (
vos-hosting auto-added) — accumulated automatically as other plugins add entries
- •bottom: Settings / Password / Account Deletion / Logout
- •Changes: config/mypage-menu.php, mypage-sidebar.php
Plugin menu registration: section + position fields in plugin.json's menus.admin / menus.mypage entries specify the zone. The services menu has been moved from core mypage-menu.php → vos-hosting/plugin.json (auto-registration).
Added
The license validation API and developer portal API were in the core, meaning they would be exposed on all deployed voscms sites. Both APIs have been separated into a new headquarters-only plugin.
- •
vos-license-server (scope: hq_only): 7 license endpoints moved (check, register, verify, stats, updates, register-plugin, _init)
- •
vos-developer (scope: hq_only): 7 developer portal endpoints moved (register, login, submit, my-items, earnings, update-version, _init)
- •
api/_plugin_dispatch.php — core wrapper checks rzx_plugins.is_active before dispatching, returns 404 if inactive
- •Core
/api/license/.php and /api/developer/.php retain only the wrapper (external compatibility maintained)
- •
/api/notices.php with HQ_HOSTS env whitelist added (responds only from HQ voscms.com domain)
- •The build script (
build-voscms.sh) already excludes api/license, api/developer, api/notices.php, and vos-license-manager
Fixed
- •Hosting contract expiry +1 day bug (
+12 months → +12 months -1 second) — applied to 4 locations: subscription/free/paid/bank
- •Business email: 2 or more cannot be added (selector was catching the X button)
- •Order summary: additional storage not aggregated (missing
name attribute on storage select)
- •Hosting plan label not localized — resolved by matching
service_hosting_plans._id with db_trans()
Added
_init.php now queries rzx_translations directly for board.{id}.{title|description|header_content|footer_content|seo_keywords|seo_description} (current locale → en → source). On any page — list/read/write/settings — $board[$field] is automatically populated with the translated value. The "← Back" button on the board settings page is also auto-translated via the same mechanism.
13-language seed for title + header_content of the issue board (id=6) — HTML structure preserved, only text translated, processing-stage badges also rendered in each language. Seed scripts: scripts/seed_issue_i18n.php + scripts/seed_issue_i18n_data.php.
Added
Translated definitions (label/description/options/default value) of rzx_board_extra_vars into 13 languages. 4 fields × 13 = 52 rows.
- •
ExtraVarRenderer::renderAll($vars, $values, $mode, $boardId) — added boardId argument, applyDefTranslations() bulk-loads all var translations in a single query.
- •
ExtraVarRenderer::getOptionLabel($boardId, $varName, $value) — single option translated label lookup for list views (static cache).
- •
renderStatusBadge($value, $size, $displayLabel = null) — color uses the source (ko) mapping, only text is translated → color consistent across all languages (received = rose, resolved = emerald).
- •select/radio/checkbox:
value=source / label=translated pattern. Source value is always stored on post save, ensuring data compatibility.
- •Caller updates:
read.php, write.php, _list-row.php, default skin's _list-card/_list-webzine/_list-gallery.
13-language seed for issue board status extra variable: scripts/seed_ev_status_i18n.php.
Added
The extra_var_get API returns translated values for the current locale as a localized object via db_trans(). The JS modal displays localized.title || ev.title as priority → in Japanese mode, opening the modal automatically shows "処理ステージ".
extra_var_update branches by locale:
- •Source locale (ko) → update entire source row (existing behavior)
- •Non-source locale (ja/en/…) → save only meta (
var_type/permissions/3 checkboxes) to source; save 4 translation fields (title/description/options/default_value) to rzx_translations in the current locale → source integrity guaranteed
Display names in the extra variable list also use db_trans('board_ev.{boardId}.{varName}.title').
Added
The "Edit permission" section (label/dropdown options/help text) of the extra variable edit modal separated into 13-language lang files. New keys: site.boards.ev_permission, ev_perm_all, ev_perm_member, ev_perm_admin, ev_permission_help.
Added
Bidirectional normalization for the "Options" field in the extra variable edit modal:
- •Modal load: converts DB JSON array (
["a","b","c"]) to line-break format for textarea display (matching the instructions)
- •Save API:
ev_normalize_options() helper accepts either line-break or JSON input and consistently saves as JSON array. Fixed a bug where line-break input was previously saved as NULL.
Added
Two new skins for content patterns beyond issues/notices.
FAQ (skins/board/faq/) — post = question, body = answer. Includes search bar, category filter, and pagination. Title area display toggle, multilingual title/subtitle, background (image/video/none) support, overlay opacity adjustment. v1.0.1.
Q&A (skins/board/qna/) — post = question (title+body), comment = answer. Unanswered badge, first item auto-expanded, tree-sorted (parent_id/depth) comment display. Replies to replies are indented + labeled "↳ Reply" (top-level = "A."). Inline edit/reply form (author/admin only for edit, perm_comment permission for replies). v1.0.0.
Both skins share: admin settings gear icon placed at bottom-right of the search bar (separated from title area). boardSkinFile() operates with _list-full.php priority.
Added
update action added to api-comments.php. Only the comment.user_id == current user or admin can edit. After character count validation, updates content + updated_at.
Added
{year} token in the layout settings copyright field is automatically replaced with the current year at render time. The DB retains the original (with {year}) so it never needs manual updating. Applied to: default/minimal/modern/base-footer.php. The copyright description in 4 layout.json files expanded to 13 languages, with {year} usage instructions shown in each native language.
Also fixed a bug where $_lc['copyright'] was ignored in default/minimal footers — the layout setting value now actually renders.
Added
13-language seed for menu_item.38.title (VosCMS Introduction / About VosCMS / VosCMSについて / Über VosCMS / Acerca de VosCMS / À propos de VosCMS / Tentang VosCMS / VosCMS-ийн тухай / О VosCMS / VosCMS Hakkında / Giới thiệu VosCMS / 关于 VosCMS / 關於 VosCMS).
Added
One sample per processing stage + 1 existing resolved = 4 total. 13-language translation of title/content for each post (104 rows).
| Stage | Title (ko) | Status |
|---|
| Received | [Bug] Mobile hamburger menu X button does not work | New |
| Under review | [Feature request] Email notification to post author when a comment is written | New |
| In progress | [Performance] Improve initial load speed of marketplace detail page | New |
| Resolved | Attached image in a board post inserted into body... | Existing |
Deleted existing post 34 (accumulated Tailwind inline style test post).
Fixed
customer/board/list.php required rzx_admin_icons helper at line 167, but the skin file include at line 161 returned early — causing a 500 error when rzx_admin_icons() was called in FAQ/Q&A skins. Moved the require_once above the skin include.
Changed
Removed remaining references to the old /marketplace name. Fixed a bug where the search form submitted via GET to /marketplace, causing a redirect to the dashboard (page navigation triggered on search/sort/filter changes). Unified all of browse.php, item-detail.php, item-submit.php, purchases.php, and assets/js/marketplace.js to /autoinstall.
Added
In install.php Step 4, added a yellow hint below the admin path input, a real-time URL preview, and an expandable "Why should I change this?" guide. Three reasons explained in plain language that anyone can understand: automated hacking attempts (brute force), vulnerability scanning, and the limits of strong passwords. Applied to all 13 languages.
Added
At the installation completion step in install.php, the vos-autoinstall plugin is automatically registered and activated (by calling PluginManager::install() directly). Users can access the marketplace immediately upon entering the admin panel without a separate installation step.
Added
Added ?locale=xx parameter support to market.21ces.com/api/market/catalog and /item. Multilingual JSON for name, description, short_description, cat_name is returned as a single string from the server (fallback: requested locale → en → first value). Removed all duplicate json_decode + locale fallback logic from the VosCMS side (browse.php, item-detail.php, _components/item-card.php).
Added
Integrated management of the file-based marketplace API response cache TTL into PluginManager settings. Hard-coded 30 minutes → default 5 minutes, with options for 1/5/10/30/60 minutes. Both https://vos.21ces.com/theadmin/settings/general marketplace cache section and /theadmin/autoinstall/settings read from and write to the same setting, auto-syncing. Added an immediate cache refresh button on both the AutoInstall page header and the general settings page. Existing cache is automatically invalidated on TTL change.
Added
Displays the same data type / short description / slug / Product Key / minimum VosCMS & PHP version / partner / price / license table as the marketplace admin, immediately after the header. Changed left:right content ratio from 2:1 to 3:1 to expand the content area.
Added
"Write a Review" button in the top-right of the review tab on the detail page. Star rating / nickname / body input modal → VosCMS API → relayed to marketplace POST /api/market/item/review. VosCMS license key + domain attached automatically, one-submission-per-domain limit, purchasers get is_verified=1 automatically. Reviews are stored in the marketplace DB (rzx_mkt_reviews) as status='pending' and published after operator approval. New GET /api/market/item/reviews?slug=... endpoint returns only approved reviews. Domain is partially masked (mys***.com).
Added
Expanded the previous Free only checkbox to a All / Free / Paid 3-way toggle. Added paid=1 parameter to the market catalog API (i.price>0). Moved out of the search bar to in front of the view toggle area, integrated with the sort select into a header toolbar.
Changed
Search input border border-2 border-emerald-300, green background box on the left search icon, search button bg-emerald-500. Simplified the search bar by keeping only keyword input; sort and filter features moved to the body header.
Changed
| Status | Display |
|---|
| Free | Install + Download |
| Paid (not purchased) | Purchase (marketplace PAY.JP payment modal) |
| Paid (purchased) | Install + Download |
| Already installed | "Installed" |
Same branching applied to the 4 list page views (list/webzine/card/grid). The set of purchased slugs is queried once on page entry and reused. Payment is handled by the marketplace POST /api/market/item/purchase, which performs PAY.JP billing + license issuance.
Changed
- •View style toggle: text labels removed → icons only +
title / aria-label (accessibility maintained)
- •Type badge:
bg-<color>-100 light background → bg-zinc-900/85 backdrop-blur-sm dark translucent + colored text (improved readability over images)
- •Screenshots section: moved from immediately after header → below tab content, count displayed as
(n) like the marketplace
- •Detail page
dark:prose-invert not working → added typography plugin to CDN Tailwind
- •Detail page description area: same approach as marketplace admin — renders HTML markup as-is, only removing dangerous elements like
<script>/<iframe>/<style>/onXX
Fixed
- •PAY.JP card form not accepting input: mounted in hidden state in advance caused the iframe to render at 0px. Fixed with lazy initialization +
requestAnimationFrame mount
- •Widget thumbnail missing: 28 widgets registered in the marketplace DB but only 12 free widgets copied to
/var/www/market/widgets/. Synced the 14 missing ones. Added absolute URL conversion for screenshots + automatic filtering of non-existent files to catalog/item API
- •Bundle plugin unauthorized warning:
vos-autoinstall was being caught as unauthorizedPlugins. Added BUNDLE_PLUGINS = ['vos-autoinstall', 'vos-marketplace'] constant to LicenseStatus.php, filtering applied across active()/fromCache()/canUsePlugin(). Same treatment applied to the license server verify.php
- •Widget duplicate addition possibility:
install.php seeding used only INSERT IGNORE, but rzx_page_widgets has no UNIQUE constraint so duplicates were possible. Changed to DELETE FROM page_widgets WHERE page_slug='home' before INSERT (DB UNIQUE constraint not added because intentional duplicates exist for cases like board widgets)
Migration Notes
- •
vos-autoinstall plugin menu/routes/migrations are auto-activated on fresh installs (existing installs need to activate once from the admin panel)
- •Marketplace API cache default TTL 30 minutes → 5 minutes (for faster reflection of price/content changes)
- •
vos-autoinstall.cache_ttl plugin setting is new
Changed
- •
browse.php: switched from local DB query to live API call at market.21ces.com/api/market/catalog
- •
item-detail.php: switched from local DB to /api/market/item and /api/market/item/versions API calls
- •
CatalogClient.php: API base URL changed from /api/v1 → /api/market, response field items[] → data[] aligned
Fixed
Problem: The rzx_plugins block in market.21ces.com/index.php that applies the layout was missing the $__noLayout = true case, causing all plugin API routes (api/market/*) to return HTTP 200 + empty response.
Fix: Added elseif ($__pageFile && $__noLayout) { include $__pageFile; } at the end of index.php. This normalizes api/market/catalog, api/market/item, api/market/updates/check, etc.
Added
Added sort, free, and featured parameters to plugins/vos-market/views/api/catalog.php, with expanded response fields (sale_price, rating_avg, is_featured, etc.).
Added
mpApiFetch() helper: minimizes marketplace server requests with 30-minute TTL file cache. Displays an error banner on API response failure.
Fixed
Problem: In AdminRouter, after creating a $licenseClient = new LicenseClient() instance and attempting auto-registration, the $_ENV['LICENSE_KEY'] update was not reflected in the instance's internal $this->licenseKey — so a check() call within the same request still returned unregistered.
Changed
- •On entering
check(), if $this->licenseKey is empty, immediately call /api/license/register
- •On success: update
$this->licenseKey + update $_ENV['LICENSE_KEY'] + save .env file, then proceed to verify step
- •Only return
unregistered on failure (no server response)
- •New
saveKeyToEnv() method: replaces an empty LICENSE_KEY= line (failed install.php case) with the actual key; skips if a valid key already exists
Effect
- •Users who installed manually without install.php → license registered automatically on first admin access
- •Users who updated from an older version (no LICENSE_KEY) → automatically registered on first admin access after update
- •The "License not registered" dashboard message now only appears when the license server itself is unreachable
Fixed
The database/seeds/legal_pages.php seed file had Tailwind CSS variables (--tw-scale-x, --tw-pan-x, etc., 400+) embedded inline in the style="" attribute of every <h1>/<h2>/<p> tag. Each installation propagated this pollution into the DB, bloating the terms and privacy pages (notably privacy/ko reached 128KB; normal is 2KB).
- •
database/seeds/legal_pages.php: 442KB → 131KB (70% reduction, syntax valid)
- •Cleaned 26 already-polluted rows in the development DB: 1.04MB → 56KB (988KB removed, −95%)
- •New
scripts/clean-inline-styles.php — bulk cleanup tool for style="... --tw-* ..." (reusable if the issue recurs)
Changed
The .board-content CSS contained rules that force-converted every document page by treating "h2 as a card header and h2+p as a card body". This broke the hero structure of pages designed directly with Tailwind (e.g., Brand).
- •Removed 103 lines of card-style block rules (149 → 45 lines)
- •Remaining rules: base typography (p/h·/blockquote/code/ul/ol/table), OG link card, dark-mode text override
- •Result: original
Brand page design restored (left blue-bar h2, centered hero, etc.)
- •Legal documents (terms/privacy, etc.) are adequately served by standard
<h1><h2><p> typography — card styling was excessive decoration
Changed
Background: In 2.3.1 the page content was split into rzx_page_contents for the Korean original and rzx_translations for non-Korean locales, and in 2.3.2 a separate save path was added in the editor. After a design review, however, it became clear that the two tables share an essentially identical structure (slug/key + locale + content) while introducing the following costs:
- •Korean source duplicated across both tables (for the db_trans fallback chain mirror)
- •Editor save logic requires a branch for ko vs. non-ko
- •Locale status display in the page management UI requires a JOIN across two tables
- •The original justification ("unified AI translation pipeline queue") has little value in practice, since long page body HTML (10 KB) and short UI strings (20 bytes) require different workflows (chunk splitting, tag preservation, etc.)
Restored structure: single rzx_page_contents table, one row per locale (original design)
Migration
- •
rzx_translations.page.{slug}.title/content × 7 slugs × 12 non-ko locales (168 rows) → restored to rzx_page_contents
- •All
page.* keys deleted from rzx_translations (234 rows)
- •Targets: Brand, terms, privacy, refund-policy, tokushoho, funds-settlement, data-policy
- •Applied to both development and production databases
Changed
- •
resources/views/admin/site/pages-edit-content.php — ko/non-ko branch removed, simple UPSERT
- •
resources/views/admin/site/pages-document.php — same
- •
resources/views/customer/page.php — db_trans('page.*.title/content') removed, reads directly from page_contents (fallback chain locale → en → ko retained)
- •
resources/views/admin/site/pages.php — page list titles also read directly from page_contents.title
Kept
rzx_translations itself is retained — continues to be used for:
- •Boards (
board_post.{id}.title/content)
- •UI strings (
menu.*, settings labels)
- •Page SEO meta (
page.{slug}.browser_title|meta_keywords|meta_description) — short strings, appropriate here
Principle: short UI/meta strings → rzx_translations; long page body content → rzx_page_contents, stored according to each data type's characteristics.
Changed
After migrating the page content save structure in 2.3.1 to original ko (rzx_page_contents) + translation DB (rzx_translations), the admin editor was still INSERT/UPDATE-ing all locales into rzx_page_contents only — causing a mismatch with the migration format and query failures from db_trans() on the render side. This release fixes that.
Branching rules (source locale = $_ENV['DEFAULT_LOCALE'] ?? 'ko'):
| locale | save destination |
|---|
ko (source) | rzx_page_contents + mirror to rzx_translations (fallback for db_trans()) |
en/ja/zh_CN/... | rzx_translations only (page.{slug}.title, page.{slug}.content) |
When saving a non-source locale, if there is no source row in rzx_page_contents, a stub row is auto-created (to preserve menu/settings connections). is_active is applied uniformly to the source row.
Changed
When opening a non-source locale edit page:
1. First look up page.{slug}.title/content + locale from rzx_translations
2. Fall back to legacy rzx_page_contents row if not found (for compatibility with pages missed during migration)
For the source locale, only rzx_page_contents is used as before.
Changed
- •Page deletion: also delete
rzx_translations WHERE lang_key LIKE 'page.{slug}.%'
- •Slug change: bulk UPDATE
page.{old_slug}. → page.{new_slug}. in rzx_translations.lang_key
Changed
The previous approach of storing a full-content row per locale in rzx_page_contents has been unified to original ko + translation DB (rzx_translations) method. Achieves structural consistency with the board (board_post.*) storage format.
Target 7 pages: Brand, terms, privacy, refund-policy, tokushoho, funds-settlement, data-policy
Before:
rzx_page_contents
├── Brand / ko (full content)
├── Brand / en (full content)
├── ... (13 locale rows)
After:
rzx_page_contents
└── Brand / ko (1 original row)
rzx_translations
├── page.Brand.title × 13 locale (including ko)
└── page.Brand.content × 13 locale
Migration results:
- •
rzx_page_contents: 7 slugs × 12 non-ko rows = 84 rows deleted
- •
rzx_translations: 7 slugs × 13 locales × (title + content) = 182 rows migrated
- •Applied to both development and production DB, render validation passed (per-locale display confirmed for ko/en etc.)
Changed
- •"New Page" header button removed — was a non-functional placeholder. Pages are created from menu management as the single source of truth.
- •Trash 🗑 icon + AJAX handler +
deleteUserPage() JS removed — pages are cleaned up when the menu is deleted, so a standalone page-deletion path is unnecessary. Prevents broken menus from erroneous deletions.
- •Retained: settings ⚙ · edit ✏ · preview 👁 icons
Fixed
- •
Settings > Site > Logo Image upload failure: move_uploaded_file() silently failing due to missing storage/logos/ subdirectory. Added automatic subdirectory creation to upload handler + created directory on dev and production servers (www-data:www-data, 775).
- •Layout logo priority restored: briefly reversed to "site settings first", then restored back to layout > site fallback to match "layout-specific logo" requirements. If no logo is set in the layout, the site logo is used automatically.
- •Target:
skins/layouts/{default, minimal, modern, modern-copy-260413}/header.php and footer.php
Fixed
- •Kept only the earliest created row for
bi-guide / logo-guide slug duplicates (id=186, 187), deleted duplicate id=193, 194
- •Applied to both dev and production. The duplicate INSERT bug in the menu API was already fixed in v2.3.0
Added
Logo assets created at /var/www/_upload/ — upload as needed for production:
Icon (mark only)
- •
voscms-logo-color-{256,512,1024}.png · color (deep·ocean·sky·ocean + deep center)
- •
voscms-logo-mono-{256,512,1024}.png · sky solid + opacity 1/0.7/0.48/0.7 gradient
Horizontal wordmark (for header) — 820×256 ratio
- •
voscms-wordmark-{128h,256h,512h}.png · light (Vos deep + CMS gray)
- •
voscms-wordmark-dark-{128h,256h,512h}.png · dark (Vos sky + CMS slate-300)
Vertical wordmark (for app icon / OG image) — 360×420 ratio
- •
voscms-stacked-light-{256h,512h,1024h}.png
- •
voscms-stacked-dark-{256h,512h,1024h}.png
All SVG originals preserved. librsvg2-bin installed provides rsvg-convert CLI. OKLCH color support issue resolved with hex-converted version (voscms-logo-mono-hex.svg).
Added
- •DB table
rzx_changelog — independent records per version × locale, tracking translation_source (original/ai/manual) + source_hash
- •Parser
RzxLib\Core\Changelog\ChangelogParser — Keep a Changelog standard + internal section whitelist (Infrastructure/Internal/Refactor/Chore/Docs/Test are private)
- •Importer
ChangelogImporter — idempotent merge, manual translation protection, detects changes via content_hash
- •CLI script
scripts/import-changelog.php — --file --locale --dry-run --silent options, SemVer pre-release (2.1.1-hotfix) support
- •Translation interface
RzxLib\Core\Translate\{TranslatorInterface, NullTranslator, TranslatorFactory} — activated by changing the driver in config/translator.php once Gemma 4 (ai.21ces.com) GPU is ready
- •System page
/changelog registered (config/system-pages.php), front-end view resources/views/system/changelog/index.php
- •Dedicated skin
skins/page/changelog/skin.json — card styles (filled/outlined/flat), per-section colors, version badge, date format, relative time display, internal section visibility toggle
- •Edit page
/changelog/edit separated — MD upload preview, multilingual coverage matrix, per-version toggle & delete, AI translation button (coming soon)
- •Deployment automation —
1.5/5 Changelog import step added to deploy-voscms.sh: auto-detects and merges ko + 12 locale .md files
- •Translation key
site.pages.changelog added for 13 locales
Changed
- •Tab order rearranged:
[Basic Info] [Permissions] [Advanced] [Skin] — system pages can append their own tabs at the end
- •
settings_tabs schema introduced (config/system-pages.php) — system pages declare a top-level tab array; each entry rendered dynamically via key/label/icon/view. Legacy settings_view bottom-include removed
- •Principle established: Settings = appearance + behavior / Edit = data — data management UIs such as upload, version, and translation are separated to
/changelog/edit
- •MERGE save strategy —
save_settings handler merges with existing settings. Other tab data is not cleared even when tabs are separated
- •
saveSettings() JS — only submits fields present in the DOM. Safe when some fields are not rendered in a split-tab environment
- •Layout/skin card position — moved back to the Basic Info tab (user feedback applied). Skin tab is exclusively for skin.json variables
- •Removed duplicate page width —
page_width field removed from Basic Info tab (consolidated into skin's content_width)
- •service/order secondary tab flattening —
general/domain/hosting/addons in config/service-settings-tabs.php migrated to settings_tabs, promoted to top-level tabs
Changed
- •
/{slug}/settings and /{slug}/edit now recognize config/system-pages.php — previously only queried rzx_page_contents DB, causing system pages to return 404. Now queries DB → config in order
- •
edit_view field added — directly specifies the edit view file path for system pages. Clicking /{slug}/edit directly includes that view (removes admin redirect hop)
- •pages-settings.php fallback — system pages without a DB record now build
$pageData from config, preventing redirect to the page management list
- •Admin icon URL unified —
/{slug}/settings and /{slug}/edit use the standard front-end URL
Fixed
- •
menus-api.php: fixed missing duplicate check before INSERT for external page type — prevented duplicate rows in rzx_page_contents when saving the menu twice
- •Fixed cleanup omission for external type + widget
page_contents when deleting a menu — prevents orphan records
Fixed
- •Title background (
title_bg_type, title_bg_image, title_bg_video, title_bg_overlay, title_text_color), show_title, show_breadcrumb, custom_css, custom_header_html, custom_footer_html — all standard page skin variables now reflected in the public view
- •
_page-title-bg.php partial now accepts $_contentWidth — title area follows content width
- •Admin icon separated from the title — independently positioned at top-right of content area; always visible even when
show_title=0
- •Added 20px top margin above title area (background layout balance)
Added
- •
site.pages.contact translation key added for 13 locales (ko/en/ja/zh_CN/zh_TW/de/es/fr/id/mn/ru/tr/vi) — resolves previous omission
- •Page management system guide
docs/PAGE_ADMIN_GUIDE.md — documents tab structure, schema, routing, permissions, and extension methods
Added
- •Per-item number, suffix, label (multilingual), icon, and color fields
- •Admin widget builder supports add/edit/delete of items (new
stat_items type editing UI)
- •16 icon types (users, chart, star, heart, check-circle, clock, calendar, globe, shield, lightning, trophy, cart, dollar, building, chat) + 10 color options
- •Count-up animation on viewport entry — IntersectionObserver + easeOutCubic 1.4s
- •Only numeric values are counted (
24/7-style strings are displayed as-is), comma formatting and decimals preserved
- •
widget.json v1.1.0 → v1.2.0
Fixed
- •Fixed issue where
admin_name was stored with enc: prefix and exposed without decryption
- •Fixed avatar lookup incorrectly LEFT JOINing a non-existent
staff table — now directly queries rzx_users.profile_image matching the admin_id = users.id unified structure
- •Automatically prepends baseUrl prefix to relative-path avatars
Added
- •
[Get Thumbnail] [Get Screenshot] buttons automatically inserted next to every <input type="url"> field (including extended variable url type) across all boards
- •Thumbnail: native render at 1280×960 → 800×600 output (first-screen viewport)
- •Screenshot: native render at 1280×full_page → 800×scaled output (full scroll page, actual page height)
- •Step-by-step progress bar UI —
Connect to server → Load page → Capture screen → Generate image → Save file → Awaiting response
- •Auto-set captured thumbnail as featured image — flagged as
board_files.is_primary=1, prioritized by the board-showcase widget
- •New route
/board/api/url-capture?type={thumbnail|screenshot}&url=... + api-url-capture.php proxy
- •
set_primary action added to /board/api/files — designates one of the existing attachments as the featured image
Changed
- •Attachment image query
ORDER BY is_primary DESC, id ASC — captured thumbnails shown first
- •
primary_file_pos parameter passed to the server when creating a post to specify the featured file index
v2.2.0
2026-04-18 · 6 months ago
Marketplace Separation
Changed
- •Marketplace product separation: functionality previously shared by the
plugins/vos-marketplace/ plugin has been spun off as a standalone product → github.com/THEVOSjp/Marketplace new repository, independent deployment at market.21ces.com
- •The plugin retains only the "customer-facing UI for browsing, purchasing, installing, and licensing" — data, sellers, and settlements are handled exclusively by the marketplace server
- •Core-side API integration points updated
Added
- •Language-selection modal — warning about the need to back up
.env + DB + storage/uploads/, and a notice that APP_KEY loss makes decryption impossible
- •Two-step confirmation: checkbox + confirm button (unconditional installation is prohibited)
- •Full translation for all 13 languages added (
backup_title/body/item_env/db/uploads/warn/check/confirm/cancel)
Added
- •
scripts/register-widgets-to-marketplace.php — bulk-registers 28 file-based widgets into rzx_mp_items
Changed
- •
plugins/vos-marketplace/src/CatalogClient.php — default API URL https://market.21ces.com/api/v1, fetchItem() path updated, checkUpdates() query format updated
- •
plugins/vos-marketplace/plugin.json — marketplace_api_url default value updated
- •
rzxlib/Core/Modules/WidgetLoader.php — thumbnail URL now includes ?v={mtime} cache-buster (to handle Cloudflare immutable cache)
Fixed
- •
widgets/feature-tour/widget.json — field cleanup
- •Some widget thumbnails refreshed (staff, testimonials, feature-tour, shop-map, shop-ranking, stylebook)
v2.1.5
2026-04-17 · 6 months ago
Added
- •contact-info (v0.1.0) — contact card (address/phone/email/hours + image + small map)
- •location-map (v0.1.0) — horizontal map (Google Maps + 3-column info below, adjustable width)
Added
- •Item submission page full refactor — 3 tabs (basic info / release / sales)
- •License (GPL/LGPL/BSD/MIT/CC/PD/commercial), repository URL, demo URL, dependent plugins, banner image
- •Release history, sales info (public/paid toggle, discount price/end date), UX improvements
Added
- •Developer menu (ID:20) in 13 languages, Hero CTA description/typing_words supplemented in 12 languages
Changed
- •Widget name distinction clarified (map / contact card),
page.php fullWidthTypes added
- •contact-form category → label only (code removed, widget settings editable)
Fixed
- •board-contents/board-showcase: file_type → mime_type
- •boards-api: missing is_shown_in_list + options JSON constraint
- •contact-form POST: full ob buffer flush
- •Hero CTA: typing_words/description
\n double escape
DB Changes
- •rzx_mp_items: license, repo_url, demo_url added
- •rzx_board_extra_vars: is_shown_in_list added
- •rzx_contact_messages: new table
v2.1.4
2026-04-17 · 6 months ago
Added
- •download-hero widget — software download page (live integration with version.json, 4 built-in languages, key features / system requirements / version history)
- •/downloads page — widget-type system page, 13-language menu translations,
is_system=1
- •contact-form widget (v0.1.0) — private 1:1 inquiry form widget (guest-friendly, category selection, honeypot spam protection, AJAX submission)
- •contact-info widget — contact information display widget
- •location-map widget — location map widget
- •rzx_send_mail() shared helper — new SMTP mail-sending function in
rzxlib/Core/Helpers/mail.php
- •rzx_contact_messages table — DB table for storing inquiries
- •/contact page — widget-type system page, 13-language menu translations
Fixed
- •System page deletion protection — preserves page content/widgets when removing a system page from the menu (only the menu entry is removed)
- •
menus-api.php: dual check via DB is_system + config/system-pages.php slug
- •
menus-js.php: displays "will only be removed from the menu" notice when deleting a system page
- •System page menu multilingual — when adding a system page to a menu, 13-language labels are automatically registered from the
site.pages.* translation keys
- •Downloads page translation — added
site.pages.downloads translations for 13 languages
Changed
- •Homepage hero — Download button → direct download at
/download/voscms-latest.zip, version updated to v2.1.1
- •Auth.php — calls
rzx_send_mail() shared helper first before falling back to sendSmtpMail
- •index.php — added auto-loading of the
mail.php helper
v2.1.3
2026-04-17 · 6 months ago
Changed
- •Hosting tab — summary card + usage progress bar + FTP/DB connection info table (auto-displayed when configured by admin)
- •Domain tab — domain card + primary setting + nameserver info table (auto-displayed when configured by admin)
- •Mail tab — mail server info (IMAP/SMTP) + per-account card + inline password change
- •Maintenance tab — info card + scheduled date + work history (auto-displayed when admin records it)
- •Add-on services tab — unified card layout
- •Unified information structure matching the admin partial (admin enters in metadata → auto-reflected in my page)
Changed
- •Activity log area reduced from 1/3 to 1/5 ratio
- •Admin domain tab NS default value hardcoding removed (in preparation for Cloudflare API integration)
Changed
- •Provisioning workflow overhauled: subdomain first → purchased domain connected afterward
- •Provisioning ID rule: subdomain name or purchased domain SLD
- •Cloudflare wildcard tunnel utilised (no subdomain A record addition needed)
- •Server 2 disk configuration changed: NVMe 256 GB + SSD 2 TB + HDD 8 TB three-disk setup
v2.1.2
2026-04-16 · 6 months ago
Added
- •Service classification system — added
service_class column (recurring/one_time/free), completed_at completion date for one-time services
- •My page service detail —
/mypage/services/{order} tab management by type (hosting/domain/mail/maintenance/add-on services)
- •Service management API —
api/service-manage.php (auto-renewal toggle, renewal request, primary domain setting, mail password change)
- •Admin order management —
/admin/service-orders order list + detail page, one-time status modal (received/in-progress/on-hold/cancelled/completed)
- •Admin menu — "Service Orders" menu added to sidebar (position: 65)
Added
- •phone-display — phone number display component (server-side
format_phone() helper), no JS dependency
- •format_phone() — PHP international phone number formatting function (Korea/Japan/USA/China/UK/France/Germany/Australia etc.)
- •Free domain setup — free subdomain list management in admin settings (
service_free_domains), multiple domain select support
Fixed
- •Missing free service subscriptions — free orders did not create subscription records for domain/add-on/maintenance etc. → refactored to create all services in bulk
- •Payment section terms agreement — terms checkbox was hidden with the payment section on free orders → separated outside the section
- •updateSubmitButton isFree detection —
btn.closest('form') NULL → fixed with document.querySelector
- •Admin sidebar active indicator —
$adminPath undefined caused all menus to be inactive → added variable declaration
- •My page avatar — missing
use Auth + $user = Auth::user() in services.php/service-detail.php
- •Add-on one-time check —
one_time flag in admin settings now correctly reflected in service_class at order time
- •One-time service initial status — changed to create as
pending (received) instead of active
Changed
- •Order API refactoring — consolidated subscription creation code across 3 payment paths (free/card/bank transfer) into
$subscriptionData + _insertSubscriptions() shared function
- •Service list UI — inline expand → card link + separate detail page
- •Phone number display — applied
format_phone() to profile/member list/staff list
- •Free domain dynamicization —
.21ces.net hardcode → reference admin settings value
v2.1.1-hotfix
2026-04-16 · 6 months ago
Fixed
- •Footer menu 404 errors — missing legal page data (terms, privacy, refund-policy, data-policy, tokushoho, funds-settlement) in the
rzx_page_contents table → restored from test DB
- •Widget builder image/video upload failure — fixed issue where
FormData POST from the front-end wrapper (page-widget-builder.php) was not being passed to the AJAX handler (added file upload condition + ob_end_clean/exit handling)
- •Upload directory permissions — changed owner of
storage/uploads/widgets/ to www-data to grant PHP-FPM write access
Added
- •Service order menu multilingual — added
menu_item.19.title translations for 13 languages in the rzx_translations table
- •Hero CTA widget multilingual — applied 13-language translations to the title, subtitle, and button text of the Dev page hero banner
- •Upload status preview — added visual feedback during image/video upload: in-progress (spinner), success (checkmark), and failure (error message)
v2.1.1
2026-04-12 · 6 months ago
Added
- •Step 0 language selection screen — choose from 13 languages before installation begins; entire UI displays in the selected language from that point
- •Installation translation files separated —
resources/lang/install.php (13 languages × 45 strings)
- •Member tier multi-language — Normal/Silver/Gold/VIP auto-generated translations for 13 languages
Added
- •config/admin-menu.php — core admin menu separated into a config file (preserved on Core updates)
- •admin-sidebar.php refactored — 731-line hard-coded → ~200-line dynamic renderer (config + plugin.json based)
- •vos-shop plugin.json — hard-coded menu migrated to
menus.admin format
Added
- •Slide-in menu — mobile: avatar+name toggle bar → left slide-in overlay (300ms ease-out)
- •Fade dim background — close by clicking background or X button; body scroll locked
Added
- •force_locale — enforce the default language (
?lang=xx parameter allows user override)
- •default_language — install.php ↔ Translator config key unified
- •URL parameter —
?lang=xx detected first, saved to session/cookie
Fixed
- •FAQ skin not applied — migration SQL ran a bare
INSERT for the default board before install.php, causing the INSERT IGNORE to be silently skipped; fixed
- •translations table source_locale missing — column missing from core migration → db_trans() failing entirely → user info/menu/i18n not displayed; fixed
- •Settings tab sidebar active indicator —
route_prefix attribute added so settings menu stays active on all settings sub-routes
- •Installation session residue bug — on a fresh install with no .env, a leftover PHP session caused Step 0 to be skipped; fixed
- •nginx 403 — conflict between admin/ physical directory and route fixed (removed
try_files $uri/)
- •Obfuscation script version hard-coded — obfuscate.php now reads version dynamically from version.json
Changed
- •Grid/board-contents widget multi-language view — rzx_translations lookup added (current locale → English fallback)
- •Site industry categories — expanded from 15 to 26 (added: corporate, shopping mall, legal, accounting, IT, media, etc.; 13 languages)
- •Grid widget settings — grid-section render.php changed to native cell type (
board-list); 13-language title
- •Step 4 language selection — expanded from 5 to 13 languages; pre-filled with the installation language
- •Timezone selection — expanded to 12 timezones covering 13-language user regions
v1.26.0
2026-04-01 · 7 months ago
Added
- •Bundle (set) services displayed in 3 columns at the top of the service selection screen — amber color theme visually distinguishes them from individual services
- •SET badge, event discount label, included service names, bundle price, original price strikethrough, discount rate display
- •Selecting a bundle auto-selects all included services (price calculated as the bundle price)
- •Services included in a bundle cannot be individually toggled (prevents incorrect price calculation)
- •Designation mode:
rzx_staff_bundles table for per-staff bundle filtering
- •
bundles parameter passed to the confirm page
- •Multilingual support: bundle-related kiosk translation keys added for 13 languages
Added
- •View toggle buttons —
[Month] [Week] [Day] toggle in the top bar (active state: blue)
- •Weekly view — left vertical = day of week, top horizontal = time axis
- •Horizontal scroll, reservation blocks displayed as time bars
- •Lane algorithm: detects time overlap and auto-places below, row height variable
- •Left sidebar ↔ grid vertical scroll synchronized
- •Daily view — horizontal = time axis, vertical = lanes (same method as weekly view)
- •Business-hours-based time range — based on
business_hour_start/business_hour_end settings; auto-expands for out-of-range reservations
- •Empty cell click time passing — in weekly/daily view, click X coordinate → 15-minute unit time calculation → auto-fills reservation modal start time
- •Top navigation bar fixed —
overflow-x-hidden + sticky applied; stays within viewport while scrolling horizontally
Added
- •Admin > Settings > General — business start/end time setting UI added
- •
business_hour_start, business_hour_end DB setting keys
- •Applied to the default time range in the calendar weekly/daily view
Fixed
- •POS
add-service missing fields — added start_time, end_time, staff_id, designation_fee, notes (previously: server-side time hardcoded, staff/notes missing)
- •end_time past-midnight prevention — applied
% 24 → min(23:59) upper limit cap in ReservationHelper, start-service API, and booking form calcEnd()
- •POS in-use judgment past-midnight handling —
CustomerBasedAdapter handles end_time < start_time as past midnight
- •Booking bundle validation — fixed to allow reservation creation when only a bundle is selected (no individual services)
Changed
- •Tab name changes: Reception / Reception List / Reservations
- •Reception List:
walk_in, pos, kiosk, admin sources — all statuses displayed
- •Reservations:
phone, online sources — all statuses displayed
- •Sort order: Waiting → Confirmed → In Use → Completed → No-show → Cancelled; within same status sorted by time
- •Completed/Cancelled/No-show rows: shown with
opacity-50, unassigned badge displayed
Changed
- •Completed badge: changed from grey → green
- •Cancelled/no-show badges added (hidden when count is 0)
Added
- •Card bottom: no-show button added for waiting-status cards (prohibition icon), cancel button changed to red
- •Detail modal: no-show action button shown for both
pending and confirmed states
- •Group batch no-show:
POS.noShowAllServices() function added
Fixed
- •Reservation detail back navigation —
<a href="/reservations"> → history.back(), returns to previous page
- •Multilingual: calendar view toggle (month/week/day), bundle, business hours related keys added for 13 languages
v1.25.0
2026-04-01 · 7 months ago
Architecture
- •POS separated into an independent plugin structure
- •
resources/views/admin/reservations/pos-*.php → moved to resources/views/admin/pos/ directory
- •POS feature auto-enabled/disabled based on presence of
pos/ directory (is_dir() check)
- •Sidebar menu: POS menu auto-hidden when
pos/ directory is absent
- •
index.php routing: POS routes disabled when pos/ directory is absent
- •
pos/_init.php → delegates to reservation common _init.php (shared DB, auth, i18n)
- •API endpoints remain in
reservations/_api.php (backward compatibility guaranteed)
Added
- •Unified payment modal — simultaneous use of points + cash + card (combined payment)
- •Points: balance lookup, manual input or use full amount
- •Cash: amount input
- •Card: amount input + "use remaining balance" auto-fill button
- •Installment: lump sum / 2–12 months selection
- •Real-time total, change, and shortage amount auto-calculation
- •For Stripe card payments, cash/points processed only after card success (safe on cancel)
- •Service detail modal — points display
- •Member's point balance always shown in header area (shown even when balance is 0)
- •Service detail modal — staff badge improvement
- •Assigned/designated badge shown before staff name (previously: below name)
- •Receipt/print button relocation
- •Moved above customer detail section, receipt (left) + print (right) layout
- •Print function improvement
- •Prints current service detail modal content instead of opening an external page
Changed
- •Card layout — grid-based → flex-wrap fixed-size cards
- •Fixed card size: default 320×240px (small: 260×200, large: 380×280)
- •Cards do not resize with screen width; they wrap automatically
- •Card payment modal — expanded to width 1030px, height 800px
Fixed
- •API cross-origin error resolved
- •
$baseUrl was hardcoded to APP_URL in .env, causing fetch failures from other hosts/HTTPS
- •Dashboard: changed fetch URL to relative path (
/update-api.php)
- •Reservation
_init.php: changed $baseUrl to empty string so all fetches use relative paths
- •Completely resolved mixed-content issue in Cloudflare HTTPS proxy environment
- •DB migration execution error — resolved "Failed to fetch" (cross-origin issue)
- •Stripe combined payment cancel cash pre-deduction bug
- •Before: cash processed first → cash already applied when card payment was cancelled
- •After: cash/points processed only after Stripe card payment succeeds
i18n
- •
pos_pay_after_points — amount after points applied
- •
pos_pay_proceed — proceed to payment
- •
pos_pay_remainder — use full remaining balance
- •
pos_pay_installment — installment
- •
pos_pay_lump_sum — lump sum
- •
pos_pay_months — months
- •
pos_pay_total_pay — total
- •
pos_pay_short — shortage
- •
show_receipt — receipt
- •
show_print — print
v1.18.0
2026-03-29 · 7 months ago
Added
- •Multilingual date formatting function —
formatReservationDate($dateString, $locale)
- •Supports 13 languages (ko, en, ja, zh_CN, zh_TW, de, es, fr, id, mn, ru, tr, vi)
- •Auto-localizes date and day-of-week on the reservation detail page
- •Fallback: unsupported locales → English format
Changed
- •Multilingual processing for the reservation detail page
- •Service name, staff name, and bundle name localized (based on DB translations table)
- •Integrated the date formatting function
- •Payment status label localized (payment_status field)
- •Payment status multilingual support
- •Added
payment section to all language files (11 languages)
- •Translations for unpaid, paid, partial, refunded
Fixed
- •Reservation detail page: staff name and service name that were not localized now display correctly
- •Payment status label: fixed the issue where it was only shown in Korean
- •Unified multilingual processing based on the
_tr() function
Layout & UI
- •Page width unified (max-w-5xl → max-w-7xl)
- •Board pages (list, read, write): max-w-5xl → max-w-7xl
- •External page (page.php): default value and DB setting values updated
- •Reservation detail / reservation lookup / staff pages: DB setting values updated
- •Result: all page widths now match the booking page
Documentation
- •Added detailed documentation for
formatReservationDate() in COMPONENT_MANUAL.md
- •Supported languages and format table
- •Usage examples and internal structure
- •Notes and related functions
v1.16.0
2026-03-23 · 7 months ago
Added
- •Mobile bottom menu bar — Staff / Reservations / Home / Login (or My) / Sign up / More, with current page highlight
- •Mobile "More" panel — main menu + My Page / Logout
- •Layout/skin settings actually applied — site_layout, site_page_skin, site_board_skin, site_member_skin → reflected in rendering
- •no_layout parameter — content only without layout (for preview use)
- •Translation keys added — common.nav.staff, common.nav.more (13 languages)
Fixed
- •Applied layout to member pages (login/register/forgot/reset): removed duplicate header by extracting skin
<main>
- •Fixed duplicate declaration error for ImageCropper/ProfilePhotoCropper in member skin
- •Fixed menu loader include_once issue: missing base-header menu when loaded first inside skin closure
- •
apple-mobile-web-app-capable deprecated → mobile-web-app-capable (8 files)
- •Hidden header login/sign-up buttons on mobile (replaced by bottom menu bar)
- •iOS safe-area support for bottom menu bar
Changed
- •Merged member pages from their own layout into the default layout (login/register/forgot/reset)
- •Removed footer bottom margin (mobile menu bar uses fixed positioning)
v1.15.3
2026-03-22 · 7 months ago
Added
- •Layout detail settings panel — SkinConfigRenderer-based settings form AJAX load
- •Layout menu settings — menus section of layout.json → auto-generates DB sitemaps dropdown
- •SkinConfigRenderer tab support — adding
tab property to vars auto-generates tab UI
- •SkinConfigRenderer refactoring — unified
renderVar() single method, separated renderFormFlat/renderFormWithTabs, shared renderDependsOnJs
- •layout.json extensions — menus (GNB/FNB), 14 vars (header/logo/color/footer/custom), 13 languages
- •Layout preview integration — selecting "Disable" shows preview with ?no_layout=1
Fixed
- •Layout management menu query:
rzx_menus → rzx_sitemaps table name correction
- •Center panel width adjustment + action link whitespace-nowrap
Pending
- •Applying layout/skin settings to actual page rendering (next session)
v1.15.2
2026-03-22 · 7 months ago
Added
- •Layout management page — 3-panel layout: left preview + menu, right skin list, detail settings panel
- •Per-skin item: thumbnail + detail settings / create copy / delete action links
- •"Disable" / "Install another skin" options
- •4-category saving: layout, page, board, member (
site_layout, site_page_skin, site_board_skin, site_member_skin)
Changed
- •"Design management" → "Layout management" renamed (13 languages: admin.php + site.php)
v1.15.1
2026-03-22 · 7 months ago
Changed
- •15 site management pages → shared
_head.php header applied
- •Boards: boards, boards-create, boards-edit, boards-trash
- •Menu: menus
- •Design: design
- •Pages: pages, pages-settings, pages-edit-content, pages-document, pages-compliance, pages-widget-builder
- •Widgets: widgets, widgets-create, widgets-marketplace
- •Embed-mode pages (boards-edit, pages-settings, pages-edit-content) only apply
_head.php when not in embed mode
- •Full admin page header/layout unification complete
v1.15.0
2026-03-22 · 7 months ago
Added
- •System widget module — 4 widgets: staff (staff introduction), booking (make a reservation), lookup (reservation lookup), cta001 (reservation call-to-action banner)
- •System pages — staff, booking, lookup registered in DB (is_system=1) + routing (
/staff, /booking, /lookup)
- •Widget-based page rendering — staff/booking/lookup pages render widgets when placed, fall back to legacy rendering when none placed
- •Widget auto DB registration — calling
syncToDatabase() auto-registers widgets when a widget directory is added
- •Widget category
system — translated into 13 languages (widgets.categories + widget_builder.cat)
- •Page list unification — system page loop rendering + unified gear/edit/preview icon set
- •Admin page header unification —
$pageSubTitle + $pageSubDesc (common output via _head.php)
- •Translation keys added — staff.admins, staff.attendance.report/stats, points.description, etc.
Fixed
- •Widget builder
page_slug = 'home' hard-coded → dynamic $_GET['slug'] handling
- •Widget builder AJAX save/preview
home hard-coded → dynamic $pageSlug
- •Widget builder preview URL → dynamic
$pageSlug (fixed home → per page)
- •Widget preview_widget WidgetLoader require missing — fixed
- •Service card view image path (
/storage/ prefix)
- •Kiosk settings width
max-w-3xl → 100% unified
- •POS header "Reservation Management" → "POS" renamed
Changed
- •10 staff management + 4 member management pages → common
_head.php header applied
- •Kiosk/bundle pages
_head.php applied
- •Kiosk language settings grid → flex-wrap responsive
- •Page list system pages rendered via data-driven loop
v1.14.1
2026-03-22 · 7 months ago
Added
- •Page skin tab — skin basic info + extended variable settings (SkinConfigRenderer-based)
- •Skin applied to all page types — skin settings applied to document/widget/external alike
- •Title background system — image/video background + overlay + text color (common partial
_page-title-bg.php)
- •depends_on system — declarative dependency between fields in skin.json, renderer auto-generates JS (checkbox/radio support)
- •Skin file upload — image/video upload via multipart (
storage/skins/page/{skin}/)
- •Card-style layout/skin selector — changed to thumbnail card UI in the module info tab
- •SEO system — SEO helper, title pattern, OG/Twitter meta tags, content extraction
- •OG Image upload — URL input + file upload + preview
- •Common result modal — unified save success/failure modal UI (13-language support)
- •Service card view — full-background-image card + table/card toggle button
- •Service drag-and-drop ordering — order change + DB save in both table/card views
- •Reservation staff assignment/designation — 2 buttons (assign/designate) in reservation detail
- •Reservation route UI — phone reservation / walk-in / online selection
- •Phone reservation settings — enable/disable toggle in service basic settings
- •Security headers — X-Frame-Options + CSP frame-ancestors
- •PAGE_SYSTEM.md — page system development documentation
- •PRD_OrderSystem.md — table order system planning document
- •PRD_KioskSystem.md — kiosk system planning document
Fixed
- •Favicon DB setting applied (front / admin / skin layout — 4 places)
- •Dashboard migration URL
window.location.origin → $baseUrl (subdirectory environments)
- •Page settings embed mode API URL → admin path directly specified
- •Result modal i18n: uses locale messages at page render instead of API response
- •Skin tab empty skin value fallback (
"" → default)
- •
pages-settings.php php://input double-consumption issue resolved
Changed
- •Kiosk / kiosk settings / bundle pages →
_head.php shared header applied
- •Skin settings save JSON → FormData (multipart) transfer (file upload support)
- •
$baseUrl parameter added to SkinConfigRenderer (image/video preview)
- •13-language skin translations (skin.json + site.php + common.php)
v1.13.0
2026-03-21 · 7 months ago
Added
- •Page system completion — automatic page creation when document/widget/external pages are created from the menu
- •Dynamic page renderer —
page.php (document→HTML, widget→WidgetRenderer, external→iframe/include)
- •Page environment settings — 3 tabs (basic / layout & skin / SEO), saves as
page_config_{slug} JSON
- •Page content editing — per-language title & content editing, slug change, type change, delete
- •Dynamic page management list — auto-loads user pages from DB, type-based icons & badges
- •Front-end admin icons — gear (settings) + edit (content) shown separately
- •External page iframe — "open in new window" guidance shown on load failure
- •Internal PHP/HTML files — rendered inside the RezlyX layout via include
Fixed
- •Menu shortcut URL
#menu_{id} → saves actual menu URL
- •Menu edit shows same shortcut UI structure as creation time
- •Menu save reflects shortcut URL (
formMenuType → panel visibility basis)
- •Menu translations added for 13 languages (12 menu items × 13 languages)
- •Page document editor translation key path fixed (
admin.site.pages. → site.pages.)
Changed
- •Added
page_type column to DB rzx_page_contents (document/widget/external)
- •Menu deletion now removes associated page + translation data together
v1.12.5
2026-03-21 · 7 months ago
Fixed
- •Fixed menu shortcut URL — now saves the actual menu URL instead of
#menu_{id}
v1.12.4
2026-03-21 · 7 months ago
Fixed
- •Skin settings list type not applied — fixed issue where localStorage cache was overwriting skin settings
- •When style switching is disabled, skin settings are used exclusively; when enabled, localStorage takes priority
- •Removed duplicate submission of checkbox hidden fields (unchecked state now handled in JS)
v1.12.3
2026-03-20 · 7 months ago
Fixed
- •Missing board translation keys for English/Japanese (source_locale, reply, style, board_settings, etc.)
- •Applied multilingual fallback for category names (locale → en → original)
- •Multilingual support for board list body text (webzine/card/gallery previews)
v1.12.2
2026-03-20 · 7 months ago
Fixed
- •Board list body multilingual fallback — translated body applied to webzine/card/gallery previews (locale → en → original)
- •Missing production skin files — created
skins/default/board/ directory via FTP and uploaded skin.json/thumbnail
v1.12.1
2026-03-20 · 7 months ago
Fixed
- •Production skin settings not displayed — full file sync reorganized
- •CLAUDE.md + BOARD_SYSTEM.md file structure fully updated
v1.12.0
2026-03-20 · 7 months ago
Added
- •Skin settings system completed — dynamic form generation based on skin.json + applied to actual boards
- •Section grouping (basic / list / links / design / title background / custom)
- •Added radio and video input types
- •Image/video file upload (FormData multipart)
- •Image/video deletion feature
- •Skin info table (author / URL / email / date / version / thumbnail)
- •Title background image/video — background type, height, overlay opacity, text color settings
- •4 list layout styles — list / webzine / gallery / card; skin files separated + override support
- •Gallery: full-image cover + hover slide animation
- •Webzine: thumbnail + title + preview + meta info card
- •Separated notice posts to top list (_list-notices.php)
- •Skin settings applied — primary_color, border_radius, posts_per_row, allow_style_switch, show_category, linked board, Font Awesome, custom CSS/HTML
- •skin.json default value auto-merge — skin.json defaults applied even without DB save
Fixed
- •skin_config not saved to DB (boards-api.php update action was missing skin_config)
- •Skin save changed from URLSearchParams → FormData (multipart) to support file uploads
- •Auto-parse support for
$_POST['skin_config'] array
v1.11.3
2026-03-20 · 7 months ago
Fixed
- •Applied multilingual fallback for prev/next post titles on the post detail page (locale → en → original)
v1.11.2
2026-03-20 · 7 months ago
Fixed
- •Board list title multilingual fallback — queries translations across all locales (removed Korean-only restriction), English fallback respecting the source language
v1.11.1
2026-03-20 · 7 months ago
Fixed
- •Improved standalone-URL detection for OG cards — now traverses up to the block-level parent so URLs wrapped inside inline tags such as
<font> / <span> are handled correctly
v1.11.0
2026-03-20 · 7 months ago
Added
- •URL auto-link — auto-detects URLs in board post body/editor → converts to
<a> tags
- •OG link preview card — SNS banner format (thumbnail + title + description + domain) card auto-generated
- •Editor: enter/paste URL → calls OG API → inserts card
- •Body: standalone URL link → auto-converts to OG card
- •OgFetcher module — OG/Twitter Card metadata extraction, favicon, automatic encoding handling
- •OG API —
/board/api/og endpoint
- •board-autolink.js — unified auto-link + OG card JS (globally loaded from base-header)
v1.10.2
2026-03-20 · 7 months ago
Fixed
- •Editor/body image alignment — overriding Tailwind preflight's
img { display: block }
- •Created
board-content.css shared CSS (unified editor + body display)
- •Globally loaded from
base-header.php (all front-end pages)
- •Added global editor styles to
multilang-modal.php (all admin Summernote instances)
- •Removed
prose class from board body; inline styles from the editor are now preserved 100%
Changed
- •Documented board body style rules in CLAUDE.md (
board-content class is required)
v1.10.1
2026-03-20 · 7 months ago
Fixed
- •Fixed bug where previous category data was loaded in the multilingual modal when adding a board category (generates a unique temporary langKey)
- •Added
comment_max_depth default value of 3 to migration 021
v1.10.0
2026-03-20 · 7 months ago
Added
- •Board extended variables — ExtraVarRenderer component module (15 types: input/display rendering per type)
- •Extended variable i18n — per-language independent storage of extended variable values (rzx_translations integration, fallback chain)
- •Board post multilingual split storage — when editing in the original language, saves directly to board_posts; for other languages, saves translations to rzx_translations
- •Board post read fallback chain — displays title/body/extra vars in order: current locale → en → original
- •Nested replies — 3-level deep nested comment support, recursive tree sorting, inline reply form
- •Comment like/dislike — per-comment like button + API
- •Board settings front-end access — admin gear icon → /board/{slug}/settings front-end layout
- •Board admin title link — clicking the title in the admin board list navigates to that board
Fixed
- •Board API admin permission check:
$currentUser['role'] → $_SESSION['admin_id'] (4 files updated)
- •Original-language notice shown only to author/admin
- •Board settings embed-mode tab link supports front-end URL
Changed
- •Added
extra_vars JSON column to DB rzx_board_posts
- •Changed
comment_max_depth default from 0 → 3
- •Unified comment action bar (like/dislike/reply/delete icons)
v1.9.4
2026-03-20 · 7 months ago
Added
- •Default board data — Notice board, Q&A, FAQ, Free board migrations (020_default_boards_v193.sql)
v1.9.3
2026-03-20 · 7 months ago
Added
- •Member group reset — initialize with default data (7 groups + translations in 13 languages)
- •Points settings reset — restore configuration values / level points / group links to defaults (member balances unaffected)
- •Member group multilingual —
rzx_multilang_input() common component applied to group name / benefit description
- •Member group drag-and-drop — SortableJS card reordering + auto-save
Fixed
- •Member group card translation display per locale (card view + edit modal)
- •Member group multilingual translation data corruption fixed (PHP UTF-8 re-save)
- •Points module settings board column name corrected (board_name → title)
- •Points member list collation conflict resolved (unified to utf8mb4_unicode_ci)
- •Production DB migration (019_point_system_v190.sql)
v1.9.0
2026-03-20 · 7 months ago
Added
- •Reward points (credits) management system — 3-tab structure (Basic Settings / Per-Module Settings / Member Points List)
- •3-field points structure — point (activity points), balance (payment credits), total_accumulated (cumulative points, basis for level calculation)
- •Cumulative points weighting — separate payment weight / activity weight, paying customers get preferential level-up treatment
- •Points exchange — convert points → balance (ratio, unit, minimum points configurable)
- •17 point grant/deduction categories — same structure as Rhymix (reclaim on delete, time limits, exclude notices)
- •Per-module settings — individual configuration of 15 point items per board
- •Member points list — 4 columns (points/balance/cumulative/level), search, individual editing
- •Level points — formula-based calculation, dynamic top-level adjustment, group linkage
- •Points system documentation — POINT_SYSTEM.md detailed design document
- •Member group drag-and-drop — reorder cards with SortableJS + auto-save
- •Member group multilingual —
rzx_multilang_input() common component applied to group name/benefit description
- •Member group seed data — 7 groups × 13 languages of multilingual translation data
Changed
- •Added
balance, total_accumulated columns to DB rzx_member_points
- •Changed DB
rzx_point_levels.group_id from INT to CHAR(36) (UUID compatible)
- •Unified
rzx_point_levels, rzx_member_points collation to utf8mb4_unicode_ci
- •Updated bundle page layout (
sidebar-main-content → flex-1 ml-64 dashboard pattern)
- •Fixed bundle page currency unit
$config['currency'] → $siteSettings['service_currency']
- •Changed service duration from select → input number + datalist
- •Added multilingual input UI rules to CLAUDE.md (mandatory use of rzx_multilang_input)
v1.8.3
2026-03-20 · 7 months ago
Added
- •Bundle detail management page — edit individual bundle info, upload cover image, edit service composition, link staff, toggle active/inactive
- •Event discount — event price per bundle, period (start/end), label settings; auto-activates within the period
- •Bundle list card design — grid card layout, image header, event/discount badge, pinned bottom action bar
- •Recommended package background image — cover image background + gradient overlay on bundle cards in the customer staff detail page
- •Service duration direct input — select → number input (5–480 min, 5-minute steps) + datalist recommended list
Changed
- •DB
rzx_service_bundles columns added: event_price, event_start, event_end, event_label
- •Bundle list API:
total_duration and event columns added
- •Translation files (ko, en, ja): bundle detail management + event discount keys added
v1.8.1
2026-03-19 · 7 months ago
Added
- •POS settings page — card size, image/price/phone display, opacity, default tab, auto-refresh, alert sound, staff assignment required, auto-assignment
- •POS settings applied — card size/image/price/phone/opacity/auto-refresh/default tab all reflected in POS
- •Staff auto-assignment — when enabled in POS settings, automatically assigns staff at kiosk check-in (service-capable + fewest same-day reservations)
- •POS card staff badge — assigned/unassigned indicator; proceed button disabled when unassigned
- •POS service image background — representative service image background on header + customer area of the service detail modal
- •Staff is_visible — controls list visibility; hidden staff excluded from reservations/POS/kiosk
- •Staff card banner — banner image + status badge on admin staff card
- •Kiosk auto-assignment — check-in with staff_id=NULL; assignment done by admin in POS
Fixed
- •POS staff assignment: service list refreshes immediately after assignment (cache prevention)
- •Staff assignment API parameter fix (reservation_id → reservation_ids[])
- •Multiple missing tables/columns fixed in production (migration 018 reinforced)
- •Admin logout route added
- •Kiosk pre-auth route placement
v1.7.0
2026-03-18 · 7 months ago
Added
- •Base layout system —
layouts/base-header.php + base-footer.php, ob_start auto-applied in index.php, common header/footer for all customer pages
- •Board front-end — list, detail (read), write/edit pages, Summernote editor, drag & drop file attachments
- •Board short URLs — access
/free, /notice/3, etc. without the /board/ prefix; automatic DB slug matching
- •Reservation lookup page —
/lookup short URL, search by reservation number / email / phone
- •Reservation detail page —
/booking/detail/{number}, staff greeting bubble, service/payment/cancellation info
- •Reservation cancellation page —
/booking/cancel/{number}, cancellation reason input
- •My page reservation detail —
/mypage/reservations/{id}, sidebar + detailed info
- •Board multilingual —
original_locale (language of first draft) + source_locale (current content language), fallback chain (source → en → original)
- •Staff greetings —
greeting_before (before treatment) + greeting_after (after treatment), multilingual, displayed according to reservation status
- •Admin profile dropdown — profile photo/initials + name + links to homepage / my page / logout
- •Customer header admin link — when logged in as admin, the dropdown shows a link to the admin panel
Changed
- •Unified layout for all customer pages (removed individual HTML structures, LayoutManager applied automatically)
- •Board category display condition changed from
show_category to hide_categories
- •Board URL changed from
/board/free to short URL /free by default
- •Admin topbar "Admin" replaced with actual login name + profile photo
Fixed
- •
rzx_users.role column missing → admin detection now based on $_SESSION['admin_id']
- •
rzx_users.nick_name column missing → using name + Encryption decryption
- •
rzx_staff.profile_image → corrected to avatar column name
- •Service model
category_id int→string type casting
- •Reservation lookup by email/phone uses plaintext search (data not encrypted)
v1.6.1
2026-03-18 · 7 months ago
Added
- •6 board admin tabs — Basic settings, Category management, Extra variables, Permissions, Additional settings, Skin
- •Basic settings tab consolidation — Basic info + SEO + Layout selection + Skin selection + Display + List + Advanced (collapsible)
- •Common components — 8
components/board/section-*.php files (basic, seo, layout-select, skin-select, display, list, advanced, js), reusable across create/edit pages
- •Multilingual input component —
rzx_multilang_input() function, text/editor modes, inline globe button
- •Enhanced category management — tree structure (parent_id), edit modal (name/font color/description/allowed groups/expand/default category), SortableJS drag sort, dynamic DB loading of member grades
- •Extra variables tab —
rzx_board_extra_vars table, 15 input types (including text_multilang, textarea_multilang, textarea_editor), multilingual support, drag sort
- •Permissions tab — Access permissions (all/member/grade:*/admin_staff/admin), module admin (shared user search, profile photo, permission scope), notification mail info
- •Additional settings tab — Integrated board, documents (history/recommend/report), comments (count/depth/approval/recommend/report), WYSIWYG editor (permission matrix), files (download group), feeds (RSS public/copyright)
- •Skin system —
skin.json format, SkinConfigRenderer, "multilang": true support, skin info card
- •Layout system —
layout.json format, card-style selection in basic settings
- •Shared user search —
user-search.php component, encrypt/decrypt, profile photo, reusable anywhere
- •Module admin —
rzx_board_admins table, post/comment/settings permissions, notification subscription
- •Advanced settings expansion — exclude admin from anonymous, Data URL restriction, Unicode abuse, post/comment protection (comment count/reply count/period), super-admin protection, sender email address
- •Toggle switch UI — all yes/no items converted to toggles (basic + additional settings)
- •Translation keys — approximately 300 keys, 13 languages
Fixed
- •Trash SQL error —
LIMIT ? binding string → integer literal
- •Summernote not rendering — jQuery/Summernote JS loading + initialization after modal display
- •Collation error — explicitly specify COLLATE utf8mb4_unicode_ci when joining board_admins
- •Collapse/expand not working — define
toggleSection function in <head> first
Changed
- •Standalone tabs (list/advanced) → merged into basic settings tab with collapse/expand
- •Skin selection → moved to basic settings tab; skin tab shows settings only
- •Admin email → changed to "Sender email address" in advanced settings
- •Module admin → moved to permissions tab
- •"Show category in list" → removed from category settings, delegated to per-skin settings
- •"Show category in list" → removed from category settings, delegated to per-skin settings
- •Writing allowed groups → dynamically loaded from
rzx_member_grades table instead of hard-coded
v1.6.0
2026-03-17 · 7 months ago
Added
- •Multilingual fallback system: built-in fallback chain in
db_trans() (configured language → en → default language → source_locale → any translation)
- •Board settings multilingual button: multilingual (globe) button added to 6 fields in the basic settings tab
- •Board front view multilingual fallback: title, description, SEO, header/footer multilingual applied in
_init.php
- •Widget multilingual fallback:
WidgetRenderer::t(), tBtn() empty-string handling improved
- •PWA translation keys added (13 languages:
admin.pwa.update_available, update, later)
Fixed
- •Features widget empty card issue:
?? operator treated empty string "" as a valid value → fixed with !empty() based fallback
- •Production Mixed Content error:
APP_URL http→https fixed + JS protocol auto-correction
v1.5.2
2026-03-17 · 7 months ago
Added
- •Service deletion from POS service history (delete button + API + immediate DOM refresh)
- •Added service deletion translation keys for 13 languages
v1.5.0
2026-03-17 · 7 months ago
Added
- •Kiosk check-in system: service confirmation → customer info → reservation creation → queue number issuance
- •Staff availability badge: shows busy/available in real time on the kiosk staff selection
- •International phone number module: applied the phone-input component to the kiosk check-in form
- •Reservation sample data seed:
database/seed_reservations.php (10 records across POS/kiosk/online)
- •Split out kiosk common files (
_styles.php, _bg.php, _scripts.php)
- •Added 11 kiosk translation keys across 13 languages
Changed
- •Kiosk service selection: staff-skill-based filtering (designation mode)
- •Center-aligned the kiosk category tabs
Fixed
- •Production admin login failure (password hash corruption — bash
$ variable substitution issue)
- •Update system stabilization: expanded preservation list, health check, DB version sync
- •Unified the single version source (
version.json → index.php, automatic DB sync)