Discussion#
The smallest module in the system — 13 entities, no dynamic parameters, no saved-search
alerts, no expiry. The code lives in app/UI/Api/Discussion/, the front end in
app/UI/Front/Discussion/.
What the module is made of#
| Group | Entities |
|---|---|
| content | Message (a nested set), MessageAssetRel |
| taxonomy | Category, CategoryText |
| interaction | MessageVote, MessageReport |
| payment | Tariff, TariffPrice, TariffQuickPrice, PaymentItem |
| moderation | BlacklistedWord, BlacklistedWordText |
A thread and a post are one entity#
Message is a nested set (lft, rgt, level). A thread is a root node, a reply is
its descendant — there is no type difference between "topic" and "post".
Moving a post is not constrained in any way
An administrator can drag any node anywhere and the system does not check whether it makes sense. Dragging a reply under a different topic makes the discussion incoherent, and there is no undo button.
Deleting a node deletes the whole subtree
Deleting the opening post removes the entire thread including every reply. In the listing it looks like deleting a single row.
What the module does not have#
More useful than a feature list is knowing what is not here — it saves you hunting for a setting that does not exist:
| Missing | How it is worked around |
|---|---|
| locking a thread | turn off "allow comments", or deactivate the post |
| disabling a category | a category has no active flag — only deletion |
| paid access to a category | only highlighting a post is paid for |
| per-category permissions | only the global "signed-in users only" switches |
The "free" field on a category is dead
The for_free column is in the listing and in the form, but nobody in the discussion
module evaluates it — unlike in classifieds and the company catalogue, where it drives
the listing price. It must therefore not be presented to the user as a paid category.
Thumb voting#
Votes are MessageVote rows with the value 1 = thumbs up, 0 = thumbs down. The
counters on the post (votesUp, votesDown) are not written as an absolute value but as
an increment — registerEvent() performs a server-side UPDATE … SET votesUp = votesUp + 1.
An increment instead of saving the whole entity is deliberate
Saving the whole entity would require write permission on the post, which a voter does not hold on someone else's post — and it would send an absolute value from a stale read, so two concurrent votes would lose one of themselves.
Old votes were not carried into the new counter
Older posts show zero. It is not a display bug and it cannot be recomputed retroactively — the historical data does not exist in the new shape.
Payments#
The only paid service is highlighting a post (toped_until). The payment item has no
link to a category — unlike the company catalogue, where the payment buys a
listing in a specific category.
Deleting an active payment item shortens the highlight
An extension from a payment only moves upwards (it is monotonic); a manual change in the administration can go both ways — and deleting the item behaves as a manual shortening.
Settings#
The module has index 8 in the settings table. The form today writes:
| Key | Type |
|---|---|
messagesPerPage, commentsPerPage, maxImageSize |
number |
automaticApproval, messageOnlyForLoggedIn, commentOnlyForLoggedIn, voteOnlyForLoggedIn |
switch |
Automatic approval can be saved, but the new code does not read it
The switch is in the administration and the value is stored — and it affects no approval. Until somebody wires behaviour onto it, it is just a stored number.
Image dimensions and the watermark are not in the settings — do not bring them back
The variants are driven by assetVariants.neon (discussion-messages), the watermark
by the global watermarkPath. The keys were deleted on 2026-08-13
(docs/sql/2026-08-13-settings-drop-unread-keys.sql); if somebody put them back into
the form, the first save would recreate them, because saveSettingForm() writes
every listed key.
Images on a post#
Saving replaces the whole list of images
If the upload field does not come up, the attachments disappear — the same mechanism as with any other collection. In detail: Forms, the section on file uploads.
The author's password#
Message carries a password column — an unauthenticated author can lock their post with
it for later editing.
The password cannot be reset from the administration
The entity has it, the administration form does not edit it. An author who forgets it has to ask for a database intervention.
Scheduled tasks#
| Endpoint | What it does | Recommended interval |
|---|---|---|
/cron/discussion/elastic/sync |
incremental index synchronisation | every 10 min |
/cron/discussion/elastic/reindex |
a full reindex | nightly |
/cron/discussion/sitemap/{message,category} |
sitemaps | nightly |
Where to look#
| I want | Where |
|---|---|
| the post and tree logic | app/UI/Api/Discussion/Models/Managers/MessageManager.php |
| voting | app/UI/Front/Discussion/Components/CommentListRowComponent/Traits/VoteTrait.php |
| the incremental counter write | app/Core/Traits/Api/Models/Managers/RegisterEventTrait.php |
| the module settings | app/UI/Admin/Discussion/Presenters/SettingPresenter.php |
| the cron tasks | app/UI/Cron/Discussion/Presenters/ |
Follow-up chapters: Blog · Company catalogue · Elasticsearch · Forms