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

How do I write a Plugin with a Module?

2»

Comments

  • R_JR_J Ex-Fanboy Munich Admin

    You've asked for custom setting permissions and that's what we do next. We have started the plugin part of our settings code like that:

    public function PluginController_Countdown_Create($Sender) {
       $Sender->Permission('Garden.Settings.Manage');
    

    I've told you that we do a standard permission check. Try to access the setting screen when you are not logged in: http://www.example.com/dashboard/plugin/countdown. You will be prompted to log in and if you login with a standard user account you will see an error message. That's good.
    Let's doublecheck and comment out the above permission check. Try to access this side again when you are not logged in... and OH MY GOD! The settings page is now accessible to guests! Well that looks severe, but in fact they are not able to change anything. But nevertheless, that is critical.
    Now log in as a normal user and visit that settings page. Bingo. Normal users are allowed to change the config settings if we do not use any permission check for that. That's why you always have to check for proper permissions when you create a custom setting screen.

    Checking for a permission could also mean checking for a custom permission. Here we are...

    Let's make that permission check: $Sender->Permission('Plugins.Countdown.Manage');. Yes! Bye bye guest access, bye bye user access. Only admin will be able to see your setting screen now (permission check do not work for admins, they are allowed to access anything). Now that we are checking for the right permission we would like to grant that permission to someone, maybe to moderators. But when you look at the permissions of the moderator role, you will not find that permission. We will have to create a new permission first before we can use it. But that again is most easy.

    I have told you something about the possible entries in PluginInfo array, but I left this one out: "RegisterPermissions". That's our solution. Simply add the following line somewhere to the PluginInfo array at the top of your plugin file:
    'RegisterPermissions' => array('Plugins.Countdown.Manage'),
    (By the way: the order of the entries in that array doesn't matter, but you have to take care that you separate each of the entries with a comma)

    That may look promising but when we look again at the permissions of our moderators, we cannot see our new permission. We first have to disable and reenable our plugin. Do that and then return to the permissions and you will see our newly created permission. Now assign that permission and try it out. You'll see that it works perfectly!

    To sum up what we need for a custom permission:
    1. create the new permission

    $PluginInfo['Countdown'] = array(
       'RegisterPermissions' => array('Plugins.Countdown.Manage'),
       'SettingsUrl' => '/dashboard/plugin/countdown',
       ...
    
    1. assign the new permission to some roles

    2. check for the correct permission before you render a view

    public function PluginController_Countdown_Create($Sender) {
          $Sender->Permission('Plugins.Countdown.Manage');
    

    And that's it.

    Now that the right user roles have access to your setting screen, you have to take care that they are able to find it ;)
    I think that this great tutorial describes a nice solution for that problem: http://vanillaforums.org/discussion/27099/tutorial-how-to-add-an-option-to-the-flyout-menu-in-2-1-and-add-link-in-dashboard-as-well

  • JS was exactly the kind of magic to handle the date/time issue.

    @R_J said:
    3. search for a javascript date time picker like that: http://www.ama3.com/anytime/ It transforms a normal TextBox so that you can use all the advantages of Vanilla.

    Wow, that's nice date/time picker, never seen such before. That's the one I want to use (can't play around with it right now, I am at work, but will definitely try to get that one going as soon as I have some time).

    This all is really insightful, though I have to process all the info and play around with it.

  • R_JR_J Ex-Fanboy Munich Admin

    Take your time. I will go down through the code without waiting for you, but if you have any questions, just point to what is not clear and I'll try to explain it better.
    I'll have to write it down in time in order not to loose track what I have already spoken about... ;)

  • R_JR_J Ex-Fanboy Munich Admin

    When speaking about the settings view, I haven't said much about the validation - I just said "we need it". Well, if we have something like a validation, let's use it! By now we do not validate anything. I think what we need at least is that every field is filled. So let's validate that!

    // we know that code already
    $Validation = new Gdn_Validation();
    $ConfigurationModel = new Gdn_ConfigurationModel($Validation);
    $ConfigurationModel->SetField(array(
       'Plugins.Countdown.Title',
       'Plugins.Countdown.Target',
       'Plugins.Countdown.Date',
       'Plugins.Countdown.Days'
    ));
    // let's start the validation!
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Title', 'Required');
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Target', 'Required');
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Date', 'Required', 'No date, no countdown...');
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Days', 'Required');
    

    As you see, there is a possibility to apply a rule to the fields. I've also provided one custom error message so that you see that it is possible. If the function is called "ApplyRule" and we have a rule called "Require" there's a big probability that there are other existing rules. Take a look at the source: https://github.com/vanilla/vanilla/blob/2.1/library/core/class.validation.php#L104-128
    See that there is "UrlString", "Timestamp" and "Integer"? Looks like we could do some more validation... :)

    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Title', 'Required');
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Target', array('Required', 'WebAddress')); // will only accept full addresses that include http:// and domain
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Date', 'Required', 'No date, no countdown...');
    $ConfigurationModel->Validation->ApplyRule('Plugins.Countdown.Days', array('Required', 'Integer'));
    

    Wait, I've told you above that there is a validation called Timestamp but I didn't included it! That's because I was curious and looked at the source code (functions.validation.php) and here's what I have found:

    function ValidateTimestamp($Value, $Field) {
       // TODO: VALIDATE A TIMESTAMP
       return FALSE;
    

    I'm glad I've looked that up. So you will be on your own when you try to validate that. Vanilla is great but not perfect. But there's always more than one way to reach the goal. We "simply" have to implement our own check.

    $Sender->Validation->AddRule('RegexCountdownTimestamp', 'regex:/ don\'t know what you need... /');
    $Sender->Validation->ApplyRule('Plugins.Countdown.Date', 'RegexCountdownTimestamp', "That's no valid time stamp");   
    

    At first we add a rule. We give it a name so that we can reference it later on and then we define some regex magic.
    Afterwards, we assign our new rule to the field like we have done with all the prebuild rules.
    And that's the best thing about validation if you ask me: you can let the input be validated against any custom regex on the server. Validations are one of my favorites.

  • R_JR_J Ex-Fanboy Munich Admin

    I wanted to go through the code from top to bottom and add information whenever I think it could be useful. Now we are at a point that was hard for me to leave out on the first run: translations. It is really easy to make your code translatable and you should always do.

    Look at that line: $Sender->StatusMessage = 'Your settings have been saved.';. Before we continue, I have to beg your pardon for showing you StatusMessage. That function is deprecated and replaced by InformMessage - or ErrorMessage but let's hope we never need that ;)

    I guess there is a translation for the above sentence in any language pack, but it could not be applied to that line because we passed the string directly to the function. We need a translation function that will take care of translations. The function in Vanilla for that is simply called T. If we change the above line to $Sender->InformMessage = T('Your settings have been saved.'); the function looks up for a translation in the current active language pack and returns that translation if there is one. If there is no translation for that string, it will be returned. Here is a comprehensive HowTo for language files: http://vanillawiki.homebrewforums.net/index.php/Internationalization_&_Localization

    If you want to use that translation code in your view, you'll see that you have to change something. You cannot use a function inside of the heredoc style string (which I prefer for longer strings). Here's a workaround for that...
    Each controller has a public array called $Data that you can use as a temporary storage. So if that array contains a value for 'Title' we would be able to access it like that $this->Data['Title'] and that could be enclosed in our heredoc string.
    That's the code I wanted to note down originally for the view:

    < h1>{$this->Data['Title']}< /h1>
    < div class="Info">{$this->Data['Description']} < /div>
    

    So that the translation part isn't done inside of the view but somewhere else. In fact we have to fill those variables and that is done in the function that renders the settings view.

    $Sender->Data['Title'] = T('Countdown Settings');
    $Sender->Data['Description'] = T('Countdown description', 'Write here the long description that should be presented in the settings view.');
    $Sender->Render($this->GetView('settings.php'));
    

    But that's just a matter of taste and since this is my tutorial, I show you mine ;)

  • R_JR_J Ex-Fanboy Munich Admin

    I guess that's all I have to say right now... But one thing to the Date field, although you will not use it if I got you right.

    $this->Form->Date('Plugins.Countdown.Date')
    We can add some parameters to that date field. Maybe you want to have another order of those fields? I prefer day, month, year and that's how it could be done: $this->Form->Date('Plugins.Countdown.Date', array ('Fields' => array('day', 'month', 'year'))). Seen that it only provides the current year and a lot of long past years? Try that: $this->Form->Date('Plugins.Countdown.Date', array ('Fields' => array('day', 'month', 'year'), 'YearRange' => '2012 - 2016')).
    If you do not want abbreviations for the month names, include the following in your language file:

    $Definition['Jan'] = 'January';
    $Definition['Feb'] = 'February';
    $Definition['Mar'] = 'March';
    $Definition['Apr'] = 'April';
    $Definition['May'] = 'May';
    $Definition['Jun'] = 'June';
    $Definition['Jul'] = 'July';
    $Definition['Aug'] = 'August';
    $Definition['Sep'] = 'September';
    $Definition['Oct'] = 'October';
    $Definition['Nov'] = 'November';
    $Definition['Dec'] = 'December';
    

    That's it! It was a pleasure :)

  • It all makes so much sense, and it's too bad I'm so overloaded with work right now, I could play with module creation all day long.

    I guess I should consider putting those logo links, which I placed manually into the two template files (normal and mobile) as well:

    Placing logo-links in a row above {asset name="Content"}

    Is every possible modification suitable to be made into a module, or are there things which you consider better be put into the template?

    What's the difference between a module and an application (like Yaga is, which I just installed)?

  • hgtonighthgtonight ∞ · New Moderator

    @MasterOne said:
    What's the difference between a module and an application (like Yaga is, which I just installed)?

    There are 4 different types of addons in Garden (the framework that Vanilla runs on).

    • Themes
    • Locales
    • Plugins
    • Applications

    Themes change the way your site looks. Locales change the wording of text. Plugins add functionality. Applications can do all three. The lines blur a little when you start talking about theme hooks and overriding classes. There is also a push over on GitHub to lose the distinction between Applications and Plugins.

    Modules aren't on the list because Modules can't attach themselves to controllers. They have to be bundled with a plugin or application to be used.

    Search first

    Check out the Documentation! We are always looking for new content and pull requests.

    Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.

  • R_JR_J Ex-Fanboy Munich Admin

    A module is a convenient helper class. You could achieve anything a module can do with just a normal plugin. But read that comment why it is wise to stick to modules nevertheless: http://vanillaforums.org/discussion/comment/207569/#Comment_207569

    I understand modules as what is called a block or something similar in some CMS. And I would only use it that way.
    But sometimes you cannot insert code at places that you need to. That's when a custom theme is needed. And I think it is an elegant solution to insert an asset at the place where your additional content should be. Then you can render your code with a module and set the appropriate AssetTarget.

    But as I said before: using a custom AssetTarget makes your plugin unusable for others. But there is something like a "solution" for that. In the PluginInfo array, you could specify 'RequiredTheme' => 'YourTheme' to stress that your plugin only works with your theme.

Sign In or Register to comment.