XenForo 2.0 development updates from xf2demo

Not open for further replies.


Company info
Staff member
Following some requests for the text of the development updates posted on the XF2 demo site, which has now been retired from use, here are the posts...

Please note that these have been copied as-is from the development demo site, and as such some of the information contained in the text may have been superseded or otherwise changed.
Last edited by a moderator:
Development Update (November 25th) (@Mike)

Its been a bit over a week since we first opened this demo up and started getting feedback and bug reports. We have had a lot of feedback and bug reports to pour over to help improve XF2. Thank you very much to anyone who has voiced their opinion or reported a bug. Please keep posting your thoughts.

We've been very busy since the demo launched, though you might not see too many differences visually. The majority of our focus to this point has been fixing bugs; making sure things work as expected is definitely our highest priority. Once we feel like we've gotten on top of those (and we're getting there), you're going to start seeing more style and interface changes and tweaks. The lack of changes to things like the navigation bar does not mean that we have no intention of making adjustments there, for example.

So in order to give you an idea of what we've been working on (especially when you can't necessarily see a lot of the changes explicitly) and to highlight some specific changes of interest, we're going to be posting occasional development update threads like this one. We won't go into too much detail on most of the changes and some will just refer to the underlying code changes, but it should still give you insight into what we're doing.

So, a few of the more visible/interesting changes:
  • Updated member tooltip system. On touch, this is triggered on click; a second click of the trigger element (once the tooltip is open) or the name/avatar in the tooltip will take you to the profile. On desktop, click triggers the same behavior; hover and focus still work and roughly behave as before. Thoughts?
  • Add support for "forcing" tooltips to display within the current viewport (rather than just within the body).
  • Add mention support to the disabled rich text editor.
  • Ensure that editor selection is maintained when the editor is blurred. This sorts problems with positioning of inserted quotes or attachments.
  • Voting in a poll no longer requires a reload to display the results.
  • The poll widget now supports voting and improved "simple" styling (more consistent with other widgets).
  • Implement initial version of syntax highlighting in the control panel template editor. (Still to mostly be considered a trial/test.)
  • Updates to the member stats system to fix how the overview page was generated and to make the overview page significantly less busy.
And the rest:
  • New system for "required" parameters in route matching.
  • Easier methods for skipping ignored content in thread/post queries ($finder->skipIgnored()).
  • Support autofocus in the JS "toggle" system (as used in profile post commenting).
  • Ensure always scrolled to the right #location even when images shift the page.
  • Support member tooltips on mentions.
  • Track/style recently viewed and new alerts separately (styling still being tweaked based on feedback).)
  • Re-add support checking for new messages via the draft system and make the draft controller plugin more flexible. New JS structure for easier dynamic message insertion.
  • Added separate permission surrounding viewability of the member tooltip vs the full profile.
  • New "switch overlay" system for adjusting links based on the results of an overlay form submission (watch/unwatch links). Switch settings (text and class changes) are marked up in the HTML rather than being hard coded in the PHP. This also means that the watch/unwatch links don't require a page reload.
  • Tweaks to editor button sizes to improve the sharpness of the Font Awesome icons.
  • Optimize the page title/favicon update system to not bother rebuilding them if no changes have been detected.
  • Make sure the user privacy system has enabled the ChangeLoggable behavior (so changes will be automatically logged).
  • Reporting content no longer causes a page reload. This (and a few other places) now give a more specific "flash message".
  • A new option to the ajax form submission system to control whether a redirect is followed based on whether the form is shown in an overlay. (If enabled, in an overlay, don't follow the redirect; otherwise do.)
  • Tweaks to the notice "x" (close button) styling to ensure there's spacing around text and generally improve the location of it.
  • Update to the new "user remember key" system to ensure that when logging out, your current device's remember key is invalidated and when changing/resetting your password, all existing remember keys are invalidated. Drop the no longer used remember_key column (and csrf_token).
  • Add a new secret_key (based off the original remember_key) to users. This can be used in things like emails when we want to ensure the link was sent to them but don't necessarily want to force them to login.
  • Drop hash tracking from the lightbox. While ideally helpful, the implementation was not up to scratch. It may be reinvestigated in the future.
  • Adjustments to inline mod highlighting system and simplification of the applied color to be more consistent. Ensure that IDs are removed from inline mod when you take an action on them (for example, leaving a conversation).
  • Move the inline mod checkbox to the second row in thread/conversation lists.
  • Simplified the usage of the mark forums read overlays as we have separate buttons for forum-specific and global actions now.
  • Prevent a situation where the template compiler stopped compiling if template syntax wasn't found in the first 70 kilobytes or so in the template (but where it had some later).
  • Store a count of the number of active external account providers so we don't show the link when unnecessary.
  • Ensure we don't trigger iOS's tap highlight on certain elements that are designed to invisibly cover others.
  • Ensure that, when messages are inserted dynamically, all JS functionality is enabled on them as needed.
  • In the lightbox, hide the bar and the caption much more quickly. Also, adjust the zoom icons to only show as active when zooming would do something.
  • When a label (prefix) is included in a link, adjust the link styling to ensure the space between the label and the remainder of the link doesn't show a weird looking underline.
  • When previewing a message edit, show existing attachments that are embedded into the message properly.
  • Restructure the code for first post editing (which includes editing of thread details when possible) to allow easier extension of thread edits.
  • Silently disable form buttons while the form's ajax submission is processing to reduce chances of double submits (such as from double clicks).
  • Tooltips have now separated the tooltip itself and the trigger handling from the elements that configure specific types of tooltips (regular, element, preview, member, etc). This allows more configuration options and improved reuse. It also allows use of tooltips with non-traditional triggers (select to quote).
  • Ensure that hover/focus triggers on tooltips aren't run when switching tabs or doing a long press on mobile.
  • Support menus in tooltips.
  • Support a new "touchclick" tooltip trigger which works as a click but only applies to touch devices.
  • Take steps to prevent arrows from "popping out" of tooltips when the triggers are close to the page edge.
  • Add a new dynamic z-index configuration system to ensure that things like menus inside tooltips inside overlays work as expected.
  • Fade out the thread preview tooltip content at the end to indicate it's not scrollable (and make it look better).
  • Drop the "bars" progress indicator on narrow responsive views, keeping only the line. On a smaller screen, the line should be sufficiently visible on its own.
  • Adjust editor initialization system to ensure that it can handle situations where the base editor JS code is not yet loaded. Fire events when we start doing editor initialization and make it easier for custom JS to extend this in the same way.
  • Don't show or count robots in the online visitor system any longer. They can still be browsed on the online visitor list.
  • When fetching the permission_combination_id of a user, make it react dynamically based on their user_state (this is the method of forcing guest permissions on non-valid accounts).
  • New system for handling common error types (no permission, not found, etc) through a controller plugin. This resolves issues where the online location of a user would be incorrect while also allowing add-ons to extend or change the behavior of these pages more easily.
Development Update (December 9th) (@Mike)

It's time for another development update to highlight what we've changed in the past two weeks.

The most noticeable changes are likely style/interface related. Some of these will be immediately apparent, but others are a lot more subtle:
  • Navigation has been changed to a "tab" style approach to more significantly connect to the sub-navigation. Icons have been removed and padding has been changed.
  • The darkening on the navigation row has been removed and only kept on the visitor-related links to separate them from the main navigation.
  • The position of the search and visitor-related links have been swapped.
  • Maintain the navigation row rather than the logo row on mobile (use media queries to display a small logo here).
  • Support a sticky navigation row/header. (This uses position: sticky when available to be more efficient and accurate. iOS is the best example of this.)
  • Adjusted padding, bottom border and hover styles for the sub-nav row.
  • Enlarge some clickable areas of the navigation row and add a bit of separation in other cases.
  • Adjusted off canvas navigation color and styling to be more consistent with the "main" navigation area.
  • Shift some of the header/navigation links outside of the default page edge padding so they align better visually (even with padding).
  • Take the forum list titles from "larger" to "large" text size.
  • Drop the "large" text size from 18px to 17px. (We still use this in thread list titles.)
  • Make the page title display in 24px normal weight font (rather than 30px light weight).
  • Re-add the bottom breadcrumb.
  • Ensure the footer always sticks to the bottom of the page (on recent browsers).
  • When the sidebar is pushed down and multiple sidebar elements now fit in a row, try to give a consistent height to all of the blocks for a more even look.
  • Very slight color tweaks: borders in general are slightly darker; the distinction between the content background, content background "neutral" and page background is slightly greater; muted text is slightly darker)
  • Drop the page edge padding to 10px (this is more noticeable on mobiles which have areas that push to the page edge).
  • Remove the "page edge liner" darkening on wide monitors. (Mostly due to z-index issues. It would need to be reapproached.)
  • Adjust "form section headers" to align more consistently with the labels (but still expand if needed).
  • Don't bold the last post date in unread threads.
  • Center align usernames and user titles in messages.
  • Make the message user info/message content border slightly stronger. Make the message signature border slightly stronger.
  • Make the message "action bar" one font size larger than it was.
  • Adjust quotes/BB code blocks to have a slightly larger orange border and a border to increase differentiation from the post. Quotes also default to "small" text.
  • Move the "preview" button from the submission row to closer to the message editor.
  • Adjust the styling on the attach/preview/insert quotes buttons to not grab attention compared to the form submission buttons.
  • Remove the "more options" links from quick reply as they now expose very few additional options (at best).
Some of the other more interesting changes include:
  • Profile posts in the sidebar widget are now height limited with a fade out. The full version can be seen by clicking the date or "interact" button.
  • Mention auto-completion in the rich text editor will now be positioned directly in line with the mention, provided there's space.
  • You can now drag text and HTML into the rich text editor. (Dragging within the editor is not currently supported.)
  • Expanded new posts widget display options to allow choice of display types: latest posts (read or unread), unread posts only, watched only (read or unread in watched threads/forums). Guests always see latest posts only.
  • Add support for passing explicit page context options into widgets (for example, passing the current thread) so they can react in a context sensitive manner if desired. Add some additional widget positions, support pushing widgets into a global sidebar (does not interfere with page-specific sidebars).
  • Support a new HTML widget that actually allows you to use XenForo template syntax. This can be used in fairly advanced ways to create more complex widgets or complex conditions over widgets.
  • Add a code editor when editing BB code media site templates. This is done via the <xf:codeeditorrow> tag.
  • Add a code editor to the rich text editor code overlay.
  • Add a button to insert inline code tags to the rich text editor.
  • Give options to choose from the various available languages in the code overlay (including dynamically switching the highlighting).
  • When displaying highlighted code in a message, give an indication of the code language in the upper right corner) and use a nicer syntax highlighting style.
And of course, the slightly more boring stuff:;)
  • The editor smilie list positioning has been changed so it isn't hidden when the editor is scrollable and the border doubling when re-closed has been resolved.
  • Some auto-complete issues have been resolved in mobile browsers.
  • Adjust the maximum height limit of the editor to handle small windows better. (And include iOS specific adjustments to account for the on-screen keyboard.)
  • Restored undo/redo editr buttons primarily for mobiles that don't have reasonable access to keyboard shortcuts.
  • Collapse list-related editor buttons into a single menu to avoid button wrapping on some tablets.
  • Make disabled editor buttons more faint and make the RTE disabled icon more visible when active.
  • Small improvements in the display of block link split toggles in older browsers.
  • Fix a few HTML validation issues.
  • Make sure we don't include itemprop attributes on avatars unless in a context where it makes sense.
  • Fix an issue where "view older results" in searches wouldn't maintain the search constraints correctly.
  • Support inline moderation actions on a few more pages.
  • Fix the lack of a trailing space when clicking on an auto-complete mention option.
  • Ensure that we don't re-focus the editor after submitting a quick reply.
  • Make sure that you can immediately type into the editor after dragging in an image or a file.
  • Focus the editor immediately when clicking the "quote" button on a post to ensure the keyboard appears on iOS.
  • Take steps to ensure that the cursor remains on screen when dynamically inserting content or pasting into the editor.
  • Take steps to ensure that the cursor remains on screen when the window is resized (particularly when an on-screen keyboard appears).
  • When choosing the delete draft button, ensure that the editor does not try to re-save that content unless you make a change.
  • Adjusted wordings where possible to use "latest" and "unread" wordings to be clearer about what's included. ("New posts" is intentionally ambiguous because of various filtering options.)
  • Reintroduce the log in/sign up button on threads and forums if you don't have permission to create content there to encourage registration.
  • Destroy the message preview container after submitting a quick reply so that any video inside it doesn't keep playing... and playing... and playing... Why did you embed a 10 hour video?
  • Link some of the stats on the member tooltip.
  • Display awarded trophies on the profile about page.
  • Apply block--messages to conversations to be consistent with threads.
  • Fixed an issue where profile post comments tried to escape when a profile post was quick edited. Sneaky ones!
  • Rename some of our core CSS files to be clearer what's included globally (core.less and core_*.less files) and what is automatically included in app-specific areas (app.less and app_*.less files).
  • Remove a few of the less popular default custom fields (AIM, etc) from new installs.
  • Add a placeholder in quick search to the "by" field to make it clear that it wants member user name(s).
  • Adjust ajax form submission JS events to use the new "type:action" approach consistently (AjaxSubmitError to ajax-submit:error, for example).
  • Ensure that captchas refresh regardless of whether the form submission errors.
  • Shuffle the thread list icon order; watch will now always be the rightmost icon.
  • Kill the remaining references to our old border color system and standardize on the new "structural border" colors.
  • Link the entire "X said" text to the source message.
  • Provide a base class to shift active page anchors by the size of the sticky navigation to ensure the top of the content you're linked to doesn't go under the header.
  • Create a new system for dynamically loading the Facebook JS SDK on demand (such as when a Facebook video is embedded). This should be generalizable to other cases where we need to dynamically adjust external JS state/systems based on content in the page.
  • Determine workarounds for bugs in Chrome's current position sticky implementation and created reduced test cases so they can hopefully be fixed in a future release. Until then, position sticky usage has been disabled in Chrome.
  • In development mode, automatically write out an add-on-specific style_properties.less file to aid with auto-completion of style property references.
  • Give the quick reply submit row a specific variant class to more easily control the alignment of the submit button.
  • Add support for passing dynamic attribute lists into most of our form-related template elements.
  • Experiment with suppressing flash messages by default when following a redirect. This can be overridden by changing the forceFlashMessage option.
  • Restore the hScroller to the search tabs.
As before, this isn't everything. There are some bug fixes and tweaks that aren't covered here, plus some changes to things that aren't necessary public facing anyway or is still under development.

So until next time...:)
What's new for developers in XenForo 2 (part 1) (December 16th) (@Mike)

Our original what's new in XenForo 2 thread focused on changes that will be visible to end users and administrators. While it did include some references to more developer-oriented changes, we thought that it was time we had a separate thread focusing on what's new for developers.

If you haven't read it, it's worth having a read over some parts of our previous development updates as they talk about some of the development-related changes.

So, in no particular order...

Entities and finders
Entities are XF2's version of data writers. However, they're much more extensive, as:
  • They are used for reads and writes. Most work you do with data will be through an entity.
  • They automatically handle encoding and decoding of values. For example, if you define a column as JSON, you just give it the original data structure (probably an array) and it will encode on save. Similarly, when the record is fetched, the value will be decoded back to that array when you access it.
  • They can have getters to wrap logic around grabbing a value that isn't necessarily stored in the DB. For example, option titles are actually phrases, so there's no title column in the option table. However, we can call $option->title or $option['title'] and it will return a phrase object.
  • They define their relations and they can be dynamically loaded as needed. $post->Thread->Forum will return the forum that the post belongs to, regardless of whether you included the thread and/or forum records when you loaded the post.
  • They support some amount of horizontal reuse through the "behavior" system. This currently only adds extra behaviors when write actions are taken, but examples are to automatically adjust likes when content is deleted or hidden and applying change logging to an entity.
  • Other methods can be added to expose whatever other functionality you need. Call $user->authenticate($password) to see if the password is correct or $thread->canView() to determine if the current visitor can view the thread.
Finders are essentially query builders. In the generic case, they provide methods such as where or order that allow you to apply conditions, ordering and the like based on the DB structure of an entity. However, they can also expose entity-specific methods to provide more complex functionality. Here's an example:
$finder = \XF::finder('XF:Thread');
$finder->where('node_id', $nodeId)
    ->with(['Forum', 'FirstPost'])
    ->order('last_post_date', 'desc')
    ->limitByPage($page, $perPage);
$threads = $finder->fetch();
Most of these are generic finder methods, but unreadOnly() is a thread-specific method that applies logic based on the current visitor and complex conditions to limit the results to only unread threads. While XF1 had some similar concepts (implemented very differently), the complex restrictions weren't generally re-usable and that cause a lot of repeated code. In XF2, these constraints are generally shared by the finder object and can be reused. As an example, it's trivial to display only threads that the user has posted in but are also unread.

There's a lot more to this topic. It's a major change to the fundamental data access layer of XenForo and is probably one of the most important concepts to grasp in XF2. As we have other things to cover, let's just add a couple bullet points:
  • When fetching multiple entities, a collection object is returned rather than a bare array. You can get the array if you like, but the collection object has various helper methods (pluck, filter, groupBy, etc).
  • Relationships in entities can return many entities. To take a specific example, a thread may be watched by many people, but we generally only care about one person at a time. $thread->Watch[$userId] will return the watch record for the specified $userId. It won't eager load all of the other watchers. The "Watch" value is a "sparse" collection of entities that loads individual records on demand (or you can tell it to fetch all of them).
  • You can eager load a single record of a "to many" relationship: $finder->with("Watch|$userId").
  • Finders can be built in any order. You can apply a condition after you set the order or limits.
  • You don't need to manually create a finder in every case. We have common methods in "repository" objects that setup finders for base/common cases. These repositories can also hold other logic/actions relating to a type of entity. Some examples include methods like countUnreadThreadsInForum(\) or rebuildThreadUserPostCounters(). While these aren't meant to be a generic dumping group, they can also be used for various helper methods, particularly when they manipulate the database directly or don't relate to a single entity.
  • Most of our finder-related repository methods return the finder rather than the results. This means that they can be manipulated for specific cases or extended to add extra behaviors. For example, findThreadsForForumView() could be extended to always include a specific relation (though there is another place this could be done within the thread finder).
Ok I lied, that was more than a couple. :)

Service oriented system
Most complex actions have now been moved to service objects that represent that action only. Most services work in a "setup and go" type of approach; you setup your configuration and then call a method to complete the action.

This example was posted in the development update, but it's a good example:
/** @var \XF\Service\Thread\Mover $mover */
$mover = $this->app()->service('XF:Thread\Mover', $thread);

if ($options['alert'])
    $mover->setSendAlert(true, $options['alert_reason']);

if ($options['notify_watchers'])

if ($options['redirect'])
    $mover->setRedirect(true, $options['redirect_length']);

if ($options['prefix_id'] !== null)


This is roughly the sort of code you'd see in the controller. $options would be based on the user input. The actual code for doing the changes is done within the thread move service. This includes actually moving the thread and changing the thread prefix, creating a redirect, notifying the watchers, sending the alert and logging the action. Any other location wanting to move a thread can call this service with the desired configuration and go; there's no duplication of "business logic".

Now this example is a little simplified for the sake of keeping it focused on the service. However, what you'll actually find is that most service calls in XF2 create and configure the service in one method and then pass the service to another method to actually carry out the action. This is what's actually in the move method of the thread controller:
$this->setupThreadMove($thread, $targetForum)->move($targetForum);
Because the services do all of the setup in the setupThreadMove method, this creates a perfect hook/injection point for custom behavior. Add your custom options to the service and then configure them by extending the setupThreadMove method. No creating hacky global variables/registry keys to try to inject your additional changes.

Form actions
Speaking of avoiding hacky global variables and registry keys to do extensions...

Not all actions use services. A lot of the admin control panel pages fit in the CRUD model: create, read, update, delete. They just take user input, pass it to the entity and save it. They don't try to manipulate the user input in any way or trigger other actions.

Instead of creating a service for every single case, we use a FormAction object that allows us to mimic the "setup and go" behavior of services. These actions break down into several phases: setup, validate, apply, complete. Behaviors can be added to each one and when ready, we "run" the action. If any errors occur during validation, they're thrown and apply never runs.

The simplest case would be something like how we save a style:
$form = $this->formAction();
$input = $this->filter([
    'parent_id' => 'uint',
    'title' => 'str',
    'description' => 'str',
    'user_selectable' => 'bool'
$form->basicEntitySave($style, $input);
basicEntitySave() is a helper method that pushes the input into the entity during setup, validates it through preSave(), and saves it during the apply step.

Like in services, this happens within a method and the form action is returned from it and run:
So if you have modified the form to add extra input, you'd just inject that using something like:
$form->setup(function() use ($style, $customValue)
    $style->custom_value = $customValue;

Use of LESS (CSS pre-processor) and BEM-styled CSS
We've discussed this in the past so I won't go into too much detail but it's worth mentioning it again.

In general, we're using LESS rather than raw CSS. If you're not familiar with LESS, I recommend checking out the functionality listed on their site to get a better idea of how it works. You'll find it's very similar to CSS, so there isn't a huge learning curve. It provides functionality like various manipulation functions (such as darkening colors) and support for nesting selectors. Significantly, it also provides "mixins" which allow us to easily create functions to encapsulate common CSS patterns.

(Yes, we considered SCSS on multiple occasions. It wasn't a fit for our needs. We won't be changing from LESS.)

Generally speaking, our CSS is structured along the lines of BEM: block, element, modifier. This means our classes are organized along the lines of .block, .block-row, .block-row--minor. If you explore the HTML you see here, it should be much clearer.

Unified template system, templates in the file system
There is now only one template system to power public-facing, admin and email templates. They are separated by type, but otherwise they are identical. This means:
  • The same template syntax is available everywhere (though you wouldn't want to use most complex features in email templates).
  • You can include a template of a different type from the current template by prefixing the type (public:thread_view).
  • The same editing system is used for each type of template, so functionality is shared (includes history, merging and template modifications, for example).
This means that email templates are now customizable. The email will be build using the site's default style. Email components now come from specific parts within the rendered template base on tags like <mail:subject>. We also automatically inline any CSS in emails to improve display in various clients.

Finally, all templates are now always served from the file system. This means that they will benefit from opcode caching.

Extended template syntax
While the template syntax is fairly similar to XF1, XF2's functionality is expanded. Let's look at an example from a user's profile:
<xf:if is="$user.canPostOnProfile()">
    <xf:set var="$firstProfilePost" value="{$profilePosts|first}" />
    <xf:macro template="profile_post_macros" name="submit"
        arg-lastDate="{{ $firstProfilePost.post_date ?: 0 }}"

<xf:if is="$profilePosts is not empty">
    <xf:foreach loop="$profilePosts" value="$profilePost">
        <xf:macro template="profile_post_macros"
            name="{{ $profilePost.message_state == 'deleted' ? 'profile_post_deleted' : 'profile_post' }}"
<xf:else />
    <div class="block-row">{{ phrase('there_no_messages_on_xs_profile_yet', {'name': $user.username}) }}</div>
Things to note:
  • You can call (some) methods on entities in templates. They must have a whitelisted prefix (that implies the method does not change data).
  • {$profilePosts|first} is using a filter to pull out just the first value from the collection/array. Filters are roughly another syntax around manipulating the first parameter. They can be chained and take arguments though: {$value|replace('from', 'to')|to_upper}
  • Conditions can be "is" and "is not". These are known as tests. Currently only empty is exposed but this may be expanded. These tests can wrap complex logic in an easy to understand way. (The "empty" test is true if the value is an empty string, false, null or empty array; or if the value is a countable object with a count of 0; or if the value is an object with __toString that evaluates to an empty string.)
  • Complex expressions are triggered with {{ ... }}. You can see calls to templater functions here (phrase), the use of the ternary operator, and the creation of name-value pairs like in JavaScript with curly braces. Other operators (such as math and concatenation) are available and non-string values such as true, false and null can be represented as such.
  • The big one thing here are the calls to macros.
Macros are similar to template includes, but they take particular arguments. It is much easier to see what options are available for a given macro and the macro variables don't pollute the scope of the calling template. The profile post macro is defined as:
<xf:macro name="profile_post"
    arg-showTargetUser="{{ false }}"
    arg-allowInlineMod="{{ true }}"
It defines all of the available arguments, which become available within the body of the macro as {$profilePost}, {$showTargetUser}, {$allowInlineMod}. The value of the macro arguments is their default value if not provided; the "!" indicates that it's a required argument and will error if not provided.

Again, there's a lot more to this can than be covered in this overview.

New routing system
In XF1, custom PHP code had to be written to match and generate most URLs. In XF2, we use a custom syntax to allow us to do both automatically in most cases.

Threads are a simple example. The "threads" route prefix is entered and the following is given as the "route format":
(Because of the route prefix, this is effectively matching threads/:int<thread_id,title>/:page.)

The :int<thread_id,title> indicates that it's an integer-based parameter. For building an outgoing link, we pull the integer from the thread_id key of the data passed in. If a title key is passed into the data, it will be "slugified" and prepended to the integer ID like you see in the URL to this page. For matching an incoming URL, this gets turned into a regular expression that matches the integer parameter format.

:page is a shortcut for generating the page-123 part of a link. In this case, it looks for the page in the link parameters. If found, it's put in the URL and then removed from the params. For incoming parsing, if matched (it can be empty), it will add the page number to the parameters passed to a controller.

When a link is built in template, it's done through a call like this: {{ link('members/following', $user, {'page': 3}) }} I'm using this particular example because of the "following" part. Normally, this will be the action; the link will be built with the basic "members" match. However, if there's a route that matches the prefix "members" and the "sub-name" following, it will be used instead. This is true here, so it builds a link like the following:
For incoming route matching, this route will be tested before the basic members route; if it matches, it will be used.

This sub-name system allows behavior changes (such as moving where the page param would normally go in this example) or sub-grouping (such as used in things like the resource manager and add-ons).

As with the other systems discussed today, there are a lot more options available to the routing system.

Content type management
Content type handlers are now managed through the control panel and will be included as part of add-ons. No more manual management.

To account for this, content types themselves are not attached to add-ons. Only the specific handlers. This avoids certain bugs and issues that happened in XF1.

Most content type handlers are fairly similar to what you'd find in XF1. For example, attachment_handler_class would map to XF\Attachment\Post for posts. There are some new ones though, including:
  • entity, which maps a content type to an entity (and therefore to a finder and repository)
  • phrase, which maps a content type to a phrase name (singular version)
  • phrase_plural, which maps a content type to a phrase name (plural version)
  • ...and other handler class systems which may not have existed in XF1

This is far from everything. There's too much to cover in one day, s we'll be back later with more details. :)
Last edited by a moderator:
Development Update (December 23rd) (@Mike)

It's that time again! Let's have a look at what we've been working on over the last two weeks. There haven't been a lot of changes to areas that you can see here, but there have been some significant changes.

Some of the more interesting/visible bits:
  • Re-add the "your content" link to the visitor menu. We may try to support a nested menu to allow this to be filtered down to specific content types more easily.
  • When opening the page nav "..." menu, the page number will now default to the first overflow value and, if possible, the entire text will be selected to make typing a new value easier.
  • When you see your avatar on your profile or in the visitor menu, there's now a link that appears (on hover) to allow you to quickly change it.
  • Add support for sharing to WhatsApp (only shown on small touch screens).
  • Re-add image expansion for images that have been shrunk below their natural width. They will now trigger the lightbox (which allows zooming) when the icon shown on an image is clicked.
  • Fonts are now more consistently based on the OS system font. (For example, MacOS will now use San Francisco.)
  • Lots of ACP styling improvements, though you wouldn't have seen it before. It's now fully responsive.
  • Do some ACP navigation reorganization. There are changes, but it's not drastically different; you should still be able to find things easily enough.
  • New responsive data tables system that dynamically converts tables to a vertical, key-value styled system on small displays.
  • Added a system for tracking our CSS breakpoints in JS. This allows JS to know the current width and how that relates to a target width. This system also triggers an event when a resize changes the current breakpoint.
  • Re-add distinct styling of invisible users on the online members list.
  • Add a dynamic menu builder system, similar to the system used with off canvas menus. This allows us to dynamically build menus out of existing HTML. Use this for "internal" action bar links on messages on smaller displays to prevent the action bar from wrapping. Similarly, use this in the ACP to move data list links to a menu on smaller displays.
And the rest:
  • Small reordering of the account links (in the account pages and the menu).
  • Ensure that the tweaked is-selected blockLink styling (with a background color) applies to blockLinkSplitToggle's as well.
  • Make the "show ignored content" link much less pronounced.
  • Drop the menu-blockLink CSS and use blockLink directly instead as they are generally identical and blockLinks have more variants (blockLinkSplitToggle).
  • Support a input--numberNarrow variant for places where we really don't need much space for numbers.
  • Add "concealed" links to the visitor menu and member tooltip counts to appropriate locations.
  • Ensure that trophies are hidden properly when the option is toggled, including automatically hiding the most trophy points member stat.
  • Form row CSS was changed to opt-in to the extra label alignment padding for input-based rows. Add a label aligner variant for when the content contains buttons.
  • Only handle share links in a special way if they are using http/https links. Fixes issues with mailto sharing.
  • Include <link> tags for RSS feeds in a few places.
  • Adjust the widget position tags to support both sidebar and sidenav positions.
  • Give button--primary a min-width and simplify many button wordings to things like "save" and "delete".
  • Allow some button wordings to be inferred by the icon specified in the template.
  • Move most delete buttons from the submit row to a page action (upper right of the page).
  • Dropped the gradient on flash message as it was pretty much the only place we used one like that.
  • Tweak the button hover/active state method to increase saturation and slightly darken (rather than lightening).
  • Add support for a hideempty attribute on checkbox, radio and select rows. When enabled, if there are no available choices, the entire row will be hidden.
  • inputGroups now use flex box when available to avoid some issues with the previous table-based implementation.
  • Adjust the drag target for multi-quote to use a dedicated drag gadget (always shown).
  • By default, disable caching for member stats based on values that are displayed in member list items.
  • Don't clear the code overlay in the RTE when it closes (unless you insert the code). This prevents code being lost accidentally.
As usual, there's always a few other changes that aren't mentioned here.

And with that, it's time to wish everyone a Merry Christmas and a Happy New Year! :D
What's new for developers in XenForo 2 (part 2) (December 30th) (@Mike)

A couple of weeks ago, we went into some detail about what's new for developers in XenForo 2. (You can read that here). We promised we would be back for more and so here it is:

In XF1 there are very few conventions and little structure in terms of the naming of add-ons, where the files are stored and what those files are named. We are changing that in XF2 in quite a significant way.

First of all, there is now a specific directory for all add-ons. Each add-on will have its own directory inside this parent add-ons directory and each directory will be identified by the add-on ID of the add-on. For example, files for XF Resource Manager and Media Gallery will be in the addons/XFRM and addons/XFMG directories respectively where XFRM and XFMG are the new add-on IDs for these add-ons.

On a similar note, a common pattern among existing add-ons is for an add-on vendor to put all of their add-ons within a parent directory indicated by the vendor's name. For example, if you release add-ons under the "Acme Add-ons" brand, you may currently store your add-ons files in the library/AcmeAddOns/AddOnName directory. Your add-on IDs may even be something like AcmeAddOns_AddOnName. In XF2, we support add-on IDs having a vendor prefix as part of the add-on ID, and the vendor prefix is separated by a /. So, if an add-on ID contains a slash, e.g. AcmeAddOns/AddOnName we know the files for the add-on will be stored in addons/AcmeAddOns/AddOnName. As we now use PHP namespaces in XF2, the rest of the add-on ID should be compliant with that format, so add-on IDs should contain only a-z, A-Z, 0-9 and / characters, must not start or end with a /, and must not start with 0-9.

This convention is certainly less flexible, by design, but having a predictable format for such things represents a significant improvement. For example, all add-ons will now have nearly identical install instructions, manually removing add-on files will be a clearer process and it allows us to make the process of installing add-ons different, too.

With that in mind, the new process for installing or upgrading an add-on is to simply upload the contents of the upload directory to the add-ons directory on your server. After you have done that, you no longer have to fish out a specific XML file to trigger the install or upgrade - we already know where the required files for an add-on are.

To demonstrate, this is a good point to show you what the new add-on list looks like:


The add-on list is split into a few separate lists of add-ons.

When an add-on directory contains a version of files newer than that of the version installed, we mark it as upgradeable and push it to the top of the page.

When an add-on exists on the file system but is not installed (not in the xf_addon table of the database), we pull out some information about the add-on and mark it as installable.

Finally, you will see a list of add-ons which are installed, though you will notice that one of those is disabled.

Of course in XF1 all of the data for an add-on is kept inside an XML file. In XF2, each add-on should provide an XML file (if applicable) for the various types of add-on data (phrases, templates, etc.) and this file is stored in the add-on directory and named data.xml. This file no longer stores information about the add-on title and version. Instead, every add-on should include a manually built addon.json file.

Here's a somewhat complete example from XFMG 2.0:

    "legacy_addon_id": "XenGallery",
    "title": "XenForo Media Gallery",
    "description": "The XenForo Media Gallery is an add-on that allows you and your users to create galleries of images and videos in your forum, organized into admin-defined categories or user-created albums.",
    "version_string": "2.0.0 Alpha",
    "version_id": 902000010,
    "dev": "XenForo Ltd.",
    "dev_url": "https://xenforo.com/",
    "faq_url": "https://xenforo.com/help/media-gallery/",
    "support_url": "https://xenforo.com/community/forums/media-gallery-support.87/",
    "extra_urls": {},
    "require": {
        "XF": [2000010, "XenForo 2.0.0+"],
        "php": ["5.4.0", "PHP 5.4.0+"]
    "icon": "icon.png"

For the most part, this should be fairly self-explanatory based on what you can see in the above screenshot, so we won't go through everything. Though there are a couple of interesting things here.
  • We mentioned earlier that the add-on IDs for XFMG and XFRM have changed, and this add-on ID change is handled automatically during upgrade based on the legacy_addon_id key in the JSON file. If that add-on ID exists, it will be marked as upgradeable by this add-on.
  • extra_urls is an array of link text: url pairs and these will render below the add-on list item (e.g. after the developer, FAQ and support links) and allow you to display links to other relevant things related to the add-on (perhaps a bug reports link, a manual or whatever you like).
  • The require array is a standard way of blocking an add-on install or upgrade if the environment doesn't support the required dependencies. In this particular JSON file, we block install and upgrade if the current XF version ID is below 2000010 and we block if the current PHP version is below 5.4.0. It's also possible to require other add-ons to be installed first, and also block install if certain PHP extensions are missing or not enabled.
  • The icon path is relative to the add-on directory. In this example, our XF logo is picked up from addons/XFMG/icon.png.
Another thing missing from both the XML file and the JSON file is information about how to install/upgrade the add-on. In XF1 we specify a class and method for installing and uninstalling an add-on. In a perhaps somewhat predictable move, we have also made this somewhat more predictable in XF2 :) If you have any install/upgrade/uninstall operations that need to happen, you will add these to a file named Setup.php. There is a BaseSetup class that you can extend here which can, amongst other things, loop through install steps and upgrade versions automatically.

public function installStep1()
    // Do stuff here

Development output
Although somewhat still related to add-ons, this is significant enough to deserve its own section.

If you enable development mode in config.php, this will cause add-on related data to be output as files (usually JSON) to the file system. Files will be written out to a location in the format addons/<addon_id>/_output/<type>/<id>.json.

For example, this is the XFMG navigation tab entry, stored in addons/XFMG/_output/navigation/xfmg.json:

    "parent_navigation_id": "",
    "display_order": 30,
    "navigation_type_id": "basic",
    "type_config": {
        "link": "{{ link('media') }}",
        "icon": "",
        "display_condition": "$xf.visitor.canViewMedia()",
        "extra_attributes": []
    "enabled": true

Outputting all associated development data in this way is perfect if you manage your projects using a version control system. It also provides a way to easily import new/updated development data when working across separate databases, without having to do a full add-on install/upgrade.
As well as outputting the files themselves, we also track the metadata for all file types which at the minimum will include a hash which represents the current content of each file but can also include information such as the version ID and string of the add-on when it was last changed.
Not all development output files are output in JSON format. For example, Phrases are output as .txt files but, perhaps more significantly, template files are output in .html, .less and .css format...

Template file watcher
As mentioned above, templates are written out to the filesystem using the development output system. The location for these files will be addons/<addon_id>/_output/<admin|email|public>/. As each template is rendered, we watch the file system to see if there are any changes by comparing the current hash of the content to the hash stored in the development output metadata. At this point, the template will be updated, imported or recompiled.

This essentially means that the ability to edit templates from the filesystem is available for add-on developers which is far more convenient than editing templates in the Admin CP.

Class extensions
In XF1, to extend certain classes, you have to go through the following process:
  • Create a listener class, and a method similar to:
    public static function extendThreadController($class, array &$extend)
        $extend[] = 'My_Extended_Thread_Controller';
  • In the Admin CP create a new code event listener, pick an event, e.g. load_class or load_class_controller and point it at your class and method and provide an event hint.
This doesn't particularly seem too onerous, but with a lot of classes being extended it can ultimately represent a lot of boilerplate code - and where's the fun in that? :)

So, in XF2, we have simplified this into a system in the Admin CP we call "Class extensions". Let's take a look:


All you need to do then is create your extended class, and that's it! It is, however, important to bear in mind that wherever possible a code event listener should still be used to listen to more specific events if there is one available.

The app object, dependency injection and service location
XF2's application config and setup actions are now configured through a general app object. (This is similar to XF1's dependencies objects, though the app object is more encompassing.)

The most significant component of this object is the dependency injection container that it exposes (and the helper methods it provides to work with it). Without getting into too much detail, the main advantage of this container is that it wraps up the dependencies for creating various objects within XenForo (or loading specific types of data) so you don't need to worry about that; you'll always get the correctly setup object. (If you're not familiar with dependency injection or containers, have a look at the first 2 parts of this blog: http://fabien.potencier.org/what-is-dependency-injection.html)

As an example, if you request the router for generating/analyzing public routes, the container will load the actual list of routes, setup the pre-processors, add the route filters (which wouldn't apply to the admin router) and apply some of the global link-building configuration such as URL romanization. This object is only created on demand, so you don't have the overhead of doing this until you need the object. To emphasize the point, even if we had no expectation that you wanted a particular type of object somewhere, you should be able to instantiate it quickly and correctly through the dependency injection container.

The specific app object that is instantiated varies based on the location being loaded; for example, public and admin pages have separate app objects which lead to different configurations and dependencies being setup. As an example, this changes the session configuration for each app so that admin sessions come from the DB while public sessions may come from a cache layer.

Generally speaking, one request will have one app object. While in an ideal world, you would be given the specific object/data that your code depends on. However, that's not always viable, particularly in add-ons. There are certain types of classes where we pass the app object in (such as services), but in a pinch, you can always access it via \XF::app() and then pull the specific elements out that you need. This approach would fit more of a service locator pattern.

In most cases, container entries are represented as closures that are cached when initialized. This isn't always the case they; the container can store raw data or uncached closures. It also provides factory-style instantiation tools.

If this sounds complex, it's mostly because we're staying very abstract in the explanation. In most cases, your interaction with the app object may be something like $app->db() to get the DB adapter out. You won't need to worry about what happens behind the scenes. However, if you are interested in manipulating the container, add-ons will be able to extend entries and change how they are instantiated. (This can even be done via config.php.)

Improved JS Framework
XF2 itself still uses jQuery as its primary JS framework but has removed its dependency on the aging and unsupported jQuery Tools. We have built our own framework around jQuery which makes instantiation and extension of JS easier.

We have two types of handler; a click handler and an element handler. Each handler type is an object which has similar behaviors and similar functions within that should be extended. Click handlers execute code once the element is clicked, and element handlers execute once the element is initialized. Let's take a look at a basic click handler:

XF.LinkClick = XF.Click.newHandler({
    eventNameSpace: 'XFMyLinkClick',

    options: {
        alertText: 'Link has been clicked!'

    init: function()
        alert('Initialization. This fires on the first click.');

    click: function(e)
        alert(this.options.alertText); // Alerts: 'Link has been clicked!'

XF.Click.register('mylink', 'XF.LinkClick');

To set up an element to call the above code when clicked, you simply add a data-xf-click attribute with a value of mylink (the identifier, the first argument of the register line).

<a href="javascript:" data-xf-click="mylink">Click here!</a>

Some interesting things to note:
  • We specify a unique event namespace which means that a number of events will be fired when the link is clicked. Specifically, they are before the handler is initialized, after the handler is initialized, before the click code executes and after the click code executes.
  • We have an options object. Options are passed to the handler automatically and allow an element to adjust its configuration based on data-X attributes passed in. For example, when the above link is clicked, the alert "Link has been clicked!" will display. If we wanted this to alert something different, we can do that by adjusting the HTML as follows:
<a href="javascript:" data-xf-click="mylink" data-alert-text="Something different!">Click here!</a>

Element handlers work in a nearly identical way, but the init function for element handlers executes when the element is activated (usually on page load for the entire document, or on specific elements on demand).
It is also trivially possible to extend an existing handler. Let's take a look at a very basic element handler which when assigned to an element will change its text color to red, and underlined:

XF.RedText = XF.Element.newHandler({
    options: {
        color: 'red',
        decoration: 'underline'

    init: function()

    changeColor: function()
            color: this.options.color

    underline: function()
            'text-decoration': this.options.decoration

    alert: function()
         alert('The text changed to red and underline...');

XF.Element.register('red-text', 'XF.RedText');

<div data-xf-init="red-text">This is black text.</div>

When this element initializes, the "This is black text" text will turn red and become underlined and an alert 'The text changed to red and underline...' will be displayed on the screen.

We might want to re-use or extend this code for a different element handler, and this is possible using XF.extend. This allows you to extend an existing handler with your own code. You can then change default options, or even override or extend entire functions with the added option of still calling the original function if desired. Let's see that in action:

XF.BlueText = XF.extend(XF.RedText, {
    __backup: {
        'alert': '_alert'

    options: $.extend({}, XF.RedText.prototype.options, {
        color: 'blue',
        decoration: 'line-through'

    alert: function()
        alert('...actually, it changed to blue and strike through!');

XF.Element.register('blue-text', 'XF.BlueText');

<div data-xf-init="blue-text">This is black text.</div>

What we have done here is we have registered a new element handler called blue-text but the bulk of the behavior is coming from the red-text handler.

Notice that the default options of the red-text handler are overridden so that the text is going to be blue and line-through rather than red and underline.

We've also used a __backup object to map the alert function in the original handler to a new name of _alert in the new handler. This is equivalent to extending a class and method in PHP and calling the parent:: method.

The end result of this blue-text handler is that the color of the text is changed to blue and underline (based on our extended options), the alert text from the original handler is displayed ('The text changed to red and underline...'), followed by the new alert text we've specified in the new handler ('...actually, it changed to blue and strike through!').

While this demonstrates a clear hierarchy style, we have also exposed a more dynamic method of extension. This would likely fall into the realm of "monkey patching".

XF.Element.extend('red-text', {
    alert: function()
         alert('Hey a different message');

This dynamically extends the XF.RedText instance in place and overrides the functionality of the alert function. All code that is using the red-text element will automatically use the extended version. Further, multiple extensions can happen to the same element handler without causing name conflicts; in this regard, it's similar to the class extension system for PHP.

Additionally, this XF.Element.extend method can be called before or after the base element is registered. This is relevant when JS is loaded asynchronously. (Clearly, it needs to be called before the element is instantiated, but the JS framework won't do that until all pending asynchronous JS has loaded.)

Abstracted file system
Instead of writing to semi-hard coded paths, XF2's main file writes now write through an abstracted system. This makes it much simpler to write files out to locations other than a local disk. Commonly, this would involve writing the files to a cloud storage system such as AWS S3, but there are various other potential approaches.

Reading and writing is done through the file system object grabbed from the app ($app->fs()). Here's what a path might look like:
This would identify the internal-data as the "mount" and the rest would represent any subsequent path and file name. The specific adapter would represent these as is appropriate for their usage.

The available mounts are:
  • internal-data -- for data that is only accessible via PHP. By default, this is still the local internal_data directory.
  • data -- data that is accessible via PHP and directly via a browser.
  • code-cache -- this is specifically for generated PHP code that we then write out to files. By default this is within the internal_data directory. This is distinguished from regular internal data files because we will be including/requiring files via the specified path so that we can take advantage of opcode caching. You want accessing these to be fast.
  • temp -- just for temporary files. This always resolves to a local temporary directory. This isn't used commonly but it can help transition from a potentially remote file to a file that is known to be locally accessible (as some processes require local files).
Phrase grouping
When we dynamically load phrases in XF1, if the phrase is not globally cached there can be a query overhead in terms of retrieving that phrase. Although globally caching is an option, it's often unnecessary to have something cached globally as it may only be used on certain pages.

Our solution for this in XF2 is phrase groups.

A phrase group is indicated by a special prefix applied to a phrase name in the format of <group>.<phrase>. All phrases that belong to the same group are compiled and written out to a file on the file system for each language and loaded from there instead when needed.

Generic change logging system
In XF 1.3 we introduced a user change logging system which causes all changes made by any user to any other user (or themselves) to be logged. The system itself is still present in XF2 and hasn't changed much on the surface, but behind the scenes, it is now handled by an entirely generic system which developers can extend for logging other changes to other content types besides users.

The change logging system is very simple to implement for your content types, too. You just extend our AbstractHandler and configure the Entity/Entities to use the ChangeLoggable behavior and the bulk of the logging and formatting of the logs happen automatically.

You can either configure an entire Entity to be "change-loggable", which means all fields by default will have changes logged, or you can opt into the logging on specific fields you explicitly want to log.

CLI Framework
This feature doesn't actually exclusively benefit developers, it also benefits site admins too, especially those with larger sites.

XF2 includes a framework which allows CLI commands to be defined and allow you to run those commands from a shell/command line. From a non-development point of view, it means admins of larger sites can perform intensive tasks such as site upgrades without having to worry about various timeouts that can be problematic when running such things through the web interface.

Upgrading the site using the CLI is not actually something new and it is something we actively recommend when we detect an upgrade of a larger site, but the framework behind it is generic, so it opens the door eventually for more tasks to be run in that way. As an example, however, here's what you would type from a shell on your server to upgrade XF to a new version:

C:\xf> php cmd.php xf:upgrade
Current version: 1051170
Upgrade target: 2000010 (2.0.0 Alpha)
Are you sure you want to continue with the upgrade? [y/n]

In addition to upgrading, you can also install XF2 from scratch, and upgrade your tables to support utf8mb4.

In terms of developers, there are some development commands which you may find useful. Here's a few of them:

  • xf-dev:addon-create This will take the initial steps necessary to create an add-on, and write out the initial addon.json file. The command asks various questions such as add-on ID and title and uses the responses to build and write out the file.
  • xf-dev:addon-export <addon_id> Exports all of the add-on related data (phrases, templates, navigation etc.) to the [/ICODE]data.xml[/ICODE] file for the specified addon ID file.
  • xf-dev:import --addon <addon_id> Imports all of the add-on related data (phrases, templates, navigation etc.) from the development output files on the file system to the database for the specified add-on.
  • xf-dev:export --addon <addon_id> Exports all of the add-on related data (phrases, templates, navigation etc.) from the database out to files on the file system for the specified add-on.
In addition, it will be possible for developers to create their own commands for their own personal use or for distribution with add-ons.

Master/slave support in DB adapter
XF2 introduces master/slave replication via a new DB adapter which allows separate read and write connections to be made. By default, select statements will be sent to the read server and all other statements will be sent to the write server.

You can see a typical example of configuration below:

$config['db']['adapterClass'] = 'XF\Db\Mysqli\ReplicationAdapter';
$config['db']['write'] = [
    'host' => '',
    // ... username, password, dbname etc.
$config['db']['read'] = [
    'host' => '',
    // ... username, password, dbname etc.

As well as inferring the correct connection to make from the type of query, you can also control the behavior by prefixing a query with a comment in the form of -- XFDB=modifier.

Where the modifier is one of:
  • fromWrite - forces a specific read query to come from the write server even if it normally wouldn't
  • forceAllWrite - forces this query and all subsequent queries to the write server
  • noForceAllWrite - if this query would normally force all subsequent queries to the write server, this option disables that (useful for a write you know won't be read back immediately or if it is, can tolerate lag)
General improvements
While we have called out a lot of new things in XF2, there are plenty of systems that have also changed under the hood but generally just represent improvements. These include:
  • General reduction of static method usage. We now operate on object instances (where possible) except for some basic utility functions. This allows easier customization.
  • Rebuilt permission compilation code which allows significantly more reuse for new content types that require permissions (such as resource or media gallery categories).
  • Rebuilt BB code parsing system. This now separates rules defining the available BB codes, the parser, and the rendering mechanisms more cleanly. Significantly, BB code "processing" systems -- such as auto-linking, adding mentions, or limiting the allowed tags -- have now been split out separate analyzer and filterer systems, independent from the core rendering framework.
  • Improved generic systems for prefixes and custom fields so that add-ons do not need to repeat as much boilerplate code to implement the basic functionality.
  • A generalized connected accounts system to make it easier to support logging in/account creation through external services. We support Facebook, Google, Twitter, Microsoft, GitHub and LinkedIn out of the box, but we now include a powerful OAuth client library that includes Amazon, Dropbox, Instagram, Reddit, Spotify and others so add-ons should be able to add more connected account providers easily.
I think that sums of the most significant changes to the core of the XF2 code. Inevitably, there will be thousands of other changes that we haven't covered here and those will become more apparent once you start working with the code. We're still working very hard and marching towards the developer preview release.

And with that, we wish everyone a very happy new year!
Development Update (February 10th) (@Mike)

It's been a little while since we've done one of these development updates. It may have seemed like there weren't many changes happening on the site, but there have been very significant changes happening behind the scenes.

Here are some of the things we've been changing since the last update:

  • The find new system has been changed to allow users to save their preferred filters. The filters used in a particular search can now be seen immediately and easily removed.
  • A short caching period has been added for find new requests. This will be most significant for guests as this will limit the number of records that will be inserted, even with a large amount of traffic.
  • These changes have been applied to the "new posts" page within the forums section and an explicit option has been added to allow this to be the default page of the forums section.
  • A global "Post thread" button has been added to the forum overview pages (forum list, new posts).
  • Refactor the add-on data export system to use a file per data type. This reduces overhead during installation as only specific types of XML needed to be loaded.
  • Refactor the add-on data import system to do the necessary rebuilds as it goes along. Significantly, this means that only data being created or deleted needs to be rebuild, which should significantly increase installation/upgrade speed for most add-ons and particularly for smaller ones.
  • Refactor the base add-on installer components to natively support multiple steps when installing, upgrading, or uninstalling an add-on. Developers would generally only need to define the steps with particular method names.
  • Implement a new approach to file hash checking. Our add-on package building tools also automatically calculate hashes. These hashes will be verified before add-on installation/upgrade and will either block installation or warn of the error.
  • Beyond this, the file health check can now be run automatically via a cron. If an error occurs, this will email the appropriate person.
  • Added a command for building add-on release packages. This includes pulling additional files in if they're outside the add-on root, exporting the add-on data, building the file hashes, and packaging the zip file in the correct format.
  • Ensure that our new HTTP client still supports redirects but securely (as was re-added in 1.5.11).
  • Significant improvements to our tools that help automatically phrase templates (and files) and running them against XF. All text should now be phrased. We have also built a tool to identify (likely) unused phrases and have removed them.
  • Many phrases have been adjusted to use sentence case. Adjust various wordings to be more consistent, particularly in areas of the control panel.
  • Adjust exactly how admin navigation works and begin to setup the ACP entry page as more of a dashboard.
  • Significant refactor to the registration code to make it easier to extend and to share more code between standard registrations and connected account registrations.
  • Added a "New threads" widget which is ordered by thread creation date rather than last post date. This also offers an "expanded" view which displays the first post content, similar to what you might see in a portal.
  • Add support for forum filters in the new threads and new posts widgets.
  • Refactor of the ACP stats display system to generally be easier to work with and be accessible through a single "stats" entry point. Raw data points for the displayed stats are now exposed via the interface. When displaying grouped stats (weekly/monthly), the values will now be shown as daily averages within the time period, which prevents certain stats from being misleading and makes more comparable stats over incomplete periods.
  • Completed the core of the style properties system. Beyond the standard elements, properties can depend on another boolean property in their group. If the boolean property is disabled, the dependent properties will either be disabled or hidden. Template-based style properties (for add-ons) can be used to support non-scalar value style properties.
  • Significant refactor to the base color palette. The number of primary color variations has dropped to 5 and the number of accent/secondary color variations has dropped to 3. Situations where minor variations are needed can be generated programmatically.
  • There is now a style property that defines whether the style is light- or dark-based. This will be used with our xf-intensify and xf-diminish LESS functions to programmatically adjust colors. The functions will darken/lighten on light styles and the inverse on dark styles.
  • Page nodes support an "advanced mode" flag which currently controls whether the output will automatically be wrapped in a block. (The meaning of this option may be expanded in the future.)
  • A number of code events were added into places we think may be common extension points.
  • The sticky page header and sticky form footer are now disabled if the window is too short.
  • A new <xf:button> tag has been added that simplifies the code to output <button> or <a> tags that look like buttons.
  • The main members section pages have changed to use a sidenav to display the various member stat options and the registered members list, rather than a tab header.
  • Adjusted styling of <select> elements to allow them to be styled consistently across devices. (This was very noticeable on Macs.)
  • Various fixes for issues relating to new installations.
  • Originally missed from this list: a new advertising position system. While the new system ultimately uses templates, you won't have to wade through templates to add advertisements. We have defined a list of advertising positions (that can be associated with add-ons) which you can assign ads to. By default, we provide options to control only displaying ads to users in certain groups and/or not displaying ads to users in certain groups. You can still use template syntax within the ad for more complex conditions.

As always, there are a large number of small fixes and tweaks that we haven't covered here.

While we still have a large number of things on our todo list, we're getting very close to the release of the first development preview. Keep an eye out for that soon!
Not open for further replies.
Top Bottom