belisoful / prado-forum
A full-featured, template-control driven forum extension for PRADO 4.4: categories, boards, threads, posts, Markdown, reactions, polls, tags, attachments, subscriptions, notifications, search, moderation, feeds, permissions and cron maintenance.
Package info
github.com/belisoful/prado-forum
Type:prado4-extension
pkg:composer/belisoful/prado-forum
Requires
- php: >=8.2.0
- ext-json: *
- ext-mbstring: *
- ext-pdo: *
- erusev/parsedown: ^1.8
- ezyang/htmlpurifier: ^4.17
- pradosoft/prado: ^4.4@dev
- pradosoft/prado-activerecord: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpdocumentor/shim: ^3
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10
Suggests
- ext-pdo_mysql: MySQL / MariaDB storage
- ext-pdo_pgsql: PostgreSQL storage
- ext-pdo_sqlite: Zero configuration default database and the unit tests
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 01:36:26 UTC
README
A complete, template-control driven forum for PRADO 4.4 applications, installed through Composer as a prado4-extension.
The forum is built the PRADO way: one TDbPluginModule (BEForumModule) owns the configuration, the database and the domain managers; every piece of UI is a TTemplateControl you can drop on any page; pages are plain .page templates composed from those controls; authorization runs through PRADO's TPermissionsManager and TAuthorizationRules; and every decision point is a dy* dynamic event so behaviors can extend or replace forum logic without touching the extension.
Features
- Categories, nested boards (locked, hidden, members-only), threads and posts with denormalised counters and last-post tracking
- Thread types (discussion, question with accepted answer, announcement), pinning with expiry, locking, moving, soft deletion and restore, permanent purge
- Markdown (safe mode) and plain text content, rendered through HTMLPurifier, with
@mentionlinks, quoting and preview - Edit history with revisions, edit windows, flood control, approval queue for new content
- Reactions (configurable set), polls (single or multiple choice, closing time, revote), tags and tag cloud, attachments with type and size limits
- Subscriptions to boards and threads, in-forum notifications (replies, mentions, quotes, reactions, accepted answers, moderation, badges, reports), bookmarks, read tracking with unread markers and "first unread post" links
- Built-in search index over posts and thread titles (prefix matching, quoted phrases, relevance ranking; identical on SQLite, MySQL and PostgreSQL; replaceable through
dySearchCriteria) - Thread moderation: lock, pin, move, approve, delete, restore, purge, merge into another thread and split replies off into a new thread
- Moderation: reports, moderation log, warnings, temporary and permanent bans, board moderators, badges
- Member profiles (display name, avatar/gravatar, signature, bio, timezone, notification preferences), member directory, statistics
- RSS/Atom feeds (
TFeedService), a read-only JSON API (TJsonService),prado-cli forum/*shell commands, cron maintenance tasks (TCronModule) - SQLite (zero configuration), MySQL/MariaDB and PostgreSQL through one driver-neutral schema with versioned migrations
- Localizable through
Prado::localize, themable through a CSS class prefix and an optional bundled stylesheet
Requirements
- PHP 8.2 or higher with
pdo,json,mbstring,intl,domand one ofpdo_sqlite,pdo_mysql,pdo_pgsql - PRADO 4.4 or higher (the Active Record package
pradosoft/prado-activerecordis installed with the forum)
Installation
composer require belisoful/prado-forum
Configuration
Add the module to the application configuration (XML shown; PHP configuration works the same way). The module is the bootstrap class declared in composer.json, so the module id may also be the package name.
<modules> <module id="db" class="Prado\Data\TDataSourceConfig"> <database ConnectionString="mysql:host=localhost;dbname=app;charset=utf8mb4" Username="app" Password="secret" /> </module> <module id="forum" class="Belisoful\Forum\BEForumModule" ConnectionID="db" Title="Community" AdminUsers="admin" ModeratorRoles="Moderators" /> </modules> <parameters> <parameter id="PluginContentId" value="Main" /> </parameters>
Without ConnectionID the forum creates beforum.db (SQLite) in the runtime directory. With AutoInstall (the default) the tables are created and upgraded on first use; php prado-cli.php forum/install does the same from the shell and forum/seed creates a first category and board.
The pages ship with <com:TContent ID=<%$ PluginContentId %>>, the PRADO plugin convention: the page service must set a MasterClass whose layout provides a TContentPlaceHolder with that id, a <com:THead /> and a <com:TForm> (without a master, PRADO throws templatecontrol_placeholder_inexistent).
Module properties
| Property | Default | Purpose |
|---|---|---|
ConnectionID |
TDataSourceConfig module id; empty uses SQLite in the runtime path |
|
TablePrefix |
forum_ |
Table name prefix (set before initialization) |
Title |
Forum |
Forum name used in titles and feeds |
PagePathPrefix |
Forum |
Page path prefix of the shipped pages (Forum.Index, Forum.Thread, ...) |
AdminUsers, AdminRoles |
Users/roles holding every forum permission | |
ModeratorUsers, ModeratorRoles |
Users/roles holding the moderation permission everywhere | |
ThreadsPerPage, PostsPerPage, ItemsPerPage |
25, 20, 20 | Page sizes |
ContentFormat |
markdown |
Default content format (markdown or text) |
EditWindow |
0 | Seconds an owner may edit a post, 0 for unlimited |
FloodInterval |
30 | Minimum seconds between posts of a member, 0 disables |
MaxTitleLength, MinPostLength, MaxPostLength, MaxTagsPerThread |
255, 1, 65535, 5 | Validation limits |
EnablePolls, EnableAttachments, EnableReactions, EnableTags, EnableSubscriptions, EnableSignatures, EnableBookmarks, EnableBadges |
true | Feature switches |
RequireApproval |
false | New threads and replies of non-moderators await approval |
ReactionTypes |
like,love,laugh,wow,sad |
Available reactions |
AttachmentPath, AttachmentMaxSize, AttachmentTypes |
runtime path, 2 MB, png,jpg,jpeg,gif,webp,pdf,txt,zip |
Attachment storage and limits |
AutoCreateMembers |
true | Create a member profile for every authenticated user on first contact |
AutoInstall |
true | Install and upgrade the schema automatically |
CssClassPrefix, EnableDefaultStyles |
beforum, true |
CSS class prefix of the controls and the bundled stylesheet |
DateFormat, Timezone, GuestName |
M j, Y H:i, UTC, Guest |
Display settings |
SearchMinLength, EnableSearchIndex, NotificationRetentionDays |
3, true, 90 | Search (minimum query length, built-in index or LIKE fallback) and maintenance settings |
RendererClass, UrlBuilderClass, ShellActionClass |
Replace the content renderer, URL builder or shell action with subclasses |
Pages and controls
The extension ships template-only pages under src/Pages/Forum (Index, Board, Thread, NewThread, EditPost, Search, Tag, Member, Members, Notifications, Subscriptions, Bookmarks, Attachment, Admin.Structure, Admin.Moderation, Admin.Members). They are reachable as ?page=Forum.Thread&thread=12 (pages are &pg=2) and the like; with TUrlMapping the URLs become pretty without any forum change (see Pretty URLs).
PRADO only serves plugin pages that live under the application directory. If your vendor/ directory is outside it (the usual layout), either point the module at a copy of the pages inside your application (PluginPagesPath) or build your own pages from the controls, which is the intended way to integrate the forum into a site:
<com:TContent ID="Main"> <com:BEForumToolbar /> <com:BEForumBreadcrumbs BoardID=<%= $this->Request['board'] %> /> <com:BEForumBoardHeader BoardID=<%= $this->Request['board'] %> /> <com:BEForumThreadList BoardID=<%= $this->Request['board'] %> PageSize="15" /> </com:TContent>
Controls (namespace Belisoful\Forum\Web\UI): BEForumToolbar, BEForumBreadcrumbs, BEForumSearchBox, BEForumCategoryList, BEForumBoardHeader, BEForumThreadList, BEForumThreadView, BEForumPostList, BEForumPostView, BEForumPostEditor, BEForumThreadEditor, BEForumPoll, BEForumReactionBar, BEForumSubscribeButton, BEForumModerationTools, BEForumSearchResults, BEForumTagCloud, BEForumRecentPosts, BEForumStatistics, BEForumMemberCard, BEForumMemberProfile, BEForumProfileEditor, BEForumMemberList, BEForumNotificationList, BEForumSubscriptionList, BEForumBookmarkList, BEForumAttachmentDownload, BEForumAdminStructure, BEForumAdminModeration, BEForumAdminMembers, plus the BEForumPager and the item renderers BEForumBoardRow, BEForumThreadRow.
Templates name the controls by their short class name (<com:BEForumThreadList>): composer.json declares the class map config/classes.json under extra.prado.class-map, which PRADO 4.4 registers for every installed extension. Every control finds the forum module through PRADO's plugin lookup; set ModuleID when several forums exist. Templates only render pre-built, HTML escaped view models: all logic lives in the managers.
Permissions
Permission names live in Belisoful\Forum\Security\BEForumPermissions: forum_view, forum_search, forum_thread_create, forum_thread_edit, forum_thread_delete, forum_thread_merge, forum_thread_split, forum_post_create, forum_post_edit, forum_post_delete, forum_react, forum_poll_create, forum_poll_vote, forum_attach, forum_report, forum_subscribe, forum_bookmark, forum_profile_edit, forum_moderate, forum_admin and forum_shell. By preset, viewing and searching are open to everyone, creating content needs an authenticated user, editing and deleting are for owners and moderators, and merging and splitting threads are for moderators (board moderators, ModeratorUsers/ModeratorRoles, administrators).
Without a TPermissionsManager sensible preset rules apply: everyone reads, authenticated users write, owners edit their own content, board moderators and the configured ModeratorUsers/ModeratorRoles moderate, AdminUsers/AdminRoles administer.
With a TPermissionsManager the module registers the same permissions (with the preset rules) and the manager's roles and rules take over:
<module id="permissions" class="Prado\Security\Permissions\TPermissionsManager" DefaultRoles="Default"> <role name="ForumAdministrator" children="forum_admin, forum_moderate" /> <role name="ForumModerator" children="forum_moderate" /> <role name="Default" children="forum_view, forum_thread_create, forum_post_create, forum_react, forum_poll_vote, forum_attach, forum_report, forum_subscribe, forum_bookmark, forum_profile_edit" /> <permissionrule name="forum_thread_create" action="deny" users="?" /> </module>
Application users get a class behavior: $this->User->getForumMember(), getForumDisplayName() and forumCan('forum_moderate').
Feeds, JSON, shell and cron
<services> <service id="feed" class="Prado\Web\Services\TFeedService"> <feed id="forum" class="Belisoful\Forum\Feeds\BEForumFeed" /> <feed id="forum-posts" class="Belisoful\Forum\Feeds\BEForumFeed" Scope="posts" Format="atom" /> </service> <service id="json" class="Prado\Web\Services\TJsonService"> <json id="forum" class="Belisoful\Forum\Feeds\BEForumJsonResponse" /> </service> </services>
?feed=forum&board=3, ?feed=forum-posts&thread=7, ?json=forum&action=threads|posts|search|tags|stats.
Shell: php prado-cli.php forum/status|install|upgrade|seed|recount|rerender|reindex|maintenance|ddl [driver]. reindex rebuilds the search index (run it once after upgrading to schema version 2, or after disabling and re-enabling EnableSearchIndex).
Cron: the module advertises forum_maintenance (expired bans and pins, old notifications, unused tags) to TCronModule; schedule the recount separately as a forum->recountStatistics method task.
Pretty URLs
examples/forum-urls.xml maps every forum page to a path (/forum/thread/12/page/2/, /forum/board/3/, /forum/member/alice/, ...). Copy it into your application (for example protected/config/forum-urls.xml), register a TUrlMapping as the URL manager of the request and switch the request to path URLs:
<modules> <module id="request" class="THttpRequest" UrlFormat="Path" UrlManager="forum-urls" /> <module id="forum-urls" class="Prado\Web\TUrlMapping" EnableCustomUrl="true" ConfigFile="Application.config.forum-urls" /> </modules>
The forum builds every link through THttpRequest::constructUrl(), so the patterns apply to the controls, feeds and JSON links without any forum change; parameters that a pattern does not name (search queries, filters) are appended as a query string. examples/htaccess.example holds the Apache rewrite that hands such paths to index.php. Patterns are matched in order: keep the variants with more parameters (the page ones) before the shorter ones.
Extending the forum
- Events: the module raises
onThreadCreated,onThreadMerged,onThreadSplit,onPostCreated,onPostUpdated,onPostDeleted,onReaction,onPollVoted,onReportCreated,onNotification,onMemberBanned, ... with aBEForumEventParameter(record, actor, data). Attach handlers in a behavior or module:$forum->onNotification[] = [$mailer, 'send']; - Dynamic events: attach behaviors to the module or its managers to filter decisions:
dyAuthorize,dyCreateManager,dyCreateRenderer,dyBuildUrl,dyValidateThread,dyValidatePost,dyEditWindow,dyNotify,dySearchCriteria,dyAllowedAttachment,dyReactionReputation,dyRenderContent,dyRenderFormat(add a content format),dyCreateMember,dyValidateProfile,dyIsThreadVisible,dyIsPostVisible,dySearchTerms(filter the indexed terms),dyCronTaskInfos. - Managers: subclass a manager and register it with
setManager()or thedyCreateManagerevent; all business rules live there (getThreads(),getPosts(),getMembers(),getBoards(),getReactions(),getPolls(),getTags(),getAttachments(),getSubscriptions(),getNotifications(),getSearch(),getModeration(),getReadTracker(),getBookmarks(),getStatistics()). - Records:
Belisoful\Forum\Data\BEForum*Active Records raise PRADO'sOnInsert/OnUpdate/OnDeleteevents and accept class behaviors. - Templates: copy the pages, recompose the controls, override
CssClassPrefix, or disable the bundled stylesheet.
Development
composer install vendor/bin/php-cs-fixer fix --dry-run vendor/bin/phpstan analyse --memory-limit=512M composer unittest
The unit tests run on SQLite by default. Set BEFORUM_TEST_DSN, BEFORUM_TEST_USER and BEFORUM_TEST_PASSWORD to run the same suite against MySQL or PostgreSQL (the schema is recreated for every test).
License
BSD-3-Clause, see LICENSE.
Author
Brad Anderson (belisoful@icloud.com)