Forum and thread types
From a development perspective, it's important to consider forum and thread types as separate systems with a bit of interaction and encompassing similar ideas. However, they are totally separate systems and there's nothing to say you can't define a forum type without defining any unique thread types and vice versa.
Though there are some exceptions to this rule, when viewing a forum, you're dealing with a forum type handler and when viewing a thread, you're dealing with a thread type handler.
We're not going to go into every method these type handlers cover in this thread; we're just going to go over some concepts that relate to each. The abstract handlers have full phpDoc blocks that try to explain how the methods are used and in some cases, things to keep in mind.
Array validators
Before we dive into forum and thread types, it's worth noting the new
XF\Entity\ArrayValidator
class. This is designed to add simple entity-like validations to a basic array, including things like types, constraints, required fields, and validation callbacks. Both forum and thread types use this for their type config/data columns to centralize data validation.
Basic setup mirrors entity column definitions:
PHP:
protected function getTypeConfigColumnDefinitions(): array
{
return [
'display_style' => ['type' => Entity::STR, 'allowedValues' => ['full', 'expanded']],
'expanded_snippet' => ['type' => Entity::UINT],
'expanded_per_page' => ['type' => Entity::UINT],
];
}
Forum types
Forum types add an additional
type_config
field to forums where you can store, well, forum-type configuration options. The defaults are setup in the
getDefaultTypeConfig()
handler method. When accessing this via the entity, we merge the defaults with any overrides, so you can be sure that each config option is present, which helps avoid unexpected errors when new config options are added.
Setting up custom type config options involves defining an admin template to contain those options, along with a method to handle reading the config from input and validating it. Best practice would also be to expose these options to the REST API, so there are methods to both expose the configuration when the forum data is returned and to update the config itself.
It's worth mentioning that a forum's type can be changed. The change process involves setting up the new type config options before the forum's type has actually changed. This is important because changing a forum type may change the type of the threads within the forum and that can be affected by the type config. (It is possible to block changing out of a specific forum type via the handler, if needed.)
Each forum type also defines the allowed thread types within that forum. This will affect the UI when creating a thread. If you want to add behaviors when viewing the forum that depend on the thread being a particular type, then you would likely want to make your forum only accept a single thread type. (Note that
redirect
threads are always allowed, so that is something you may need to account for.) This is done via:
getDefaultThreadType
- defines the type that will be selected by default if there are multiple creatable types and the type that will be used if the thread is created in a manner that doesn't explicitly set the type.
getExtraAllowedThreadTypes
- any additional thread types that may be allowed.
getCreatableThreadTypes
- potentially a subset of the first two methods, this differentiates the types of threads that users can create manually vs those that can only be created internally. As an example, resource discussion threads would be allowed in discussion forums but end users can't manually create them.
The majority of the remaining methods relate to controlling the display and behavior of the forum view pages. Some of the things you can do include:
- Override the display entirely. This is the nuclear option, but if you don't want to use any of the controller or display code from a default forum, you have that option.
- Override the name of the view or template (
forum_view
) and manipulate the parameters sent to the template.
- Override some of the macros used, such as for displaying thread list items or quick thread, and optionally pass additional arguments to the macros.
- Add additional ways to filter the threads shown or to customize the available sort orders.
- Adjust the thread list finders and to fetch additional content with the threads.
- Customize the number of threads shown per page. (This is primarily for situations like the expanded article view, where you're going from a very small amount of content per thread to a large amount.)
In conjunction with the template extension system, these options give you the ability to heavily customize how a forum looks and works, while being able to use the existing code and default output where you want it. It also means that unless you change a particular area, any add-ons that change the output or additional behavior will still applied.
Thread types
Many of the concepts we just explained under forum types apply to thread types as well. However, some times the functionality differs.
Similar to forum types'
type_config
column, thread types expose
type_data
. This may expose configuration options, but it may also include additional data that's specific to that thread type. For example, with questions, we store the post ID and user ID for the solution in this data (though we do also mirror this to a separate table to aid certain tasks). However, some thread types may involve significantly more complex data that you might not want to store in the thread record. Our type-specific data management methods are designed to handle this.
Thread type data is displayed via the
renderExtraDataEdit
method. Note that this is used both in thread creation and thread editing contexts. Information about the context is passed into the method, allowing you to react appropriately. For example, while polls can be created with the thread, there are different constraints on editing an existing poll, so we don't show anything unless we're in the
create
context.
When processing the extra data, there are two approaches: simple and service. Simple processing is essentially the same as what we saw in forum types. We use the array validator system to map inputs to the extra data we want to store. The service approach allows you to create a service object to setup the actions you want to take. This will be used in conjunction with the base service (thread create, edit, etc) to ensure that no action is taken unless both services validate and once the base service's action has run, the type data service will execute as well. This is wordy, but it's roughly equivalent to how we handled poll creation in 2.1 but in a more generic way.
In terms of thread viewing, thread types introduce two new concepts:
- Pinned first post - if set for the thread type, the first post of the thread will be pinned to the top of each page. This is integrated directly into
thread_view
so simply toggling this on will make it work, though there are some additional overrides that allow you to easily change the display of just the pinned first post.
- Highlighted posts - these are an arbitrary number of additional posts that will be fetched on each page. You're responsible for controlling how and where they will be displayed. This is how the solution is handled in questions, but there are virtually limitless ways this can be used to enhance threads.
Beyond that, thread types allow you to override a number of things in a similar way to forum types:
- Override the thread display entirely.
- Override the name of the view and template used and manipulate the parameters sent to the template.
- Override some of the macros used, such as for displaying posts, deleted posts or the pinned first post, and optionally pass additional arguments to the macros.
- Apply custom filters to thread viewing. While this isn't currently being used out of the box, it made sense to bring the concept of filters to threads so that we could apply the necessary behavior changes when a thread is filtered.
- Allow custom sort orders and control the default order. Note that while it is possible to change the default order of a thread to be something other than chronological, chronological sorting will always be available and there are situations where we will automatically switch to it. There are also caveats that non-chronological orders will disable some standard behaviors, most notably thread read marking.
- Adjust the post list finder and to fetch extra content for individual posts.
- Control when thread or post voting is supported and the related permission checks.
Thread types also define extension points for various thread and post lifecycle events, including:
- Thread pre-save, post-save and post-delete.
- Thread is made visible or hidden.
- Thread enters or leaves the type.
- Thread's counters are rebuilt.
- Threads are merged into the thread.
- A visible post is added to or removed from the thread.
- A post in the thread is saved or deleted.
While there is significant power in both of these systems already, we still have ideas for additional places where these systems can hook into XenForo to provide even more flexibility. We're very excited to see what can be done with these systems!