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.

What is the hijackable handler that Add Tags uses?

I have a script that does some Tag recalculation, and then saves the info to custom Category column. Right now I can fire it when I save/delete a discussion.

How can I fire it when from DiscussionList view, using the nice quick Add Tags feature?

This works when I edit a Discussion

public function discussionModel_afterSaveDiscussion_handler($sender, $args) {
    $categoryID = valr('Fields.CategoryID', $sender->EventArguments, 0);
    $this->calculateCategoryTags($categoryID);
}

I am now looking for something like this

public function discussionController_tag_create($sender) {
   $this->calculateCategoryTags($categoryID);
}

Thanks.

Comments

  • R_JR_J Admin

    It's possible but it was hard to find out how...

    If you look at the link you see that it is "/discussion/tag". Since it is the tagging feature which is extending a Vanilla controller, you first would inspect class.tagcontroller.php (without success) and afterwards you would have to look at /applications/vanilla/settings/class.hooks.php but there is no public function discussionController_tag_create there (which is the method definition which would create that endpoint.

    But when doing a full text search for this code, you'll end up in /applications/dashboard/settings/class.hooks.php. I wouldn't have searched there but at least I've found the code where that magic happens.

    But there is no event fired in there. For saving data, it uses class.tagmodel.php but also in that class there is no event that you can use, either.

    That's bad luck


    You now have to rely on another event which is used quite often which means that you have to waste resources, though I consider this no real problem, i just think it is unstisfyingly uneffective

       /**
        * Intercept rendering of a discussion and only react on /dscussion/tag
        *
        * @param DiscussionController $sender Instance of the calling class.
        * @return void.
        */
       public function discussionController_render_before($sender) {
           // directly return if this is no /discussion/tag request
           if ($sender->RequestMethod != 'tag') {
               return;
           }
           $tags = $sender->Form->formValues('Tags');
    


  • rbrahmsonrbrahmson ✭✭✭

    @R_J - Neat analysis and guidance ! I wonder whether another approach would also work -- use render_before hook to add a .jquery that changes the link to ones own plugin and invoke the tag model from inside augmenting the process with whatever the plugin wants to do...

  • R_JR_J Admin

    Injecting a script in each page load is "more expensive" than checking the request type only when a single discussion is shown.

    The only alternative I would consider would be changing core files to insert a fireEvent into the TagController or the TagModel and use that. This change should be made a pull request, too and with a good description why that event is needed, chances are high that the PR will be accepted

  • I am still plugging away. I hope to get this working soon.

    Thanks for the pointers.


    PS. Imagine if one could edit/remove Tags, not just add new ones from the DiscussionList. This would be great for sites who are using tags as categories.

  • R_JR_J Admin

    Well, why not?

    The menu where you find that "Tag" entry is built in /applications/vanilla/views/discussion/helper_functions.php and there you find

           // Allow plugins to edit the dropdown.
           $sender->EventArguments['DiscussionOptionsDropdown'] = &$dropdown;
           $sender->EventArguments['Discussion'] = $discussion;
           $sender->fireEvent('DiscussionOptionsDropdown');
    

    Hooray, your intention was to have a "Edit Tags" instead of a simple "Tags" and therefore it is correct: you want a plugin that edits this dropdown.

    The $dropdown we have to remove a link and add a link from is a DropDownModule. looking at its source doesn't show us a way to add links or remove them. Those methods come in from the NestedCollection trait (/applications/dashboard/library/class.nestedcollection.php) which has a removeItem method.

    The method which adds links reads like that: public function addLinkIf($isAllowed = true, $text, $url, $key = '', $cssClass = ''..., the link is added with that parameters:

               ->addLinkIf(
                   $canTag,
                   t('Tag'),
                   '/discussion/tag?discussionid='.$discussionID,
                   'tag',
                   'TagDiscussion Popup'
               )
    

    So the $key of this item is "tag" and that is what removeItem($key)consumes. With that knowledge, you can prepare the menu for your plugin:

       public function base_discussionOptionsDropdown_handler($sender, $args) {
           if (!Gdn::session()->checkPermission('Plugin.TagManagement.Manage')) {
               return;
           }
           $args['DiscussionOptionsDropdown']->removeItem('tag');
           $args['DiscussionOptionsDropdown']->addLink(
               t('Edit Tags'),
               '/plugin/tagmanagement?discussionid='.$args['Discussion']->DiscussionID,
               'tag',
               'TagDiscussion Popup'
           );
       }
    

    In your plugin you can copy most of what you find in the hooks file mentioned earlier. You can most probably reuse the Add Tag view that is used and only have to add existing tags to that view. It shouldn't be too hard for someone who has some Vanilla experience.

Sign In or Register to comment.