HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Controllers in Plugins

I am writing a plugin that adds a page and I have run into an issue with making a pretty route for it (see below). I'm considering adding a controller to the plugin and it got me thinking: is it alright to add controllers to plugins and simple one-method controllers in general, or is it better to save controllers for larger applications? I would like to know your thoughts on this.

Perhaps it depends on how we define applications vs. plugins. Most of the time, we only see controllers in applications, but they are supported in plugins, and plugins tend to be smaller and extend the Vanilla application. This plugin I'm writing is a good example for discussing this, specifically when it comes to adding a simple one-method controller to the plugin.

The Plugin as an Example

The plugin adds one simple page that displays a Twitch live stream video embed and live chat embed, represented as the website's official live stream.

It creates a /plugin/twitchpage endpoint in the PluginController and also creates a /settings/twitchpage endpoint in SettingsController where ConfigurationModule is used to let you set the Twitch channel name to be displayed.

Also, it has code to display link shown as "Live" to /plugin/twitchpage in the main menu. I would like to let you add a pretty route to this such as /live. For example, they can add an internal route from the dashboard with live$ (substituting "live" with whatever route you want) with the target plugin/twitchpage.

The issue is that the link in the main menu won't reflect the new route if one is added. This can't be solved by calling the Gdn::Router()->matchRoute() method because it checks the route expression, which can be whatever the user has set, and not the target (should be plugin/twitchpage).

Some solutions:

  1. Assume the user only would ever use /live as the route (I'd use this) and add an option to the settings page to add the route in; however, that means I wouldn't be able to use the ConfigurationModule, where all settings are meant to be saved in the /conf/config.php whereas the route is set by other methods. Although I can write a custom settings page, this just adds complexity to a small plugin. It would be simpler to make the endpoint /plugin/live instead.
  2. Add a LiveController within the plugin with one method.

What solution do you think is best for this plugin to achieve the pretty routing?

Add Pages to Vanilla with the Basic Pages app

Comments

  • Have you tried ?

         $matchroute = '^twitchpage(/.*)?$';
                     $target = 'plugin/twitchpage$1';
    
                     if(!Gdn::Router()->MatchRoute($matchroute))
                          Gdn::Router()->SetRoute($matchroute,$target,'Internal'); 
    
  • edited September 2016

    @vrijvlinder, that would be good to place on the settings page as a "add and use /twitchpage route instead of /plugin/twitchpage" check box, but is there a way to execute that logic when the settings are saved with the ConfigurationModule?

    Edit: You probably mean to run that code on the plugin enable and disable events--or anywhere else that isn't saving the settings page for that matter? I have no idea why I hadn't thought of this earlier. That's what happens when I get caught up in seeing how controllers work out in a plugin haha :p I think most users will be okay with the assumed route name, so this would work great, actually!

    Add Pages to Vanilla with the Basic Pages app

  • That code works flawless in a setting screen. I've used this in a plugin I've never released. My approach was making the route translatable. That way I didn't have to mess around with a setting ;)

    Although your problem is already solved: x00 often uses a lot of controllers in his plugins. I have thought about doing this in complex plugins to keep the plugins controller readable, but never did that. It always was enough for me to use external models and views.
    If I had splitted my controllers tasks, I would have separated setup(), structure() and settings() in a background/dashboard controller.

  • edited September 2016

    @R_J, I didn't know x00 used controllers in his plugins. I might take a look to see how they're put into effect. =)

    Also, controllers being supported in plugins is a recent addition since Vanilla 2.2. It'll be interesting to see how developers incorporate controllers in their plugins.

    In case anyone finds it useful, here is a snippet of code I just wrote for creating and deleting the route and showing a link to the page in the main menu accordingly from within the plugin:

    /** Route settings for the live page. */
    const ROUTE = 'live';
    const ROUTE_EXPRESSION_SUFFIX = '$';
    const ROUTE_TARGET = 'plugin/twitchpage';
    
    /**
     * Add main menu link to the live page.
     *
     * @param Gdn_Controller $sender
     */
    public function base_render_before($sender) {
        if ($sender->Menu) {
            // Determine menu link by route.
            $route = Gdn::router()->matchRoute(self::ROUTE . self::ROUTE_EXPRESSION_SUFFIX);
            $menuLink = ($route && $route['Destination'] === self::ROUTE_TARGET) ? self::ROUTE : self::ROUTE_TARGET;
    
            $sender->Menu->addLink('LiveStreamPage', t('Live'), '/' . $menuLink);
        }
    }
    
    /**
     * Set route for the live page when this plugin is enabled.
     */
    public function setup() {
        if (!Gdn::router()->matchRoute(self::ROUTE . self::ROUTE_EXPRESSION_SUFFIX)) {
            Gdn::router()->setRoute(self::ROUTE . self::ROUTE_EXPRESSION_SUFFIX, self::ROUTE_TARGET, 'Internal');
        }
    }
    
    /**
     * Delete route for the live page when this plugin is disabled.
     */
    public function onDisable() {
        $route = Gdn::router()->matchRoute(self::ROUTE . self::ROUTE_EXPRESSION_SUFFIX);
    
        // Make sure that no other matching route than the plugin's default route is deleted.
        if ($route && $route['Destination'] === self::ROUTE_TARGET) {
            Gdn::router()->deleteRoute(self::ROUTE . self::ROUTE_EXPRESSION_SUFFIX);
        }
    }
    

    Apparently, Gdn::router()->matchRoute() returns route info when I'd expected only a boolean, even though there's a getRoute() method.

    After all, if I'm only adding one simple method to a controller, it seems better to create a route for it instead of adding the overhead of a whole new controller.

    Add Pages to Vanilla with the Basic Pages app

  • edited September 2016

    A controller that is not a settings controller is now called a Vanilla Controller…. in the latest stuff. Plugin controller has bugs when it comes to style from the admin.css interfering with the Master view.

    Things like this can happen, where the admin theme is shown instead of the master view…

    https://vanillaforums.org/badge/community-coder

  • edited September 2016

    @vrijvlinder said:
    A controller that is not a settings controller is now called a Vanilla Controller…. in the latest stuff. Plugin controller has bugs when it comes to style from the admin.css interfering with the Master view.

    Things like this can happen, where the admin theme is shown instead of the master view…

    https://vanillaforums.org/badge/community-coder

    Nice find! Has a discussion been made about that yet? Viewing details about a badge has been a front-end page, so the /badge endpoint shouldn't show the dashboard view at all unless it was recently made that way on purpose. Is this a bug in the official badges addon, @Linc?

    Add Pages to Vanilla with the Basic Pages app

  • Yes I let Linc know and he confirmed it's a bug…. a nasty smelly stink bug… lol

  • Perhaps it depends on how we define applications vs. plugins.

    Afaik this differentiation is going to go away soon. I don't see a reason not to use controllers for larger plugins.

  • A controller is definitely the best way to go about this. It's straightforward, clear, and less prone to future issues. @Bleistivt is correct: applications will one day be folded into plugins. There is precious little difference between them at this point. Personally I wouldn't start a new application for anything less than a new standalone product that didn't rely on the forum in any way.

    We actually have the patch for that badge view already merged internally, I just hadn't cherry-picked it to our temporary production release branch yet. We're working on merging our six-month dashboard rebuild project so it's been a bit distracting to our normal workflow.

Sign In or Register to comment.