Jump to content
Support Closed Read more... ×

Community

  • We are moving our Documentation to our new Guides area.

    As articles are moved they will be deleted. Please start following the new guides. We look forward to publishing all our new guides soon!

  • Plugins: An Example (*)


    Mark

    This guide will demonstrate step-by-step creating how to create simple plugin making use of most of the features provided. For this guide, you'll be shown how to create a plugin which displays a message at the top of every page on the community.

    Useful links for further reading:

    Step 1: Creating the Plugin

    To begin, you'll need to have a test installation in developer mode. Once developer mode is enabled, a "Create Plugin" button will appear in the Admin CP under System --> Site Features --> Plugin. Use this tool to create your plugin, after which you'll be taken into the Plugin Developer Center.

    ccs-52603-0-12359800-1375958550.png


    Step 2: Creating the Theme Hook

    The easiest way to display a message at the top of every page is to create a theme hook. Theme hooks allow you to modify the content of a template. The template we'll be modifying is "globalTemplate" which is in the "global" template in the "front" location in the "core" application.

    Under the "Hooks" tab, create a new hook and choose "core --> front: global" as the template group. Once it has been created, click the edit button, then choose "globalTemplate" from the menu on the left.

    ccs-52603-0-70275100-1375959127.png

    Choose "Select Element" to bring up the contents of the template and choose the point you want to hook on - a good place is to select <div id="ipsLayout_mainArea"> and then for the content position choose "Insert content inside the chosen element(s), at the start."

    For now, specify the contents manually:

    <div class="ipsMessage ipsMessage_information">This is the global message.</div>
    

    After saving you can go to the homepage and you will immediately see the message you've just created. Congratulations, you've just created a simple Plugin!

    ccs-52603-0-49354100-1375959934.png

    The remaining steps will show how to expand this functionality.


    Step 3: Using Templates

    It's good practice to keep all HTML in templates. While the current approach works fine, the HTML content is buried within the code of your plugin, so if someone installing the plugin wanted to modify it in some way (perhaps change the CSS classes on it), it would be difficult to do so.

    Fortunately, creating a HTML template is really easy. If you look into the directory on your computer/server where IPS Community Suite is installed, you'll notice a "plugins" directory - inside this you'll find a directory has been created for your plugin with the name you specified in Step 1. Inside this is a folder navigate to dev/html. Within this folder, files you create will become available as a template.

    Create a file called "globalMessage.phtml" - and set the following as it's contents:

    <ips:template parameters="" />
    <div class="ipsMessage ipsMessage_information">
    	Now using a template!
    </div>
    

    The first line is just to name any parameters that will be passed into the template - which is not needed in this case.

    Once the file has been created, edit your theme hook and change the contents it inserts to use this template by using this code:

    {template="globalMessage" group="plugins" location="global" app="core"}
    

    This is a template tag which pulls in the contents of a template. All templates created by plugins are created in the "plugins" group in the "global" location in the "core" application.


    Once this is done, you should see the message has changed to "Now using a template!"

    As a side-note, if you wanted to add CSS code, you can just add css files into the dev/css folder and they will automatically be included.


    Step 4: Settings & Language Strings

    Now you have a global message - but currently there's no way to customise what it says. It would be handy if the plugin had a simple setting in the Admin CP that the admin could use to change the contents.

    To create a setting - go to the "Settings" tab in the developer center for your plugin and add a setting - for the key, use "globalMessage_content", and set the default value to whatever you like. It's important to make sure that your plugin starts working straight after it installs so the admin knows it's working properly, so don't leave the default value blank.

    Creating the setting here allocates space for your setting in the database, but you still need to create a form where the admin can edit it. To do this, again look in the directory for your plugin on the filesystem - you'll see a file called "settings.rename.php", first rename this to "settings.php" then open it up. It already contains example code to get you going. Change the first line of code (the $form->add(...) call) to this:

    $form->add( new \IPS\Helpers\Form\Editor( 'globalMessage_content', \IPS\Settings::i()->globalMessage_content, FALSE, array( 'app' => 'core', 'key' => 'Admin', 'autoSaveKey' => 'globalMessage_content' ) ) );
    

    This code is using the form helper.

    Now when you go to the Plugins area of the Admin CP, you'll see a new "edit" button next to your plugin, when clicked, this brings up a form with a place where users can fill in a message.

    There is however, a problem with your form. The label for the setting just says "globalMessage_content" - obviously this needs to be changed to something more useful, for which you'll need a language string. To create one, look in the directory for your plugin again and open up the dev/lang.php file. It will contain just an empty array - add an element to this array like so:

    $lang = array(
    
    'globalMessage_content'	=> "Message",
    
    );
    

    The label will now say "Message".


    Finally, you need to actually make the user-defined message show. To do this, open up the globalMessage.phtml file you created in step 3 and change it's contents to:

    <ips:template parameters="" />
    {{if settings.globalMessage_content}}
    	<div class="ipsMessage ipsMessage_information">
    		{setting="globalMessage_content"}
    	</div>
    {{endif}}
    

    That adds some template logic which detects if there is a value for our setting and only displays the message if there is, and another template tag which gets the value of our setting.

    If you wanted to add your own CSS, you can create CSS files in the dev/css folder of your plugin directory - any files you create there will be included automatically.

    Step 5: Making changes to the database

    If we wanted to take this plugin even further, you could add a "close" button to the message allowing users to dismiss the message once they have read it. Whether or not any given user has dismissed the message is information can be stored in the database.

    Open up the dev/setup folder of your plugin directory. In here you'll find a file called "install.php" - this file is ran when your plugin is installed. If you need to make subsequent database changes in future versions of your plugin, you can create new versions in the developer center, and a file will be created for each version you create - you need to make sure you add whatever changes you need both to the install.php file and the appropriate upgrade file.

    Open up install.php and add this code inside the step1 method:

    		\IPS\Db::i()->addColumn( 'core_members', array(
    			'name'		=> 'globalMessage_dismissed',
    			'type'		=> 'BIT',
    			'length'	=> 1,
    			'null'		=> FALSE,
    			'default'	=> 0,
    			'comment'	=> 'If 1, the user has dismissed the global message'
    		) );
    		return TRUE;
    
    

    This code adds a new column to the core_members table in the database, which is the table which contains information on all the members. The column created is a BIT(1) column (which means it can store only 1 or 0) - 0 will indicate the user has not dismissed the message (so it should show) and 1 will indicate they have - the default value is set to 0.

    Though this code will make sure when your plugin gets installed the column will be added, you'll need to run a query on your local database so that you have it during development. Run this SQL query using whatever your preferred method is for managing your database:

    ALTER TABLE core_members ADD COLUMN globalMessage_dismissed BIT(1) NOT NULL DEFAULT 0 COMMENT 'If 1, the user has dismissed the global message';
    

    After the user dismisses the message, their preference will need to be reset if the admin changes the contents of the message. To handle this, add this line to your settings.php file in your plugin directory, just after the $form->saveAsSettings(); call.

    \IPS\Db::i()->update( 'core_members', array( 'globalMessage_dismissed' => 0 ) );
    

    Step 6: Creating a Code Hook

    Now you've allocated space in the database where the flag will be stored, you need to write code to actually set that value, and link it up with the template.

    You'll need to add a method to a controller which will handle the user's click. A generic-use controller intended for this sort of thing is available - \IPS\core\modules\front\system\plugins - though theoretically, you could add the method to any controller.

    In the developer center, create a code hook on that class and open it up (either in the Admin CP or by opening the file that has been created in the hooks directory of your plugin directory) and add the following code inside the class:

    	public function dismissGlobalMessage()
        {
    		\IPS\Session::i()->csrfCheck();
    		
    		if ( \IPS\Member::loggedIn()->member_id )
    		{
    			\IPS\Member::loggedIn()->globalMessage_dismissed = TRUE;
    			\IPS\Member::loggedIn()->save();
    		}
    		else
    		{
    			\IPS\Request::i()->setCookie( 'globalMessage_dismissed', TRUE );
    		}
    		
    		\IPS\Output::i()->redirect( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : \IPS\Http\Url::internal( '' ) );
    	}
    
    

    It's important to understand thoroughly what this function is doing:

    • It first performs a CSRF check. Because this is a controller method, it is executed automatically on accessing the appropriate URL. Because it does something which affects the user (it modifies a preference) it is essential that it have a CSRF check. If the check fails, execution will be halted automatically.
    • It checks if the current user is a registered member. \IPS\Member::loggedIn() returns an \IPS\Member object for the current user - if the user is a guest (not logged in) the member_id property will be 0. If the user is logged in, it sets the globalMessage_dismissed property to TRUE and saves the member. Since \IPS\Member is an Active Record, this will edit the column you created in step 5 for the appropriate row in the database. If the user is not logged in, it sets a cookie so that even users who are not logged in can dismiss the message.
    • It then redirects the user back to the page they were viewing, or if the server cannot provide the HTTP Referer, the home page.

    Now that the action has been created, you need to adjust the template to honour the preference, and add a button that links to the dismiss action.

    Modify the template you created in step 3 to:

    <ips:template parameters="" />
    {{if settings.globalMessage_content and !member.globalMessage_dismissed and !isset( cookie.globalMessage_dismissed )}}
    	<div class="ipsMessage ipsMessage_information">
    		<a href="{url="app=core&module=system&section=plugins&do=dismissGlobalMessage" csrf="1"}" class="ipsMessage_code ipsType_blendlinks ipsPos_right"><i class="fa fa-times"></i></a></span>
    		{setting="globalMessage_content"}
    	</div>
    {{endif}}
    

    Step 7: JavaScript

    You now have a completely functional plugin which displays a message to all users that they can dismiss. For a little bit of added flair, you can make it so when the user dismisses the message, rather than reloading the page, it performs the action with an AJAX request and then the message fades out. It's really important that you only think about JavaScript enhancements after the core functionality has been written, so that users who don't have JavaScript enabled can still use your plugin, and search engines can access any content your plugin provides.

    To do this, you need to create a JavaScript controller. In the dev/js directory of your plugin directory, create a file named "globalMessageDismiss.js" with the following contents:

    ;( function($, _, undefined){
    	"use strict";
    	
    	ips.controller.register('plugins.globalMessageDismiss', {
    	
    		initialize: function () {
    			this.on( document, 'click', '[data-action="dismiss"]', this.dismiss );
    		},
    		
    		dismiss: function (e) {
    			e.preventDefault();
    			var url = $( e.currentTarget ).attr('href');
    			var message = $(this.scope);
    			
    			ips.getAjax()(url).done(function(){
    				ips.utils.anim.go( 'fadeOut', message );
    			}).fail(function(){
    				window.location = url;
    			});
    		}
    
    	});
    }(jQuery, _));
    
    

    To explain what this is doing:

    • Everything other than the contents of the initialize and dismiss functions is required code for JavaScript controllers. The ips.controller.register line specifies the name of the controller.
    • When an element with a JavaScript controller attached to it is loaded (you'll attach it to the global message in a moment) the initialize function is ran - the best practice is to only set up event handlers here and handle events in other functions. An event is being set up here to fire when any elements matching the selector [data-action="dismiss"] (you'll add that attribute to the close button in a moment) are clicked, and it calls the dismiss function when this happens.
    • This dismiss function first prevents the event from performing it's default action (as a click will of course take the user to the target URL), then sets the variables needed (the URL that the button points to and the message box. It then sends an AJAX request to the URL that the button points to - if it succeeds, it fades out the box, and if it fails, it redirects the user to it just as if there was no JavaScript functionality.

    To make it actually work, you need to specify that you want your message to use this controller. In your template, add data-controller="plugins.globalMessageDismiss" to the div element, and data-action="dismiss" to the a element.


    For completeness, we should also adjust our action a little so that is aware of AJAX requests - while this is not strictly necessary, if it is not done, it is possible that if the redirect has to redirect to a screen which will display an error message, that the AJAX request will see this response and assume the request failed. Open up the action.php file you created in step 6 and change the redirect line to:

    		if ( \IPS\Request::i()->isAjax() )
    		{
    			\IPS\Output::i()->sendOutput( NULL, 200 );
    		}
    		else
    		{
    			\IPS\Output::i()->redirect( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : \IPS\Http\Url::internal( '' ) );
    		}
    
    

    Once this is done, closing your message will now fade out without a page reload.


    Step 8: Downloading

    Congratulations, you've just created your first plugin! You can download so that you can install it on other sites or distribute it through the IPS Marketplace from the developer center.

    For reference, a downloaded version of the hook, as well as a zip of the plugin directory for development are attached:


     Global Message.xml

     globalMessage.zip

    Global Message.xml

    globalMessage.zip



    User Feedback

    Recommended Comments

    Just to clarify for my own peace of mind (apologies in advance if questions sound stupid but am not into this side am more of a skinner so this is all new to me),

    1) if created a very simple theme hook (e.g. Global Message as per post at top) would this be exported with a theme and able to be used on another site when theme installed there ?

    2) Would theme hook get overwritten with any forum or other ips app updates ?

    3) If no to (2) above then if forum updates changed the point the hook looked for on where to insert it then hook would not work ?

    4) Would the template that has the theme hook be upgraded or would it not be upgraded and have to be upgraded manually (similar to 3.4.x theme templates that had manual edit in them, when run a diff report would show that templates with edits in them would have to be upgraded manually) ... hope that makes sense ?

    Share this comment


    Link to comment
    Share on other sites

    Just to clarify for my own peace of mind (apologies in advance if questions sound stupid but am not into this side am more of a skinner so this is all new to me),

    1) if created a very simple theme hook (e.g. Global Message as per post at top) would this be exported with a theme and able to be used on another site when theme installed there ?

    2) Would theme hook get overwritten with any forum or other ips app updates ?

    3) If no to (2) above then if forum updates changed the point the hook looked for on where to insert it then hook would not work ?

    4) Would the template that has the theme hook be upgraded or would it not be upgraded and have to be upgraded manually (similar to 3.4.x theme templates that had manual edit in them, when run a diff report would show that templates with edits in them would have to be upgraded manually) ... hope that makes sense ?

    ​1) No. You would need to distribute a plugin with the theme. Though that would be a strange thing to do - if you're creating a theme, you can just add a message where you want it in the theme's HTML code. Remember that plugins will affect every theme.

    2) Usually no.

    3) It's possible if we move elements around that hooks on those elements will need to be updated. It would depend on the nature of the change.

    4) If you have manually modified the HTML of a template, it will be the same as in 3 (not upgraded and a diff report would typically be done). If a template is being modified by a hook, it will be updated as normal. A template is not generally aware if it has a hook on it.

    Share this comment


    Link to comment
    Share on other sites

    ​1) No. You would need to distribute a plugin with the theme. Though that would be a strange thing to do - if you're creating a theme, you can just add a message where you want it in the theme's HTML code. Remember that plugins will affect every theme.

    2) Usually no.

    3) It's possible if we move elements around that hooks on those elements will need to be updated. It would depend on the nature of the change.

    4) If you have manually modified the HTML of a template, it will be the same as in 3 (not upgraded and a diff report would typically be done). If a template is being modified by a hook, it will be updated as normal. A template is not generally aware if it has a hook on it.

    ​Thanks for reply.

    Idea I had then would not be of any use as a hook then if (1) is no, shame really but there you go

    I understand would be a strange thing to do but I have my reasons for it that way instead of manually editing template directly.

    Share this comment


    Link to comment
    Share on other sites

    Hi! Love this tutorial! I just need to point out a little weirdness. After step 6 my site threw an error: "Use of undefined constant cookie - assumed 'cookie' ".

    I do believe the error references this line from step 6:

    {{if settings.globalMessage_content and !member.globalMessage_dismissed and !cookie.globalMessage_dismissed}}

    I then went to globalMessage.phtml and changed cookie to 'cookie'. After this it threw the error: "Use of undefined constant globalMessage_dismissed - assumed 'globalMessage_dismissed' ". Same story. I went to globalMessage.phtml and changed globalMessage_dismissed to 'globalMessage_dismissed' . Then there were no errors, and the basic functionality worked with a logged on user, but the cookie functionality did not work. This does not surprise me, since we have reduced the cookie logic to string concatenation. Has the template markup changed, or is this a quirk associated with localhost setups/older versions of php?

    Also:

    This is a new build with several bugfixes

    Global Message.xml

    globalMessage.zip

    ​The above links are dead, and could be very useful.

    Thanks!

    Edited by snugRugBug

    Share this comment


    Link to comment
    Share on other sites

    how about adding Furl? Like if something has a UCP setting, how would one be added in? instead of getting the long url, get something like /settings/<word you want>   similar to /settings/facebook, and adds it to the furl list?

    Share this comment


    Link to comment
    Share on other sites

×