1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Creating an Addon.

Discussion in 'Official Development Tutorials and Resources' started by Lawrence, Oct 7, 2010.

  1. Lawrence

    Lawrence Well-Known Member

    Part 1

    This walk-through will take you step-by-step through the process of creating a working XenForo Add-on.

    The Add-on created will allow Admins to prevent the editing of signatures for members with less than a specified amount of posts, and who belong to a particular group. To accomplish this we will need to set up a Listener, an Admin Options Page, and create the PHP files.

    I decided to not use screen shots in this walk-through as I want to give the feel that you already know XenForo's Model View Controller (MVC) concept and are about to write your Add-on. Each step will be explained in detailed.

    The working Add-on will be available at the end of this walk-through for coders to reference, or for licensed members to use.

    If you plan on building the Add-on step by step you will need to enable Debug Mode. It is recommended that you first make a back-up of your config.php file before editing it. To enable debug mode:

    open: yourForo -> library -> config.php

    And add at the bottom: $config['debug'] = true;

    At this time you may also want to add:

    // $config['enableListeners'] = false;

    Save your config.php file.

    Creating the Add-on

    Log into your AdminCP, and navigate to: Development -> Create Add-on

    The following fields for this Add-on need to be filled with the following information:

    Add-on ID: limitSigs
    Title: Limit Signatures for new Members
    Version String: 1.0
    Version ID: 1

    The Version ID is used internally to keep track of revisions. Increase this number for each revision.

    The rest of the fields we do not need for this Add-on.

    Click the Save Add-on button.

    As the purpose of our Add-on is to not allow new members to edit (create) a signature, we must locate where members can edit their signature within XenForo, so we can Listen for the class being called. This is handled by a Controller. There are four Controller directories: Admin, Helper, Public, and Response. Editing a signature is not an Admin function. Nor is it a Helper, or a Response, that leaves us with Public. From your filemanager navigate to ControllerPublic. As editing a signature is part of a members account look for Account.php. Open this file and search for signature. The first word highlighted is just above: public function actionSignature(). This is the function we want to override.

    Now we must listen for the class which this function belongs to be called (XenForo_ControllerPublic_Account).

    XenForo comes with quite a few Listeners that we can choose from. Navigate to: Development -> Code Event Listeners. Click on the Create New Code Event Listener.

    The first drop down allows you to select which event to listen to. As we want to listen to a class from the controller being loaded, from the drop down, select Load_Class_Controller.

    The load_class_controller listener is tricky, but it is powerful and provides the most flexibility of all the Listeners. When you select this listener you will notice in the explanation of the load_class_controller that the class must be defined as: class My_Class_Name extends XFCP_My_Class_Name. This is true, but it can not be loaded directly. Loading this class directly will result in the AutoLoader to generate this exception: Cannot load class using XFCP. Load the class using the correct loader first.

    What the error means is that the new class you want to create must be loaded before it can be resolved. But the question is, where does it get loaded, and how? This is the purpose of the $extend array argument.
  2. Lawrence

    Lawrence Well-Known Member

    Part 2

    Leave the Listener page open, and using your file manager browse to: yourForo/library/

    We will now set up our Add-on's directory structure.

    Create a new directory called LimitSig

    Notice the L and S in the directory name is upper case. We use this format to maintain the same standard as XenForo's directory names.

    Create a new directory in LimitSig called Listener. Here is where we will store our listener. Within Listener, create a new file called LoadClassController.php. The directory and file name structure we are using will make it easy to locate files to be edited in the future, and it follows the same format as XenForo.

    In your LoadClassController.php file add:

    class LimitSig_Listener_LoadClassController
            public static function loadClassListener($class, &$extend)
                    if ($class == 'XenForo_ControllerPublic_Account')
                            $extend[] = 'LimitSig_ControllerPublic_LimitSig';
    Save LoadClassController.php

    If you notice the class reflects the directory structure. This is very important. Our function contains two arguments: $class and $extend. $class will contain the class name that we will test for. $extend[] is an array that we will add to if $class contains XenForo_ControllerPublic_Account. Using this array allows other Add-ons to use this Listener without interference.

    Now we can add our in our Class and Method in the Execute Callback text boxes of our Listener we are creating in the AdminCp -> Development section.

    For Class type in: LimitSig_Listener_LoadClassController

    The Method is our function. Type in: loadClassListener

    Leave the Callback Execution Order defaulted. This value will only need to be changed if another load_class_controller event must happen before this one is executed.

    Ensure Enable Callback Execution is checked.

    In Description type in: Listens for XenForo_ControllerPublic_Account class

    Select Limit Signatures for new Members for the Add-on drop down and click save. If you followed the structure above the Listener will be successfully saved.

    The class in our LoadClassController.php file will now listen for XenForo_ControllerPublic_Account. When that class is loaded, $extend[] will be populated with the name of LimitSig_ControllerPublic_LimitSig. We now need to create that file and class. By looking at that class name, you can tell what directory it will be in, and the name of the php file that we need to create:

    LimitSig -> ControllerPublic -> LimitSig.php

    Create the ControllerPublic directory, and then LimitSig.php. In the LimitSig file add:

    class LimitSig_ControllerPublic_LimitSig extends XFCP_LimitSig_ControllerPublic_LimitSig
        public function actionSignature()
    You should notice the use of the XFCP_ prefix that was referenced in the listener description. As we can not extend a non existing class, loading it into $extend[] validates our class so it can now be extended.

    Notice the function actionSignature(). That is the function in Account.php that we want to over ride. Within this function is where we will be adding our code to check for post count.

    We can leave this function blank until we create a new options group in the AdminCP. If you are following along with each part, you can turn off this Add-on until we add in the code:

    AdminCP home, and uncheck the Add-on Limit Signatures for new Members.
  3. Lawrence

    Lawrence Well-Known Member

    Part 3

    To add options to the Limit Signature Add-on, navigate to AdminCP Home -> Options.

    At the top of the page, click: + Add Option Group

    Group ID (the options name that contains the values Admins set): limitsigOptions.

    Title (the name that appears in the Options list): Signature Options

    Description (a brief description that shows when you click the Option Group): Leave blank, unless you feel a more detailed description is needed.

    Display Order: default

    Add-on: Choose Limit Signatures for new Members from the drop down.

    Click Save Option Group.

    Now that you have your new group created, it's time to add a couple of options. The first option will allow Admins to set the minimum amount of posts a new member needs to make to be able to have a signature. And the second is to apply this minimum amount to a specific group, or groups. Adding the group option will allow us to select which groups should have the message count check applied to it, and which would be exempted, for example: a new Sponsor or Contributor with a message count less than that specified will still have the permission to create a signature.

    Click: Add Option

    The Add Option page has numerous text boxes, drop-downs, and check boxes that you can use to create an option. The first option is Option ID. This is the ID of the option we will be testing in our LimitSig.php file. Give it a name that will easily associate it with this Add-on, and what the option will contain. For this Add-on I chose: sigmessageCount. Take note of the case.

    Add-on (Select from the drop-down): Limit Signatures for New Members

    Title (the name of the option): Minimum Message Count

    Explaination (a brief description of this option): Members minimum message count required to create a signature. Enter a value between 0 and 50. A setting of O turns the Minimum Message Count check off.

    Edit Format. From the drop-down, select what type your option will be: a text box, spin box, On/Off Check Box, Radio Buttons, Select Menu, Check Boxes, Named Template, or PHP Callback. As we are looking for a number that we want to limit, select: Spin Box.

    Format Parameters. You can set limits and behaviors to certain types of options. As we are using a spin box we can add to this option a min value, max value and a step value. Any value below the min, will default to the min value, and any value inputted above our max value will default to the max value. Just place each one you want to define on it's own line. The step value sets how much pressing the + or - will increase the value in the text box. Remove the step=x line if you want a step of 1.

    Data Type: The type of data you want set. Select Unsigned Integer from the drop-down. We do not want our data to be a positive integer as any value below 1 will default to 1, and we want to be able to use 0.

    Default Value. This is the defaulted value set for an option when an Add-on is imported. As 5 is a good number for limiting users with less than x posts, we'll set the default value to 5.

    Array Sub Options. As we are not storing our value in an array, we will leave this empty.

    Validation Callback. Leave blank. This field will be used if you need to validate an Administrators selection. For example, if a forum an Admin selected is indeed a Forum and not a Category or a Page.

    Display in Groups. This is a list of all Option Groups you want this option to appear. The default is the Add-on selected in the drop-down. Leave as set.

    Include this Option in Backups. Default checked. Checked.

    Press the Save Option button. You will now have your first working option in our Signature Options Group, that you can set and save.

    Once again, Select Add Option.

    Option ID: siglimitGroups

    Add-on: Limit Signatures for new Members

    Title: Apply to Groups

    Explanation: Select which groups require a minimum message count to create a signature. Those members that are Administrators and Moderators that belong to one of the selected groups will be excluded.

    Edit Format: PHP Callback. We are using PHP Callback as we want to generate a list of groups (with the exception of Unregistered, Administrative, and Moderating), that include those created by an Administrator.

    Format Parameters. As we selected PHP Callback, we need to point the Option to where the Class and Method used is: LimitSig_Option_Group::renderOption

    Data Type: Array. As Admins can select more than one group, an array will be required

    Default Value: a:1:{i:0;s:1:"2";} The default value is set to Registered groups. How to change this value I'll explain when we have this option working as intended.

    Array Sub-Options: * (we use an asterisk here, as we do not need to store information in specific named keys of an array, eg: width, height, user_id, etc.)

    Validation Callback: default. As we are controlling what groups an Administrator can check, we know they will be valid.

    Display in Groups: Signature Options.

    Include this option in back-ups: Defaulted (checked)

    Before we select Save, we need to create our PHP Callback Class and Method to render a list of groups for Admins to choose from.
  4. Lawrence

    Lawrence Well-Known Member

    Part 4

    The Callback we will call to render the checkboxes for our option is called LimitSig_Option_Group. By looking at the name you should now be able to tell where the callback will be located and the name of the file itself. From your file manager, navigate to: Library/LimitSig. Create a new folder named Option, and within the Option folder create a new file: Group.php

    Edit the file to add the Class and function:

        class LimitSig_Option_Group
            public static function renderOption(XenForo_View $view, $fieldPrefix, array $preparedOption, $canEdit)
                $preparedOption['formatParams'] = XenForo_Model::create('LimitSig_Model_GetUserGroups')->getUserGroupOptions(
                return XenForo_ViewAdmin_Helper_Option::renderOptionTemplateInternal(
                $view, $fieldPrefix, $preparedOption, $canEdit
    Select save.

    This Class passes on the list of groups generated by our LimitSig_Model_GetUserGroups class to be rendered into checkboxes by the Admin Helper class. To view all available rendering options, take a moment to examine the Admin Helper Option file.

    We now must create a new file for our LimitSig_Model. Create the folder Model/ within Library/LimitSig/ directory. Navigate in the Model folder and create GetUserGroups.php. Edit this file to add:

        class LimitSig_Model_GetUserGroups extends XenForo_Model
        // returns a list user groups with the exception of Administrative, Moderating, and Unregistered
            public function getUserGroupOptions($selectedGroupIds)
                $userGroups = array();
                foreach ($this->getAppropriateUserGroups() AS $userGroup)
                    $userGroups[] = array(
                    'label' => $userGroup['title'],
                    'value' => $userGroup['user_group_id'],
                    'selected' => in_array($userGroup['user_group_id'], $selectedGroupIds)
                return $userGroups;
            public function getAppropriateUserGroups()
                return $this->_getDb()->fetchAll('
                SELECT user_group_id, title
                FROM xf_user_group
                WHERE user_group_id = 2 OR user_group_id > 4
                ORDER BY user_group_id
    Select save.

    This file will return a list of all User Groups that will be rendered into check boxes, with the exception of unregistered, Administrative, and Moderating. All new user groups you create will be listed in our Options as well.

    To view the generated check boxes: AdminCP -> Options -> Signature Options

    You should see Registered (defaulted to checked) and any new groups you may have created (which will not be checked), listed.

    Setting the default value(s) for Check Boxes:

    If you recall, we placed a:1:{i:0;s:1:"2";} as our default value, when we created the Apply to Group option. When you are creating multiple choice options, the easiest way to set which is selected as default is to leave the default value blank. Create the Callback, and then for the option, select which should be checked as defaults, and select save.

    Open phpMyAdmin, select your XenForo data base, and click the xf_option table. Notice the row called default_value, it is stored as a BLOB, and it is located third over.

    To see the contents of the default_value BLOB for siglimitGroup, click the Export tab. Under options located on the right side, scroll down and uncheck: Use hexadecimal for BLOB. Under Save as File, ensure Compression is set to None, and click Go. You should be given the choice open with:, select Note pad. If you are not given a choice, open the newly saved file with note pad, or equivalent. As the option was our last one created, scroll to the bottom of the text file. For our Addon, look for siglimitGroups, and you will see:

    ('siglimitGroups', 'a:1:{i:0;s:1:"5";}', 'a:1:{i:0;s:1:"2";}', 'callback', 'LimitSig_Option_Group::renderOption', 'array', '*', 1, '', '', 'limitSigs');

    Remember the default value is third over. Highlight and copy: a:1:{i:0;s:1:"2";}. This is the value that you will paste in the Default Value of the Apply to Groups option.
  5. Lawrence

    Lawrence Well-Known Member

    Part 5

    As our options are completed, we need to add in the code that will check a members post count and groups to determine if they can create a signature, or not. Open: LimitSig/ControllerPublic/LimitSig.php

        public function actionSignature()
    and replace with:
        public function actionSignature()
            if (!XenForo_Visitor::getInstance()->canEditSignature())
                return $this->responseNoPermission();
                    $visitor = XenForo_Visitor::getInstance();
                    $options = XenForo_Application::get('options');
                    if ($visitor['message_count'] < $options->sigmessageCount AND
                       (!$visitor['is_moderator'] AND !$visitor['is_admin']))
                                $applytogroups = $options->siglimitGroups;
                                if (empty($applytogroups))
                                    $applytogroups = array(2);  // default it to Registered group
                                $belongstogroups = $visitor['user_group_id'];
                                if (!empty($visitor['secondary_group_ids']))
                                    $belongstogroups .= ','.$visitor['secondary_group_ids'];
                                $groupcheck = explode(',',$belongstogroups);
                                $sigcheck = true;
                                foreach ($groupcheck AS $groupId)
                                        if (in_array($groupId, $applytogroups))
                                              $sigcheck = false;
                                if (!$sigcheck)
                                            return $this->responseNoPermission();
            return $this->_getWrapper(
                'account', 'signature',
    The first IF statement checks to see if the visitor has permission to edit their signature, and if not, exit right away as there is no point checking for post count, or group.

    If the visitor does have permission, we now need to retrieve their post (message) count and all user groups they may belong to. We know if they can edit a signature, then they must be registered, so we can pull their user data:

    $visitor = XenForo_Visitor::getInstance(); // load the member's user data

    Now we need our Signature Option settings:

    $options = XenForo_Application::get('options'); // initiate an instance of XenForo Options.

    We can now compare a members post count to our Admin setting, and see if the member is a moderator or an administrator. If the member is a moderator or an administrator, or if their post count is not less than sigmessageCount value there is no point in checking what group they belong to.

    If the member's post count is less, and the member is not a moderator or an administrator, we need to check if they belong to one of the groups that an administrator may have selected to be checked against.

    $applytogroups = $options->siglimitGroups; // grab our addon's group settings

    If $applytogroups is empty, we will default it to check for the Registered group. As members can belong to a primary group and numerous secondary groups, we will combine both into one array (if they are a member of a secondary group). A foreach is used to to test each members group with siglimitGroups. We can break on the first positive result, and return the no permission error. Otherwise, load the Signature editor.

    If your Addon was turned off, be sure to turn it back on:

    AdminCP home, and check the Add-on Limit Signatures for new Members

    Exporting your Add-on

    AdminCP Home -> Add-ons

    From the Controls drop-down for Limit Signatures for new Members, select Export. This will save an XML document that will contain the Listener, and our Options group. If we had added any new templates or phrases, these would be exported as well.

    Because we have files to include in our Add-on package, FTP to: yourForo/library/ and download the LimitSig/ directory to include all files and sub-directories.

    Highlight the XML and the downloaded LimitSig/ directory and add to a zip file. Normally you would also create a readme text file that would contain: how to install, what the Add-on will do, available Options, a disclaimer, and a copyright notice.

    The package is now ready for upload. Before you distribute your new Add-on, you should uninstall it from your forum, remove all files, and follow your install instructions to verify that it works.

    The Limit Sig file is included below in zip format. Download and unzip, and follow the install instructions.

    Feel free to add other options (such as strip images if trophy points is less than X, or even replace a members signature with an Admins choice if their Likes is below that of X).

    As stated in the first post, this Add-on is for beta 1, and will be updated to reflect changes in furture versions of XenForo. The Add-on can be found in the Tutorial Addon.zip file.

    Attached Files:

    Mike30, wang, Raaj nadar and 46 others like this.
  6. karrott

    karrott Active Member

    I've been following along for some time now. Thanks for posting this tutorial. It's truly very helpful, and I appreciate the work you put in to make this. :)

    One error: Your post suggests that the first option should be named 'limitsigmessageCount', when your code uses 'sigmessageCount' instead.
    Marc and Lawrence like this.
  7. Lawrence

    Lawrence Well-Known Member

    Thanks, :) I'm hoping this tutorial will be helpful for those members who want to try creating a XenForo Add-on a try.

    Fixed, and thanks. I had the original version installed when I wrote the new Add-on for this tutorial, and incorrectly used the older options group as reference.
    Bobage24 likes this.
  8. borbole

    borbole Well-Known Member

    Thank you for taking the time and post this. I am sure this will come in handy to a lot of coders and help them with understanding xenforo and creating mods. It is very well written and detailed :)
  9. Darren Gordon

    Darren Gordon Member

    Nice job.

    I'd also suggest renaming LimitSig_ControllerPublic_Listener to something else, such as LimitSig_Listener, or even better LimitSig_AddOn as it's not a ControllerPublic and naming it as such infers a relationship/requirement/convention that doesn't exist.

    The LimitSig_AddOn class could also be used to provide the install and uninstall methods of the addon, as well as any additional listener callbacks that may be added at a later date.
    Shadab likes this.
  10. Lawrence

    Lawrence Well-Known Member

    Thanks, :)

    I placed the listener in that directory because it is extending this class: XenForo_ControllerPublic_Account. Having the listener.php file in ControllerPublic directory of LimitSig tells me at a glance that it is listening for a specific class in XenForo_ControllerPublic to be loaded in $class. My memory isn't the greatest, so it works for me, :)

    There are no DB alters, so there is no need for an install or uninstall function, the xml file uploaded suffices.
  11. Darren Gordon

    Darren Gordon Member

    I was really meaning from a reader's point of view. XenForo, Zend and many other frameworks that follow the class -> file convention also follow a naming standard. The class name is in the form:

    This is useful because I can then learn things about the structure of the application without looking at the code. I know that everything in ControllerPublic/ is a ControllerPublic; everything in Model is a Model/; etc.

    This approach breaks that convention and implies to someone looking at the file structure that SigPic includes a new controller, which isn't actually true.

    This is a very good tutorial and I'm sure it will be a starting point for many people, so I would hate to see this practice picked up by a lot of people as it will cause confusion.

    Besides, if you had all your listeners in one place there'd be nothing to remember ;)

     * AddOn for MyAddOn.
     * @package MyAddOn
     * @author john.smith@example.com
    class MyAddOn_AddOn
    	 * Controller overrides.
    	 * @see XenForo_Application::resolveDynamicClass()
    	 * @param string $class Name of original class
    	 * @param array $extend In list of classes that will extend the original
    	public function loadClassController($class, &$extend)
    		if ($class == 'XenForo_ControllerPublic_Thread')
    			// do that magical thing to threads
    			$extend[] = 'MyAddOn_ControllerPublic_Thread_MyThread';
    		else if ($class == 'XenForo_ControllerPublic_Forum')
    			// do that other magical thing to forums
    			$extend[] = 'MyAddOn_ControllerPublic_Forum_MyForum';
    	 * Model overrides.
    	 * @see XenForo_Application::resolveDynamicClass()
    	 * @param string $class Name of original class
    	 * @param array $extend In list of classes that will extend the original
    	public function loadClassModel($class, &$extend)
    		// extend the user model to include that extra information we need
    		if ($class == 'XenForo_Model_User')
    			$extend[] = 'MyAddOn_Model_MyUser';
    Weiyan, arabic, Jeremy and 3 others like this.
  12. Lawrence

    Lawrence Well-Known Member

    Thanks, Darren for your guidance, and more importantly your explaination. :) I will be updating this tutorial with the changes for the listener. As I'm new to the MVC approach I have a lot of learning to do, and have learned a lot since this tutorial was written.

    Edit: Tutorial updated, and new zip uploaded.
    Jeremy and Kier like this.
  13. Kier

    Kier XenForo Developer Staff Member

    Good job, Lawrence :)
    Marocinoh likes this.
  14. Marc

    Marc Well-Known Member

    All tutorial posts liked :) ... Helped me out on how to create and use admin options :)
  15. bubbl3

    bubbl3 Active Member

    Thanks for taking the time to write this, much appreciated :)
  16. Weiyan

    Weiyan Well-Known Member

    Finished this tutor. Very helpful.

    One question: Can I customize the error message or forward to other page or popup the login window?

  17. DFx

    DFx New Member

    I'm getting this error.

    Server Error

    No controller response from XenForo_ControllerPublic_Account

    ->_handleControllerResponse() in XenForo/FrontController.php at line 311
    ->dispatch() in XenForo/FrontController.php at line 132
    ->run() in /home/darkfxco/public_html/xenforo/forum/index.php at line 15
    I've looked through everything.
  18. ragtek

    ragtek Guest

    it seems that your code doesn't return anything.
  19. DFx

    DFx New Member

    I figured that out. Just can't quite narrow it down. Since I used followed this, I would guess that the problem is in LoadClassController.php or LimitSig.php
  20. Weiyan

    Weiyan Well-Known Member

    Should you point out at which step this error happened?

    htweet likes this.

Share This Page