How to Add More Actions to an Existing Controller

Kier

XenForo developer
Staff member
Some add-ons warrant having their own controllers, but others provide little snippets of functionality that don't really need to have their own route prefix and controller. For these, it can be useful to add their actions to an existing controller.

For example, you might want to have a new member action that simply displays a member's username and user title, and have it respond to requests for /members/{username}.{user_id}/title. The XenForo code event listener system makes this easy to do, without having to go and edit the existing controller code. Let's see how we can go about adding an additional action to the XenForo_ControllerPublic_Member class to achieve our goal.

We will start by creating our extended controller class. This will act as though it has extended the member controller, inheriting all of XenForo_ControllerPublic_Member's methods and properties. Normally, a class like this would be declared as follows:
PHP:
class Dev_ControllerPublic_Member extends XenForo_ControllerPublic_Member
But for a dynamic system where there might be dozens of classes attempting to extend the member controller, this is unworkable.

Instead, we must make use of the XenForo Class Proxy system, which allows the system to effectively have multiple inheritance capabilities. To use it, we must declare the class as ClassName, and have it extend XFCP_ClassName, like this:
PHP:
class Dev_ControllerPublic_Member extends XFCP_Dev_ControllerPublic_Member
Don't worry that the declaration does not mention XenForo_ControllerPublic_Member, we'll handle that later.

We now need to define our action and our extended controller is done.
PHP:
<?php

class Dev_ControllerPublic_Member extends XFCP_Dev_ControllerPublic_Member
{
	public function actionTitle()
	{
		$userId = $this->_input->filterSingle('user_id', XenForo_Input::UINT);
		$user = $this->getHelper('UserProfile')->assertUserProfileValidAndViewable($userId);

		return $this->responseView(
			'Dev_ViewPublic_Member_Title', 'dev_member_title',
			array('user' => $user)
		);
	}
}
Next, we need to tell XenForo that you intend for Dev_ControllerPublic_Member to extend XenForo_ControllerPublic_Member. In the Development tab of the XenForo Admin Control Panel, create a new Code Event Listener. Tell it to listen to load_class_controller and give it the name of a class and method that you will create next in order to do specify the extension. When you're done, save the event listener.

Screen shot 2011-01-27 at 13.57.12.webp

Finally, we need to create Dev_Listener_LoadClassController::extendMemberController() as specified in the code event listener. This will be a very simple class with a single method that simply sets an entry in an array if the class name passed to it matches the one we want.
PHP:
<?php

class Dev_Listener_LoadClassController
{
	/**
	 * Instruct the system that XenForo_ControllerPublic_Member
	 * should be extended by Dev_ControllerPublic_Member
	 *
	 * @param string $class
	 * @param array $extend
	 */
	public static function extendMemberController($class, array &$extend)
	{
		if ($class == 'XenForo_ControllerPublic_Member')
		{
			$extend[] = 'Dev_ControllerPublic_Member';
		}
	}
}
With that class and method created, we can now request /title as an action from the members route prefix and have it perform our actionTitle() controller action. Hooray!

Screen shot 2011-01-27 at 14.11.44.webp

It is worth noting that the principles described here can apply to many of XenForo's important classes. You can extend Controller, DataWriter, Model, View, RoutePrefix and several other types of class using the very same methods as were used to add more methods to the controller in this tutorial.
 
Quick Note:

This tutorial doesn't work:p

You need to create as FIRST the file and then you can create the event listener in the acp.

If the file doesn't exist, xenforo wont save the event listener
Please enter a valid callback method.
 
Some add-ons warrant having their own controllers, but others provide little snippets of functionality that don't really need to have their own route prefix and controller. For these, it can be useful to add their actions to an existing controller.

If I was trying to add some other functionality to the uploader, is this thread a good place to start?
 
I am trying to make my addon use the xf uploader instead of my standalone uploader.php.

I need to use 4 file types not accepted by default and each one upon finishing uploading should display text that can be copied or simply be clicked to insert into the editor like an image upload would be.
 
It sounds like you're going to need a lot more than just an extended controller to be honest.
 
Hi Kier.

Can you demonstrate a bit how to add some extra condition checker to use in the template? For example, when looping $threads, how can I have an extra <xen:if is="{$thread.myCondition}"> ?
 
Hi Kier.

Can you demonstrate a bit how to add some extra condition checker to use in the template? For example, when looping $threads, how can I have an extra <xen:if is="{$thread.myCondition}"> ?
I'm not sure I fully understand the question, but I will try to answer - you may check any available data using the standard <xen:if ... > tag. If you need to check other data, you need to add that data to the view parameters in an extended controller.
 
It sounds like you're going to need a lot more than just an extended controller to be honest.

I figured as much but I have no clue where to start. :)

If you we're to look at my uploader.php that does the complete of what I need it to for me to consider my addon done for now, could you sum up what I would need to do generally or tell me if it is something that is way beyond my scope of knowledge at this point?

I am calling the uploader into the sidebar wrapped in an iframe and I don't know really that is the best way to do it..if there is nothing wrong with doing that for now (I am asking here) I suppose I could just use the same method I was using before unless there is something utterly wrong with doing it that way.
 
I figured as much but I have no clue where to start. :)

If you we're to look at my uploader.php that does the complete of what I need it to for me to consider my addon done for now, could you sum up what I would need to do generally or tell me if it is something that is way beyond my scope of knowledge at this point?

I am calling the uploader into the sidebar wrapped in an iframe and I don't know really that is the best way to do it..if there is nothing wrong with doing that for now (I am asking here) I suppose I could just use the same method I was using before unless there is something utterly wrong with doing it that way.
I can't really comment on whether anything is beyond your ability to achieve, but I will say that extending the XenForo uploader is not for the faint of heart. If your current uploader is working, I'd say you've done well and there's no real imperative to change it. :)
 
Well that is a relief to hear let me tell you (not editing the uploader part but that if mine works for it's purposes to use it)...I mean I don't run away from things much but I will take your word on it. Besides, I can always upgrade the mod later once I figure out what I am doing. (And now I can update Xenforo and not have to redo any template edits :D, xen hooks are my new best friend.)

Well then...now I am going to tie in my uploader and call it an addon for now.

Thanks!

:D
 
Hi Kier. I mean I want to add extra data in addition to the already available ones.
You will need to fetch that extra data with an extended controller, unless the data simply appears in the thread table, in which case it may be fetched automatically by the existing code (due to SELECT *)
 
I can't seem to get this working in 1.1, I just get a generic template page when requesting xxxxxx/title - it's doesn't even got the username in the breadcrumb. Has anyone else got this working or have it available as a plugin? I've created the Dev/ControllerPublic/Member.php and Dev/Listener/LoadClassController.php files according to the instructions here and added the listener.
 
This is probably obvious to everyone else, but as a beginner, it wasn't clear to me how or where the above code defined "/title" as a new URL path in the software. After some inspection and testing, apparently it's pulled from the method name, actionTitle().

I was adding an action to another ControllerPublic and found it to function the same way, although it's still not clear to me exactly how this system works.
 
Top Bottom