Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.
Options

Gamification Addon

2»

Comments

  • Options
    R_JR_J Ex-Fanboy Munich Admin

    Hiding things you do not want to see is most of the time the easiest way. Another easy approach is to make changes with JavaScript. I prefer writing plugins that make the changes before the "wrong" thing is transferred to the client. It is possible with a plugin to stop any module from being displayed. Since it is possible, I would go that way.

    Okay, by now you've altered files, but you are willing how to write plugins. So let me start explaining you something about plugins. You could use them to overwrite views, and with a little effort you could also use them to exchange complete controllers/models. But that is a bad habit. You should always strive to be least invasive. Got that? That's important. Really!
    If you rewrite complete functions, you run into the danger that after an update neither Vanilla nor your code will work any more. Changing Vanilla that way is the fastest way to achieve results, but it is a dead end. Sooner or later you end up with an unsupported, outdated, insecure installation where no one is able and willing to help with your problems.

    Having said that, you can imagine that your approach by overriding isn't good. Additionally it wouldn't work. You cannot override the method of a class like that.

    Okay, back to plugins.
    Plugins are able to take control when there is fireEvent() called in Vanilla. In your plugin you would need to add public function className_eventName_handler($sender, $args) {}.
    1. className
    2. eventName
    3. handler
    4. $sender
    5. $args

    I'll explain the parts of it:

    handler (handlerType)

    As you can see I switch the order of the five elements, simply it makes sense to explain them in a different order. We start with the most easiest. I've only called it "handler" although handlerType is the name that Vanilla uses for this part. "handler" is only one handlerType. It is the most used handlerType.

    • If you want to inject something into an existing controller/model/view, you will need to use the keyword "handler". If you do so, the eventName needs to be the string you find in a call to fireEvent.
    • If you create a new "endpoint" (e.g. "plugins/yourplugin/something") you would use "create". The eventName must be a string that is no method of the class used (you create your own and it must not exist before).
    • When you use the handler "before", the eventName must be "render". The code in this method will be executed before the html for this page is rendered.

    This is a simplified version, but it will satisfy your requirements in more than 95% of what you need. I will mainly explain the rest for the "handler" part and only say a few words for the other use cases.

    className

    Look at the file where you found the fireEvent() call. Getting the class name is easy if it is in class.discussioncontroller.php, class.categorymodel.php or any other file that already have the class name in the file name (and the class name is at the top, right after the keyword "Class"). Sometimes it is not obvious. You will learn how to find that name anyhow if you gain more experience, but here is a trick that will help you at the start: use "base" if you do not know the class name.
    Here is an example: there is a fireEvent('AfterReactions') in the file functions.render.php. Obviously this is no class, so how would you be able to output anything? Try this code (really, don't only read it - try it!)

    public function base_afterReactions_handler($sender, $args) {
      decho($sender->ControllerName, 'ControllerName: ');
    }
    

    Now visit the page where you like to make your changes (I assumed you like to change a /discussion/xy/blablabla page). You should be able to see a yellow box where the name "ControllerName: discussioncontroller" is displayed. That's what you need. The next step is to replace "base" with "discussionController" in your method.

    While you could stay with using base_... this is really, really bad. Remember my comment about being minimal invasive? Using "base" instead of the specific class is like using a pump gun when all you want to do is to pop a balloon. The main reason why you should not use it is that will kill the performance of your forum. If you do the implementation wrong, it could also lead to unwanted results on other pages.

    But in order to find out the name of the class (the first part of the method you have to create in your plugin) you could start with "base". Simply don't end up with using it.

    $sender

    After you now the class name, you already can tell what $sender is: it is an instance of this class. If you change something in the discussionmodel with public function discussionModel_beforeSaveDiscussion_handler($sender, $args), $sender would be the discussionmodel. You would be able to access all public properties and methods. If you would like to notify all users when a bookmarked discussion has been edited, you would use the "discussionModel_beforeSaveDiscussion_handler" method in your plugin and you would be able to get all users who bookmarked this discussion with $sender->getBookmarkUsers($args['DiscussionID']);

    $sender could be named anyhow you like. For a short while I believed it would make more sense to write my plugins methods like that:

    public function discussionModel_beforeSaveDiscussion_handler($discussionModel, $args){}
    public function messageController_beforeAddMessage_handler($messageController, $args){}

    While it would be more verbose, it shouldn't be needed. Just stay with conventions. It doesn't take long to get used to "$sender".

    $sender will always be an instance of the class, no matter which handlerType you use.

    eventName

    When you use the handlerType handler, the event name is the string that is in the call to fireEvent. Do a fulltext search for fireEvent and you will find numerous lines like that:
    $this->fireEvent("SignIn");
    $Sender->fireEvent('BeforeActivity');
    $this->fireEvent('BeforeFlyoutMenu');
    ...
    Those lines are your key to extending Vanilla the right way! Sometimes you can change data at this place and sometimes you can directly output your own strings (depends on where the event can be found). But they all have in common that they exist because someone thought it might be a good idea to enable plugin authors to take action.
    Some of them are not only fired from one class. If you like to add something to the discussion options dropdown, you would need the "DiscussionOptions" event. But if you would do it like that discussionController_discussionOptions_handler, you would only change it while the user is looking at one discussion. If it should be customized even on the "Recent Discussions" page, you would have to use "discussionsController_discussionOptions_handler. If you want to make a 100% sure that it is changed **anytime** this event is fired/the dropdown menu is shown, you might end up using ``base_discussionOptions_handler.

    $args

    Before a line with a call to fireEvent there might be lines where there is an array "EventArguments" used like that:

    //  Prep and fire event
    $this->EventArguments['FormPostValues'] = &$FormPostValues;
    $this->EventArguments['DiscussionID'] = $DiscussionID;
    $this->fireEvent('BeforeSaveDiscussion');
    

    When the event is fired, the EventArguments array is the second parameter in the call to our custom function! Everything that is referenced here as EventArguments['Something'] can be accessed in you plugins method as $args['Something'] - great, isn't it?

    The example above shows how powerful this system is. Looking at this specific example tells you that you have the possibility to inject code before a discussion is saved.
    At the time the event is fired, the discussion has not been saved (see yourself in discussion model or simply believe me). But nevertheless there is an argument called "DiscussionID". You would be able to access it in your plugin with $args['DiscussionID']. If this is a new discussion, there cannot be any ID since the ID is created automatically by the database. But if this discussion existed before and we only save a change, we must reference which row to change and the key for this is the discussion ID. Simply by getting this information we now things about the discussion. There is another argument here and we can even alter the values of this variable!
    "FormPostValues" is another common term in Vanilla and - not hard to guess - it is used for the values that have been posted back by a html form. If we want to change, delete, add some information before a discussion is saved, we can do so by implementing a "discussionModel_beforeSaveDiscussion_handler" method and change $args['FormPostValues'] in or new method. There is no need to touch any other file. All this work is done in out plugin.

    When you use className_render_before, $args will always be empty.

    When you use the handlerType "create", the eventName is the url for your new page and not the string inside of a fireEvent call. That's why there are no EventArguments. $args are the slug parameters in such a case. Try this one:

    public function vanillaController_ice_create($sender, $args) {
      echo 'learning<br />'.implode('<br />', $args);
    }
    

    Now open your browser at yourforum.com/vanilla/ice/is/cool ;)


    That was a less than 100 lines Vanilla plugins crash course. =)

    Concentrate on the "handler" part and ead this comment again.
    Be sure to test the few snippets provided here.
    Use any Vanilla event you like and accomplish to take any action/output to screen when this event is fired (don't start with events fired in models, you might not be able to output anything to screen)
    Now go and try to follow my steps from a-e in the previous comment. Read it again, start by looking at addModule() in class.controller.php and let's see what you come up with.

Sign In or Register to comment.