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.

Building a Tags Extension Plugin

KioriKiori New
edited August 2016 in Vanilla 2.0 - 2.8

Lately with the help of forum member i've been developing a plugin to extend the tags functionality in Vanilla Forums.
So far i can get the tags to show after the discussion title, after the meta content, on the "Discussions" page, and after the end of the first post on a Discussion proper.

However when i see the posts, be them discussions or question, on the categories page, or on the unanswered page, in short, in any page other than the main "recent discussion" page, the tags don't appear.

The current functions I'm using are:
public function discussionsController_discussionMeta_handler public function discussionController_afterDiscussionBody_handler
It seems to me that the catch is that when Vanilla is building these pages, it doesn't fire the events these functions are tied to.
Being that both the categories and the unanswered are in a way a minimal view of them.
If that's correct, then i may have to add these events somewhere, so they are fired and the functions will run.
However I'm not an expert yet, so I'd like the community to help me with this project.

I'll keep updating this first post as it's needed.

Thanks for any response!

Tagged:

Comments

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    the categories page, or on the unanswered page, in short, in any page other than the main "recent discussion" page, the tags don't appear.

    Because you must add the controller/page you want them to appear in

    public function categoriesController_discussionMeta_handler
    
    public function categoryController_afterDiscussionBody_handler
    

    a controller is a nerdish term for page … look at the controllers used in vanilla by checking out the files in the views folder etc.

  • @vrijvlinder said:
    Because you must add the controller/page you want them to appear in

    public function categoriesController_discussionMeta_handler
    
    public function categoryController_afterDiscussionBody_handler
    

    a controller is a nerdish term for page … look at the controllers used in vanilla by checking out the files in the views folder etc.

    That's awesome, so simple! I didn't expect Vanilla to be logical.(Being frank here)

    Also, I have a php question. After that, i guess the base of the plugin is done, and i can move to the gui part, which is creating a 'settings' page that opens up from the plugin menu, allowing the easy setting of styling for your tags.

    So for now, my question is:
    I got all these functions, and they all have the same content, Is there any way to not repeat myself in php?
    Furthermore, ideally i wanted the function to check if it the discussion has this or that tag, and execute this or that function.

    Currently my code looks like this:
    `public function discussionsController_discussionMeta_handler($sender, $args) {
    if (!property_exists($args['Discussion'], 'Tags')) {
    // No tags, nothing to do.
    return;
    }

            foreach ($args['Discussion']->Tags as $tag) {
                echo anchor(
                     Gdn_Format::text($tag['FullName']),
                    "/discussions/tagged/".Gdn_Format::text($tag['Name']),
                    array('id' => 'Tag_'.str_replace(' ', '_', Gdn_Format::text($tag['Name'])), 'style' => 'margin: 0px 3px 0px 3px;')
                  );
            }
        }`
    

    The inline styling was a there as i was developing it, forgot to remove it.

    So basically 2 things I'm not entirely sure how to do with php in this case, how to not have 3-4 different function calls with the same content, and how to check if the discussion has x or y tag before calling anything.

    I tried simple programming logic with if($tag['Name'] == [tagNamehere] || ...)
    But that didn't work. I've been reading about strcmp, but I'm not sure what exactly is inside of $tag['Name'] and how the code will deal with it.
    It should be the string value that comes from the database, but then why didn't it work...

    Btw, is there a list of values that can follow a namedController, like "discussionMeta", anywhere?
    I found that one after i had help from a forum user and searched the helper functions in each vanilla folder. I'm guessing there is more than what i found.

    Any help again is greatly appreciated, and thanks for the quick responses!

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    The best advice I can give you is to inspect other plugins and how they are built all together.

    If you want serious help from php gurus here, you would need to either post your code or attach a copy of the plugin you have made so others can check it out and tell you what to do.

    You should also know that the tagging plugin had or has some vulnerability which you need to make sure it has been fixed in your version.

  • @vrijvlinder
    This is the main file, the only other thing i got is a css atm.
    Please let me know of any vulnerabilities with the tag plugin. I'll gladly remove them, and integrate it to my plugin if that's needed.

    With the advice you gave me the tags now show up everywhere, except in the All Categories view, I couldn't guess the controller for that, and app/vanilla/controllers only has the usual suspects.

    Btw the php file inside the zip is a preliminary of my study by getting an example plugin and going from there, it's all very simply and hacky atm.
    I'm leveraging the standard tagging plugin, I need both enabled for anything to work atm.

    Thanks again!

  • R_JR_J Ex-Fanboy Munich Admin

    You have to take care for the casening of the plugins name
    1. The name in the PluginInfo: $PluginInfo['tagsextended'] = array(
    2. The folder for your plugin $sender->addCssFile('tagsextended.css', 'plugins/TagsExtended');

    If they are not the same, you will get into problems when you deactivate your plugin. So you should change the plugins name in the PluginInfo. Since you cannot deactivate it now from of the dashboard, I would suggest you delete the corresponding "EnabledPlugins" line in your config.php


    You can add a 'Name' => 'Tags Extender', line to your plugin info so that it is displayed nicely


    In order not to repeat yourself, create a method that the other methods call like that:

    /**
     * Echo formatted Tags of a discussion.
     *
     * @param vanillaController $sender Instance of the calling class.
     * @param array $args Event arguments.
     * @return void.
     */
    private function outputFormattedTags($sender, $args) {
        /* The code that you do not want to repeat all the time */
        if (!property_exists($args['Discussion'], 'Tags')) {
        ...
    }
    
    public function discussionsController_discussionMeta_handler($sender, $args) {
        $this->outputFormattedTags($sender, $args);
    }
    public function categoryController_discussionMeta_handler($sender, $args) {
        $this->outputFormattedTags($sender, $args);
    }
    public function categoriesController_discussionMeta_handler($sender, $args) {
        $this->outputFormattedTags($sender, $args);
    }
    

    But I would change it slightly to make it more versatile:

    /**
     * Format Tags of a discussion.
     *
     * @param vanillaController $sender Instance of the calling class.
     * @param array $args Event arguments.
     * @return string HTML formatted list of the discussions tags.
     */
    private function formatTags($sender, $args) {
        if (!property_exists($args['Discussion'], 'Tags')) {
            // No tags, nothing to do.
            return;
        }
    
        ob_start();
        foreach ($args['Discussion']->Tags as $tag) {
            echo anchor(
                Gdn_Format::text($tag['FullName']),
                "/discussions/tagged/".Gdn_Format::text($tag['Name']),
                array('id' => 'Tag_'.Gdn_Format::text($tag['Name']))
            );
        }
        return ob_get_clean();
    }
    
    public function discussionsController_discussionMeta_handler($sender, $args) {
        // That way you would be able to give extra stylings for different pages.
        echo '<span class="TagsOnDiscussions">'.$this->formatTags($sender, $args).'</span>';
    }
    public function categoryController_discussionMeta_handler($sender, $args) {
        echo 'Tags: '.$this->formatTags($sender, $args);
    }
    public function categoriesController_discussionMeta_handler($sender, $args) {
        echo $this->formatTags($sender, $args);
    }
    

    By the way: when you look at the file class.tagmodel.php you can find the line where the "Name" (not the "FullName") of a tag is built: 'Name' => trim(str_replace(' ', '-', strtolower($name)), '-'),. As you can see, all the spaces are already replaced with dashes, so you do not need to do that again for the Tag ID you are building.


    All those events that you can use can be used by doing a fulltext search for "fireEvent". Also the "eventi" plugin that peregrine already pointed you to is quite helpful for beginners.

  • R_JR_J Ex-Fanboy Munich Admin

    @Kiori said:
    That's awesome, so simple! I didn't expect Vanilla to be logical.(Being frank here)

    :lol:

    In order to judge if a discussion has the tag with the Name "some-tag", you either still have to loop through all the tags:

        foreach ($args['Discussion']->Tags as $tag) {
            if ($tag['Name'] == 'some-tag') {
                // do what you would like to do.
            }
        }
    

    Depending on what you try to achieve that might force you to loop 2 times through all tags and you could avoid that by using array_column

    if (
        in_array(
            'some-tag',
            array_column(
                $args['Discussion']->Tags,
                'Name'
            )
        )
    ) {
        // foreach loop here
    }
    
  • R_JR_J Ex-Fanboy Munich Admin

    @vrijvlinder said:
    a controller is a nerdish term for page

    :lol:

    Definitely the best way to explain that I've read by now!

  • R_JR_J Ex-Fanboy Munich Admin

    There are two way to create a setting screen: an easy way and a not so easy way. If you only need to have settings that are "not dynamic", you would most probably be able to use the easy way. Look at this two links to see how to easily build a setting screen:
    https://vanillaforums.org/addon/howtosettings-plugin
    https://vanillaforums.org/discussion/25253/simple-setting-screens-with-configurationmodule

    If your settings should include something like: "allow admin to add setting per tag" than this tag is the "dynamic" part which would require to have a not so easy setting screen.

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    @Kiori - I see that a lot traversed since you first started this discussion so you may have solved your issue regarding the display of information in different places but on the slight chance that you still need a reference you can take a look at the DiscussionTopic plugin where the settings screen provide options as to where to display plugin data (in your case the extended tagging data) and you will also see the different hooks that accomplish that (but don't use the plugin as an example of coding standards - I still have to rework it for that).

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    @R_J said:

    @vrijvlinder said:
    a controller is a nerdish term for page

    :lol:

    Definitely the best way to explain that I've read by now!

    Thanks, PHP is a nerdish term for personal home page…

    I have a few more like that :p

  • Thanks guys for all the support.
    You could say PHP: Hypertext Preprocessor is a nerdish term for Personal Home Page indeed. :smiley:

    @R_J
    Thanks for all the info, I used most of the info you provided and adapted to what i really need.
    I'm guessing you used ob_start and get_clean, for optimization. Thanks for the heads up!

    About the settings,I'm gonna think about what exactly I want for the settings and if I want them, and go from there.

    Also, Eventi doesn't work for me. It only enables it's self after i click on it twice, and then it only works on the dashboard at that moment, the moment the page reloads(i click anywhere), it's disabled again...

    @rbrahmson
    Thanks, are you referring to the "private function SetConfig" part of the code?

    @vrijvlinder
    You mentioned that "the tagging plugin had or has some vulnerability", can you be more specific?
    If I'm going to have to edit it, it's better to do it now right?

    I'm attaching the current version. It displays tags on the meta section in the discussions and the categories pages, and on the discussion per se, in the bottom of the discussion.
    It has proper ids to by-pass any styling you've made in a theme, and should work for the most part. :tongue:

    I couldn't get the tags to show when the categories are set to "mixed" and you are in the all categories section. But i gave up on that since it became unnecessary for my use case.
    It would be cool to add, maybe eventi would help figuring out how, if I could get it to work...

    Thanks for all the help, I'll post here again with more questions or an update on the plugin.
    Once this is done, should I contribute the plugin to the addons db?
    If so, how does that work?

    Thanks again for all the patience so far guys!

  • RiverRiver MVP
    edited August 2016

    see this:

    https://vanillaforums.org/discussion/32421/help-glitch-or-hacker

    if you have debug set to True in config.php sometimes it is difficult to enable plugins. set it to false when enabling a plugin. enable and disable via the dashboard.

    if something doesn't enable and debug is set to false - you have other issues with conf/config.php folder and permissions and ownership.

    A professional tip:
    the one thing people neglect when asking questions on the forum, is reading the other questions on the forum, you will learn alot more by reading the current discussions at least on the first page (then you would have known about tagging plugin insecurity). Because user tend to not add great titles or the discussion digresses, you may be missing out on things that may already be answered in a discussion only two or three discussion topics form you post.

    avoid the use of 'style' => 'margin: 0px 3px 0px 3px;')

    'style' => 'margin: 0px 3px 0px 3px;')

    background-color: #383b9f;"

    use class tags. don't inline. it will be easier for users to change via css.

    You need to sanitize all ouput if the user can input it. otherwise you will be prone to XSS attacks. be consistent. look through your code and see if you can see the inconsistencies with regard to displaying output.

    edit: never mind I was looking at the unsanitzed commented out function. good idea to delete the garbage, easier to read the plugin.

    Once this is done, should I contribute the plugin to the addons db?

    if it doesn't have security holes and doesn't break a users forum you could add it and you plan to support it through the next version of vanilla, then by all means. Some people add insecure plugins or add things and leave the forum and then other people download it and break their forums or make them insecure and the author is nowhere to be found and other people clean up the mess. And since broken plugins are not removed form the forum it just escalates the difficulties for new users to find good working secure plugins. And since many people don't read english or just don't read, even if a plugins is broken and it is mentioned they will probably not notice anyways until they clobber their forum.

    Pragmatism is all I have to offer. Avoiding the sidelines and providing centerline pro-tips.

  • @River
    Thanks for the response.
    I finally figured out what you guys meant about the vunerability, well in my forum ppl -can't- add tags, only i get to add tags, so I guess that solves the issue right?
    I also disabled profile extender, I see the risks it brings now.
    Let me know if there are other security tips i should know about.

    About my current script, considering Gdn_Format::text which the guys help me to know about, I guess it's safe enough as it is right?
    And yeah the inline was preliminary, just to get it done quickly. But thanks anyways, for pointing it up.

    About maintaining, I intend to manage a certain forum for the foreseeable future, and these tags are essential for me. Besides I like Vanilla's MVC model, I choose it because it looks much easier to custom edit without breaking, then anything else out there, so I think I'm sticking with it.

    I'm still at the headache stage, where i dont know enough about vanilla to use the platform anyway I want, but that's always the case with something new, I've been reading a lot so hope's up. :smiley:

    I'm really grateful for how friendly the devs in this forum are!

    Thanks again!

  • I had a problem, this code wasn't working:
    if (!property_exists($args['Discussion'], 'Tags')) { // No tags, nothing to do. return; }

    I solved it by using this:
    if (empty($args['Discussion']->Tags)) { // No tags, nothing to do. return; }

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    @Kiori:
    Thanks, are you referring to the "private function SetConfig" part of the code?

    In there you will see configuration settings like "Plugins.DiscussionTopic.Showinlist" (which I use to control whether values are shown in the discussion list meta area) or "Plugins.DiscussionTopic.Showindiscussion". Follow these setting throughout the code and you will see all the related hooks. But I think that by now you are beyond that stage (which is good!)

  • R_JR_J Ex-Fanboy Munich Admin

    @Kiori said:
    About my current script, considering Gdn_Format::text which the guys help me to know about, I guess it's safe enough as it is right?

    You have to sanitize every value that came form the user before you write it to screen. You do not have to care for values that are written to db.
    But to get something as plain text, Gdn_Format::text() is what you need.

    @Kiori said:
    About maintaining, I intend to manage a certain forum for the foreseeable future, and these tags are essential for me. Besides I like Vanilla's MVC model, I choose it because it looks much easier to custom edit without breaking, then anything else out there, so I think I'm sticking with it.

    I really do think that Vanilla is the forum that could be extended the easiest. All other forum scripts might be better in other concerns or not, but if you like to tweak your forums functionality, Vanilla gets you started the quickest and you can achieve quite a lot with really few lines of code. Thus even beginners will have their first plugin finished in no time ;)

Sign In or Register to comment.