Jump to content

Rikki

Members
  • Posts

    24,413
  • Joined

  • Last visited

  • Days Won

    84

 Content Type 

Downloads

Release Notes

IPS4 Guides

IPS4 Developer Documentation

Invision Community Blog

Development Blog

Deprecation Tracker

Providers Directory

Forums

Events

Store

Gallery

Everything posted by Rikki

  1. 1. Create the Email Template The content for emails is stored in the dev/email folder of the application which will send the email. For example, if you look in the applications/core/dev/email folder (you will need to be in developer mode to see that folder) you will see all the emails the core application sends. All emails sent from the IPS Community Suite are sent in two formats: HTML (for most email clients) and plain text (for older email clients or users who specifically do not want to see HTML emails). For each type of email that can be sent, there are two files: key.phtml (which contains the HTML version) and key.txt (which contains the plaintext version). Note that both types are sent to the user, and their individual email client will determine the most appropriate one to display. To send your own type of email, you will therefore need to create both of these. They key you use can be anything so long as it is unique. The first line of both files should be a tag like this: <ips:template parameters="$parameter1, $parameter2, $email" /> Later (in step 3) when you call the code to send your email you can pass whatever parameters you want which can then be used in your templates. A final parameter, $email, will also always be passed which contains the \IPS\Email object you're working with. Within the templates, you can include template logic and most template tags, however be careful: the email you're sending will probably be to a different user to who is logged in when the email is sent, so do not use anything which is dependent on the currently logged in member. most importantly, do not use {lang=""} template tag (which is always based on the currently logged in member). Instead, you can access $email->language which is an \IPS\Lang object for the correct language of the recipient. There's also a special tag, {dir}, that you can use in your template. This is either ltr or rtl depending on the recipient's language, and is used (most notably with <table>s) to ensure the email layout is appropriate for the language. Usage example: <td dir='{dir}' valign='middle' align='center' style="..."> When creating your HTML template, be aware that email clients use very different standards to browsers and many normal techniques cannot be used. We recommend you use a template from one of the IPS applications as your base, and you may want to use a tool such as Litmus. When creating your plaintext template, be aware that you cannot use any HTML tags, and whitespace is significant; that is, any whitespace in your template will be displayed in the email. 2. Add Subject Language Strings The system will automatically look for a language string with the key mailsub__<app>_<key> to use as the subject, so add that to your dev/lang.php file. Note that you can use the parameters you pass to the template - for example, you will notice in applications/core/dev/lang.php: 'mailsub__core_notification_new_likes' => '{$member->name|raw} liked your post', 3. Send the Email The actual code to send the email is very simple: \IPS\Email::buildFromTemplate( 'app', 'key', $params )->send( $member ); The send() method can be passed either an \IPS\Member object (this is best as the system will automatically customize the email for the user, including choosing the correct language) or an email address as a string (which you should only do if you need to send an email to unregistered users) or an array of either of these to send to multiple recipients. You can also pass second and third parameters for users to CC and BCC respectively. If you want to customize the content of the message depending on the member you can include variables in your template with the format *|key|* and then send like so: Template: <td> *|some_key|*<br> </td> PHP: \IPS\Email::buildFromTemplate( 'app', 'key', $params, TRUE )->mergeAndSend( array( 'user1@example.com' => array( 'some_key' => 'value for user 1', ), 'user2@example.com' => array( 'some_key' => 'value for user 2', ) ) );
  2. 1. Create an extension The first step is to create a Notifications extension. In the Developer Center for your application go to Extensions > core and click the "+" button on the "Notifications" row. A file will be created in your application's extensions/core/Notifications folder with a skeleton to get you started. Open that file and follow the instructions within it to add your notification types to the Notification Options screen and create the method(s) which will control what gets displayed when the user clicks on the notification icon in the header. 2. Create an Email Template You must create the email templates for what will get sent to users who have opted to receive emails for your notification. Follow the instructions from Steps 1-2 of the Emails guide. For your templates, use notification_key as the key (where key is the notification key you specify in getConfiguration()). For example: the notification type new_status (in the core application) uses the email templates notification_new_status which for it's subject uses the language string mailsub__core_notification_new_status. 3. Send the Notification The actual code to send the notification is very simple: $notification = new \IPS\Notification( \IPS\Application::load('app'), 'key', $item, $params ); $notification->recipients->attach( \IPS\Member::load(1) ); $notification->send(); $item Should be an object of the thing the notification is about, which is used to prevent the same notification being sent more than once or NULL if not applicable. If it is a content item (it doesn't have to be, but if it is) the notification will automatically remove the recipient if they don't have permission to read the item or are ignoring the user who posted it. $params Any parameters that you want to pass to the email template. You call $notification->recipients->attach() for each member you want to send the notification to, passing an \IPS\Member object.
  3. Notifications or Email? You have a number of options available for you for sending notifications to members: Following Members can follow other members, content containers (nodes like categories) and content items (like topics) to be notified about content that interests them. For information on how to implement this, see the Following sections for the Content Items and Comments documentation. Notifications Members can choose how to be notified about certain events (if they want to receive an inline notification, an email, both or neither) and you can send them notifications honoring those settings. For example, notifications are sent when someone posts on another user's profile. For information on how to implement this, see below. Email You can also manually send an email to a member. For information on how to implement this, see below. Though all 3 systems interact with each other, it is important not to confuse them: the follow system sends a notification which may be an email. You can also send a notification which may be an email, or you can just send an email. If you need to send a notification to a user, you should think carefully about whether a notification or an email is appropriate. Generally notifications are best for telling a user about something that has happened that they might be interested in (for example, notifications are sent when someone posts on another user's profile) and emails are best for things which are in response to a specific action, or require action from the user (for example, an email is sent when a product a user has purchased from Commerce is shipped).
  4. Last week we introduced you to a couple of key new improvements in IPS Community Suite 4.1.12, the new post preview and enhanced activity streams and search. However, this is a packed release, so I wanted to quickly review what else you can expect to find when it is released this week. Mentions 4.0 introduced mentions, and since then a frequently-requested feature is the ability to ignore notifications triggered by particular members. In 4.1.12, we enhanced the Ignore Users functionality to also allow you to block mention notifications. They will still be able to mention you in posts, but you will no longer be notified about it. Ratings As of 4.1.12, ratings will now display half-stars in order to be more accurate. Users will still rate whole stars out of 5 (or 10 if configured so), but the aggregated ratings displayed alongside content will be more fine-grained. Custom date formatting We have used built-in, automatic locale formats for dates since 4.0, but it became increasingly clear that this did not offer the flexibility that some community administrators desired. As a result, 4.1.12 re-introduces the ability to provide custom formats for dates. Bug fixes Amongst the handful of new features, there's over 400 other bug fixes and improvements that contribute towards the overall stability of the IPS Community Suite, as we start working towards the next major release, IPS Community Suite 4.2 which will be available later this year. Further fixes for stability in the 4.1 line will come before 4.2 is available. Please check our release notes to read more about other smaller changes and fixes in 4.1.12.
  5. HTML Templates Within your application's dev directory you'll find a html directory that will contain any templates you create to use in your application. There's three sub-directories: admin/ contains templates for any AdminCP interfaces front/ contains templates for user-facing, front-end interfaces global/ contains templates that are used by both admin and front-end areas Within each of this directories you should create sub-directories that group your templates into logical areas; typically, each module in your application will have a corresponding directory in both the admin and front template folders, although you're free to group them however you wish. Templates are created as files with the extension .phtml when in developer mode. When your application is built and installed by others, these template files get compiled. To use templates within your application code, you write: \IPS\Theme::i()->getTemplate( 'group', 'app', 'location' )->template_name( ... ); Where each value is: group The name of the folder you created within the admin/front/global folder. e.g. myModule app Your application key location The location of the group, i.e. one of admin, front or global template_name The name of the template, which is the filename without the .phtml extension. Any parameters your template requires can be passed in as normal, since template_name gets compiled to a function. The template file itself must contain a special header line as the very first line: <ips:template parameters="$example1=array(), $example2=FALSE" /> The parameters attribute simply defines the parameters this template expects. The syntax of this attribute is identical to defining parameters in a PHP function. If you don't require any parameters, you must still include the header and the parameters attribute, but simply leave it empty. CSS Files CSS files that your application needs should be created in the dev/css/ directory of your application. As with HTML templates, there's three subdirectories for admin CSS, front-end CSS, and shared CSS. Files you create here need to be loaded manually in your code, like so: \IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'filename.css', 'app', 'location' ) ); Where each value is: filename.css The name of your CSS file (with suffix) app Your application key location The location of your CSS file (one of admin, front or global) Where to load CSS files Since you load CSS files manually with the above code, where you place this code matters. If your CSS file is very specific to a particular page, you may want to only load it the controller method(s) that handle that page. However, more often, a CSS file will apply to a whole controller within your application. In this case, you can load your CSS file in the controller's execute() method. See the controller documentation for more information. If you need the CSS for all front-end or admin pages of your application, you can put the .css file directly in the /admin or /front directories, with the file named the same as your application. in this case, IPS4 will automatically bundle and load the CSS file for all pages of your application. For example, if our app key was my_app, we could create a CSS file at /dev/css/front/my_app.css, and it would be included in all front-end pages of our app. Javascript Files Javascript files are handled in a similar way to CSS files; that is, they are created as .js files in the appropriate directory, and then loaded on-demand in your code. There is one slight difference: because of the way javascript files are bundled, when include your javascript file you should specify the bundle name rather than the specific javascript file. Javascript files are bundled by location and group, and that forms the filename of the resulting bundle. For example, if we created javascript files in dev/js/front/mygroup/somefile.js, then the bundle name would be front_mygroup.js (location, underscore, groupname), and loaded as follows: \IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front_mygroup.js', 'app', 'location' ) ); Images and Other Resources Images (and other resources, such as fonts) should be placed in the /dev/resources directory, and as usual, there's /front, /admin and /global locations. To use a resource file in a template, you'd use: <img src='{resource="example.jpg" app="yourapp" location="front"}'> ...changing the filename, application key and location as appropriate.
  6. This article covers how to define versions for your application and use the upgrader to run queries to upgrade between versions. Coming from an IP.Board 3 app? If you have an application which was originally developed for IP.Board 3.x, you will need to do the following steps. It doesn't matter if you installed a fresh 4.0 install or upgraded from 3.x. Create a new application as normal in the AdminCP If you installed a fresh 4.x rather than upgraded, manually create the database tables in your SQL database. Under the Database Schema tab in the Developer Center for your application, import the database tables. It is important that you use the "Import From Database" tab as the other options will assume these are new tables. Under the Versions tab, add all of your previous versions and the queries/code needed to upgrade between those steps - this is discussed in detail below. There is an option to upload your old versions.xml file to import them quickly, but you will need to specify the upgrade for each version queries/code manually. How to define versions In the Developer Center for your application, there is a Versions tab which shows all the versions there has been for your application. You do NOT need to add your current (unreleased) version in here, as the application system automatically works with an "upg_working" version which you will convert to a new version when you build your application for release. When you build and release your application (version 1.0.0), you will specify the version information when building, and the upg_working special version will be cleared and ready for your next version. This is because while you're working on your application, the system will automatically add any changes you make to the database schema to the upgrade routine for the latest version you have defined. Database schema The Database Schema tab is where you define all of the tables that your application uses. When your application is installed for the first time, all of the tables you have defined are created. As you make changes to the database schema, the system will automatically add the appropriate queries to make those changes to the upgrade routine for the latest version. For example: say the current latest version of your application under the Versions tab is 1.0.0 and you have a table under the Database Schema tab. You release that version, and then start working on 1.0.1. Later, you add a column to the table under the Database Schema tab. When you release version 1.0.1, the system will automatically: For new installs: Just create the table as it is defined, including the added column For upgrades: Add the column You do not need to manually add a statement to add the column to your upgrade routine. Custom version queries and code Though the Database Schema system is good at automatically handling tables owned by your application, you may find you need to run other queries (for example UPDATE queries or queries to add columns to tables not owned by your application). This is done under the Versions tab. Simply click the "+" icon for the version that the query need to be ran for (for example, if the query needs to be ran when upgrading to 1.0.1, you would click the "+" button for version 1.0.1 - typically, however, you will simply add this to the current version you are working on which will be denoted as "upg_working" in the Versions list). You will notice there is a special "install" version which can be used to specify queries which should be ran on install. Since on a fresh install, those are the only queries that are ran, it may be necessary to add a query both to the "install" special version, and to the version you're working with. For example, if you're working on version 1.0.1 and decide you want to add a column to the core_groups table that you didn't add in version 1.0.1, you would need to specify the query in both the 1.0.1 version (for people upgrading from 1.0.0) and the special "install" version (for new installs). If you need to run code which cannot be expressed as a single query, you can also define custom code. To do this, click the "</>" button for the version that needs custom code. This will write a file to the applications/<your_app>/setup/<version>/ directory with a skeleton to get you started. Simply open that file and follow the instructions within it to add your code. You can also do this for the special "install" version. If you are coming from an IP.Board 3 app, you can manually create a queries.php file in the applications/<your_app>/setup/<version>/ directory with the contents of the old mysql_updates.php file (setting an $SQL variable). Because this functionality is only to accommodate legacy upgrades, the file must be created manually and cannot be done from the developer center. If there is only a small number of queries, it may be easier to rewrite them as a call to an \IPS\Db::i() method and add them using the normal method (clicking "+" for the version in the developer center). Uninstall code The system will automatically delete any tables defined by your application's Database Schema when uninstalling. If you need to run code in addition to this, you can do this with the Uninstall extension. In the Developer Center for your application, under the Extensions tab, click the "+" button for core > Uninstall and create an extension (the name of it isn't important). This will write a file to the applications/<your_app>/extensions/core/Uninstall/ directory with a skeleton to get you started. Simply open that file and follow the instructions within it to add your code.
  7. 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. 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. 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. 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! 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. Plugin CSS files are bundled together with IPS4's own CSS so that they are available on every page. Step 4: Settings & Language Strings Now you have a global message - but currently there's no way to customize 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. Language strings in IPS4 are simple key/value pairs, though the language strings can use more advanced features such as string replacements and pluralization. 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. Step 5: Making Database Changes Note: If you are planning on submitting your plugin to the Marketplace, changes to stock Invision Community tables are not allowed. You may want to consider creating an application instead which offers much more robust management of database tables. 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 run 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 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 honor 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: Adding 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 run. 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. By redirecting to the original URL on fail, the user will be able to see the actual error that occurred. 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 Your Plugin 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
  8. Invision Community can periodically check for updates to your application, plugin or theme and present a message in the AdminCP if an update is available. If you are submitting your resource to the Invision Community Marketplace, we will automatically handle the update notifications when you upload new versions of your resource. When creating your application, plugin, language (4.5 or newer) or theme you can specify a URL that the Invision Community will use to check whether an update is available. "Update URL" field when creating a plugin Invision Community will send a request to the specified URL periodically with a "version" parameter specifying the current version ID number. For example, if you specify the URL as: https://www.example.com/myapp/check.php And someone is using a version of your application with the version ID "10002", then the request will be sent to: https://www.example.com/myapp/check.php?version=10002 Requests sent to this URL will also include an 'ips_version' parameter, this is the long version ID of the current Invision Community install sending the request. This URL should return with a JSON-encoded object with the following properties: version The human-readable version number (e.g. "2.0.0") of the latest version available longversion The version ID number (e.g. "20000") of the latest version available released The unix timestamp of the date that version was released updateurl The URL where the administrator can download the update For example, you might return something like this: { "version": "2.0.0", "longversion": 20000, "released": 1423841958, "updateurl": "https://www.example.com/myapp/download" } For a live example, the URL for checking for updates to Invision Community itself is: https://remoteservices.invisionpower.com/updateCheck You can visit this URL to see what is returned for Invision Community
  9. IPS Community Suite supports multiple languages and translation tools such as the Visual Language Editor. Any text your plugin uses should be abstracted to language strings so that your plugin also supports these features. Language strings are defined as key/value pairs. To add a language string, just open the /plugins/<your plugin>/dev/lang.php file and add an element to the array. Then to use the language string, use this in controllers: \IPS\Member::loggedIn()->language()->get( 'KEY' ); Or this in templates: {lang="KEY"} If your plugin creates custom language strings by utilising the IPS\Lang::saveCustom() method, you are allowed to associate them to the core application, but please use an unique key prefix and make sure that all this strings are removed while the plugin uninstallation.
  10. Plugins support external resources such as images, javascript files, CSS and templates, should you need to use them. HTML templates HTML files will become templates within the core -> global -> plugins group. In other words, you will get the content of the HTML files you create using this code in controllers, where filename is the name of your template file: \IPS\Theme::i()->getTemplate( 'plugins', 'core', 'global' )->filename( ... ); Or the following code from within other templates: {template="filename" group="plugins" location="global" app="core"} You should create your files using the extension ".phtml". The first line of the file should be the following tag: <ips:template parameters="$example1, $example2" /> Replace $example1, $example2 with the names of whatever variables you intend to pass in. It is fine to have no parameters; in that case, the parameters attribute should simply be an empty string. After that opening line, the remainder of the file should just be what you want the template contents to be. You can use template logic and template tags. Note that if any of the code in your template causes an exception to be thrown (which could happen if you're using particularly complicated template logic/tags without proper try/catch statements) the contents of the template will be ignored and will return an empty string. CSS/Javascript files Any CSS and JavaScript files you create will be compiled with the rest of the CSS and JavaScript automatically, so you do not need to do anything other than create the files. Be sure you create the files, with file extensions, in the appropriate directory within your plugin's own directory. Images When your plugin is run, images will be placed in core -> global, in a folder called plugins. In other words, you will render the images you place in the /resources directory by using this code in templates: <img src='{resource="plugins/example.jpg" app="core" location="global"}'> You must put all images directly in the /resources directory, do not create subdirectories.
  11. Installs To run code such as database changes when your plugin is installed, open the /plugins/<your plugin>/dev/setup/install.php file and follow the instructions given by the doc-comments. Note: You must add an initial version in the developer center in order for the install.php installation file to be executed. Please don't use any official application prefixes like cms_ core_ forums_ or blog_ to name your custom database tables, use some unique names to avoid any further confusion. Upgrades Plugins support version management. Whenever you release a new version of your plugin you should add a new version in the Plugin Developer Center. When you add a version, you must specify an ID number to represent it (e.g. "10000", "10001", etc.) as well as a human-readable string (e.g. "1.0.0", "1.0.1", etc.). There is no particular standard to how the ID number should be formatted other than it must increase the newer the version. The human-readable string should be in the format "x.y.z". For each version, a file will be added to the /plugins/<your plugin>/dev/setup folder where you can specify code that will be ran when upgrading to that version (see the doc-comments within them for more details). It is important to note that at install, only install will be ran, so when you make a change you will usually need to add it both to your installer, and the appropriate upgrade file for that version. Uninstalling To run code such as database changes when your plugin is uninstalled, create the /plugins/<your plugin>/uninstall.php file and include all required code which needs to be run to e.g. remove additional database tables and columns.
  12. Tasks are scripts which run at specific intervals. They are useful for performing any maintenance that must be ran on a regular basis. It is important to note however, that they cannot be relied on - though IPS Community Suite provides a way for administrators to run tasks using a cron, this may not be enabled, in which case tasks can only be triggered when users are active on the site - if the site is inactive at the time your task is scheduled to run, it will run at the soonest opportunity after. You can create tasks in the Plugin Developer Center. When you create a task, a file will automatically be created in the /plugins/<your plugin>/tasks directory for your task. The doc-comments in this file explain how to implement the task. See the complete Tasks guide for more information on writing your tasks.
  13. Much like full applications, plugins can have system settings that allow administrators to configure it. For example, you might have a setting that toggles some part of your functionality on and off, or fields that can accept some authentication values used by some other system. If your plugin requires some data or preferences to be set by the administrator, settings are the ideal way do implement it. Allocating space in IPS\Settings \IPS\Settings is a repository for scalar key/value data pairs - it is shared amongst all applications and plugins and is accessible from anywhere as a Singleton, there are also shortcuts to access it's contents within templates. Its primary intention is for storing settings the administrator provides in the Admin CP. In order to use this, you must enter the keys you want to be allocated to your plugin and the default values in the Plugin Developer Center. The keys are not namespaced, so it is recommended that you choose keys that are unlikely to be used by other applications or plugins. Creating a settings page Your plugin is allowed to have one settings page which will be displayed with the administrator clicks the "edit" button next to your plugin. In most cases, this will be just a regular form using the form helper, however, though the form helper is very flexible (it can handle tabs, sidebars and a wide variety of input types), you are not restricted to this and can display your settings however you like. In the /plugins/<your plugin> directory, you'll find a file called "settings.rename.php" - rename this to "settings.php" and this will be the code that runs when the user accesses your settings page. The example file is written with the form helper in mind, and to use that, all you need to do is add additional input fields. If you want to use a custom interface though, you just need to return either a string (with the content to display) or TRUE to dismiss the settings page. Your code will be eval'd and so you should not include an opening <?php tag - one is included in the example file so that syntax highlighting will work in most code editors, however it is commented out and should remain so.
  14. Rikki

    Theme hooks

    Theme hooks allow you to modify the HTML templates. The HTML templates within the IPS Community Suite are structured into groups, locations and applications. Each application will have at least 3 locations: front (templates used for the front-end) admin (templates used for the Admin CP) global (templates which are used for both) Though some applications may have more for specific purposes (for example, the core application has another location for installer/upgrader called setup). Each location can contain any number of groups - groups are generic collections of templates, for example, the core application has one group (in the front location) called messaging which contains all the templates for composing and viewing personal conversations. One Theme Hook acts on one template group. You can create Theme Hooks in the Plugin Developer Center. Once created there are two ways to use Theme Hooks - each of which work differently and so will be appropriate for different circumstances. CSS Selectors Editing your Theme Hook in the Plugin Developer Center will display a panel showing all of the templates in that group: Selecting any template will bring up a tabbed interface showing the modifications your Theme Hook is making to that template, and allow to create more: To make a modification to the selected template, you will provide a CSS selector - you can use any of the selectors supported by jQuery (it's worth mentioning that jQuery isn't involved in making your Theme Hook work, it is simply that the supported selectors are the same). The easiest way to choose a selector is with the interactive "Select Element" feature, which will launch a model displaying the template allowing you to simply click on the element you want to use: It is important to note that when using the "Select Element" feature, it will use the most specific CSS selector it can for the element selected, however, that selector may also match other elements so you may need to adjust it. When providing the content, in addition to the variables that are available to the template (which are shown next to the editor) you can also use any variables available at that point in the template (for example, if you're inserting code within a foreach loop, you can use the variables created by it). You can also use template logic and template tags. PHP Mode Under the hood, each template group is compiled into a PHP class, with a method for each template. You can extend this class by manually editing the file which will have been created in /plugins/<your plugin>/hooks. It is important to note that when using this mode, you are overloading the compiled template group so the return value will be the HTML that will be displayed, without any template logic or template tags. All of the same considerations as for Code Hooks (see "Important things to remember when creating code hooks") also apply here.
  15. Rikki

    Code hooks

    Code Hooks allow you to extend any class within the IPS Community Suite or an application installed. You can create Code Hooks in the Plugin Developer Center. You'll notice that throughout the code, classes are defined beginning with an underscore, but called without. For example, in /system/Member/Member.php, the declaration for is: namespace IPS; class _Member extends \IPS\Patterns\ActiveRecord Yet throughout the code, \IPS\Member is called rather than \IPS\_Member. This is a technicality in how code hooks are facilitated through a strategy known as Monkey Patching. When creating your code hook, specify the class you want to extend without the underscore. When you're actually editing the code, it will say "extends _HOOK_CLASS_" - this is because the actual class it will extend will vary if more than one hook is overloading the same class. You can edit code hooks either in the Plugin Developer Center or by manually editing the file which will have been created in /plugins/<your plugin>/hooks. Using the Plugin Developer Center has the benefit that all the properties and methods of the class you're extending (and classes that class extends) are shown in the sidebar, and clicking on any one will insert the declaration with the doc-comment into the editor. Important things to remember when creating code hooks: The first line in the class that is created is //<?php - this is so if you want to edit the file in your own editor that syntax highlighting works properly, however the <?php tag should not be used uncommented. Your hook will specify that it extends _HOOK_CLASS_ - though this is syntactically wrong, this should not be changed. The system will automatically change it to the correct class which may vary if there is more than one hook on a single class. If your code causes a PHP error or throws a RuntimeException, your hook will be ignored and the system will revert to the default class. If you need to deliberately throw an exception, throw an object of an appropriate exception class. When overriding a method, you MUST call the parent method so that ultimately you are inserting your code at the beginning or end of the method. This is necessary for allowing hooks on the same method to work. When overriding a method, you MUST NOT copy code from the original method into your hook. This is necessary to ensure that your hook does not interfere with any bug fixes or changes made to the original class in future versions. Also, it is against the terms of the IPS Community Suite license to distribute any code within it.
  16. Plugins provide a way for developers to modify or extend the behavior of applications within the IPS Community Suite.This guide will cover in detail the different features available within Plugins. Many of the features describes are accessed from the Plugin Developer Center - to access this, first place IPS Community Suite 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. The purpose of plugins Plugins differ from applications in that they are designed to extend existing applications, and the features available for use by plugins reflect that. There are code hooks and theme hooks, which allow you to piggyback off of code that exists in other applications and manipulate the output - great for adding new features. If you find yourself adding entirely new functionality that isn't a subset of something an existing application does, you may want to consider building an application of your own instead. Distributing plugins While in developer mode, plugins are created as files on the filesystem, and this guide will cover the available features and how to use them. When your plugin is ready for distribution, IPS4 will compile it as a single XML file that can be shared and installed by others, for example in the IPS Marketplace. Simply download the XML file from the Plugin Developer Center.
  17. If you are using IPS Community Suite as a master application and wish to extend or change its functionality, you can easily do so without having to edit any PHP files, ensuring your customizations are retained through future upgrades. The master IPS Connect gateway with the IPS Community Suite is found at /applications/core/interface/ipsconnect/ipsconnect.php. You can create a new file in this same location called "custom.php" with a class inside this file called ipsConnect_custom, extending ipsConnect. This file will be automatically loaded if it exists, and the ipsConnect_custom class will be instantiated instead of ipsConnect. From there, you can override any methods you need to. As an extension to this, the following methods can be defined which will be called automatically: _postCrossLogin(): Because cross login requests result in redirection instead of returning a response to output in JSON format, this method is supported which allows you to perform any custom actions you require before the redirection occurs. _postCrossLogout(): Because logout requests result in redirection instead of returning a response to output in JSON format, this method is supported which allows you to perform any custom actions you require before the redirection occurs. Example: <?php // My custom.php file class ipsConnect_custom extends ipsConnect { public function register() { $result = parent::register(); // A user just registered, do something custom now return $result; } protected function _postCrossLogin() { // Do something before the request is redirected to a different site } protected function _postCrossLogout() { // Do something before the request is redirected to a different site } }
  18. Master IPS Connect applications will need to accept the following requests and respond accordingly with the response parameters outlined for each request. It will also need to propagate the requests to each slave in its network, differentiating the requests with a "slaveCall=1" request parameter. Slave IPS Connect applications will need to call the following API endpoints against the master IPS Connect installation it is communicating with, and additionally accept the same API request calls (differentiated with a slaveCall=1) in order to accept and make changes to the local database when changes are made at a remote website in the Connect network. The following GET request parameters will always be included: do: This is the action to perform, as outlined below. key: This is the secure key and should be validated to ensure it matches the secure key supplied to the slave application. url: This is the URL to the slave application. Other GET request parameters will be sent and will vary depending upon the request. All requests from the master installation to slave installations will also include slaveCall: Always set to '1', this allows the slave application to know that the request is coming from a master application and is intended to result in the local database being updated. Important note: If 'id' is passed in the request to make a change to a specific user account, the 'key' value will be an MD5 hash of the master application's key concatenated with the id. For instance: $key = md5( $masterKey . $id ); All responses will include a 'status' key in the JSON response. Some responses may include additional information. You should verify the 'status' response is SUCCESS to ensure the action completed successfully. Invalid requests will have a status of INVALID_ACTION. Slave applications that no longer wish to be a part of the network (i.e. if IPS Connect is disabled at this installation) should respond with a status of DISABLED. Example: print json_encode( array( 'status' => 'SUCCESS' ) ); exit; Method: verifySettings This method is intended to allow a slave application to verify the settings of the master (i.e. when the master key is first provided) and to "register" with the master installation. This allows the master installation to propagate requests to slave applications later. GET parameters: ourKey: [Required] This is a unique key associated with the slave Response status codes: SUCCESS Response parameters: None Method: fetchSalt Call this method in order to fetch a user's password salt - necessary for allowing the local application to hash the user's credentials properly before sending them to the master. GET parameters: idType: [Required] What type of ID is being passed (a value of 1 indicates the id is a display name, a value of 2 indicates the id is an email address and a value of 3 indicates the value could be either a display name OR an email address) id: [Required] The user ID Response status codes: SUCCESS REQUEST_MISSING_DATA: This indicates either idType or id was not provided ACCOUNT_NOT_FOUND: No account was found based on the supplied value Response parameters: pass_salt: The salt applied to the password when hashing it Method: login This method authenticates a user and logs the user into all applications on the IPS Connect network. GET parameters: idType: [Required] What type of ID is being passed (a value of 1 indicates the id is a display name, a value of 2 indicates the id is an email address and a value of 3 indicates the value could be either a display name OR an email address) id: [Required] The user ID password: [Required] The encrypted password NOTE: The 'password' parameter must be encrypted in the same manner as the IPS Community Suite. A request should first be sent to fetch the user's salt (see fetchSalt above), and then the password should be hashed in the following manner: /* $password is the raw password and $salt is the salt returned from fetchSalt */ crypt( $password, '$2a$13$' . $salt ); $2a$13$ refers to the salt prefix and a pre-determined cost factor that should not be altered. Response status codes: SUCCESS REQUEST_MISSING_DATA: This indicates either idType or id was not provided WRONG_AUTH: This indicates that the provided credentials could not be authenticated. This may mean that no account exists with the id provided or that the password is not valid. Response parameters: connect_status: VALIDATING if the account is still validating or SUCCESS otherwise email: The member's email address name: The member's display name connect_id: The member's unique integer ID on the master installation connect_revalidate_url: If the member is VALIDATING, the URL that any slave application's should send the user to in order to complete their validation Method: crossLogin When a user logs in to a slave application successfully, they will be redirected to the crossLogin method of the master application in order to be logged in to it and all other slave applications on the network. This is necessary to work around cross-domain cookie restrictions. The master install will need to redirect the user to each slave's crossLogin method, and will also need to log the user in to the master application, before returning the user to the originating URL (the original slave application the user logged in to). GET parameters: id: The member's unique integer ID on the master installation returnTo: a URL to return the user to once the user has been logged on. NOTE: When the master application redirects the user to slave applications to log the user on, the returnTo URL should be compared to the url parameter in order to ensure the user is not sent to the originating slave. When sending the user to another slave in the network, the returnTo parameter should be set to the master URL. Further, the master application should set a 'slaveCall' parameter to 1 when calling slave applications to prevent them from performing extra work (this allows slave applications to know that the request is from the master and to perform specific duties). Response status codes: None, the user will be redirected to the returnTo URL Response parameters: None Method: logout API calls to the logout method are designed to log the user out of the master application as well as all of the slave installations. GET parameters: returnTo: The URL to return the user to once they have been logged out id: The member's unique integer ID on the master installation NOTE: Much like the crossLogin method, the logout method should redirect the user to all slave applications to log the user out and then log the user out of the master application before returning the user to the originating installation. slaveCall is passed in the URL when the master calls slave applications to differentiate requests between master and slave applications. Response status codes: None, the user will be redirected to the returnTo URL Response parameters: None Method: register Register the user on all installations in the Connect network GET parameters: name: The member's name email: The member's email address pass_hash: The member's password hash pass_salt: The member's password salt revalidateUrl: The URL to send the user to if they are validating and attempt to login to any other site in the connect network NOTE: The 'pass_hash' parameter must be encrypted in the same manner as the IPS Community Suite. The password should be hashed in the following manner: /* $password is the raw password and $salt is the salt returned from fetchSalt */ crypt( $password, '$2a$13$' . $salt ); Response status codes: SUCCESS REQUEST_MISSING_DATA: This indicates email, name, pass_salt or pass_hash was not passed Response parameters: connect_id: The member's unique integer ID on the connect network Method: validate Call this method in order to mark a user's account as validated. If a user account is marked as awaiting validation and the user validates, this should be called to ensure the user account is marked as validated across the entire network. GET parameters: id: [Required] The unique user ID of the user account Response status codes: SUCCESS Response parameters: None Method: delete Call this method in order to delete a user account. THERE IS NO UNDOING THIS ACTION. GET parameters: id: [Required] The unique user ID of the user account Response status codes: SUCCESS Response parameters: None Method: ban Call this method in order to ban or unban a user account GET parameters: id: [Required] The unique user ID of the user account status: [Required] A value of 1 will ban the user account while a value of 0 will unban the user account Response status codes: SUCCESS Response parameters: None Method: merge Call this method in order to merge two distinct user accounts into one. THERE IS NO UNDOING THIS ACTION. GET parameters: id: [Required] The unique user ID of the account you wish to keep remote: [Required] The unique user ID of the account you wish to remove Response status codes: SUCCESS Response parameters: None Method: checkEmail Call this method in order to check if an email exists at the master application. This can be useful to prevent a user who has already registered elsewhere on the Connect network from registering again on a local site, when they should instead login. GET parameters: email: [Required] The email address to check Response status codes: SUCCESS Response parameters: used: 1 if the email address is in use or 0 if not Method: checkName Call this method in order to check if a username exists at the master application. This can be useful to prevent a user who has already registered elsewhere on the Connect network from registering again on a local site, when they should instead login. It is not necessary to enforce uniqueness of display names in your application if your application has a need to allow multiple user accounts with the same display name to exist, however you should never allow logging in by 'display name' if this is the case. GET parameters: name: [Required] The name to check Response status codes: SUCCESS Response parameters: used: 1 if the name is in use or 0 if not Method: changeEmail This method is called when an existing user's email address should be updated to a new value. GET parameters: email: [Required] The new email address to use id: [Required] Unique user ID provided by the master application to a previous login or registration call Response status codes: SUCCESS REQUEST_MISSING_DATA: The new email address to use was not supplied EMAIL_IN_USE: The new email address is already being used by another account Response parameters: None Method: changePassword This method is called when a user has updated their password GET parameters: pass_salt: [Required] Password salt pass_hash: [Required] Password hash id: [Required] Unique user ID provided by the master application to a previous login or registration call NOTE: The 'pass_hash' parameter must be encrypted in the same manner as the IPS Community Suite. The password should be hashed in the following manner: /* $password is the raw password and $salt is the salt to be passed with pass_salt */ crypt( $password, '$2a$13$' . $salt ); Response status codes: SUCCESS REQUEST_MISSING_DATA: The password salt or password hash was not supplied Response parameters: None Method: changeName This method is called when an existing user has changed their display name at a local installation TIP: You can disable username changes from propagating to all sites within a network. This can be useful when you want to share login credentials amongst all of your sites, but want user accounts to otherwise appear to be separate. To do this with the Community Suite you must create a file called constants.php in your root directory (where index.php is), or edit the existing one if it already exists. Paste the following code into the constants.php file (if you are editing an existing file, omit the opening <?php tag): <?php define( 'CONNECT_NOSYNC_NAMES', TRUE ); If this is done on a slave IPS Community Suite application, that slave (only) will ignore username change requests and will not send username changes to the master application. If the above constant is set on a master IPS Community Suite application, username change requests will not be propagated to anyslaves in the network. You should carefully consider your intentions if you decide to make the change above. GET parameters: name: [Required] The new name to use id: [Required] Unique user ID provided by the master application to a previous login or registration call Response status codes: SUCCESS REQUEST_MISSING_DATA: The new name to use was not supplied USERNAME_IN_USE: The new name is already being used by another account Response parameters: None
  19. Slave IPS Connect applications will need to send requests for specific actions via an HTTP REST API call to the IPS Connect master installation. This means that you will need to obtain the URL and API key from your IPS Connect master installation and supply this to your slave application, and then when a user attempts to perform one of the actions outlined at the top of this article your login routines should make API calls to the master application in order to perform and validate those actions. Be aware: Some requests result in redirecting the user to one or more websites before returning the user to a URL you supply. See the crossLogin method outlined below for an example. For most requests, however, you will receive a JSON response which will need to be decoded and examined. If you issue a login request, for instance, you will receive a response with a status code and then some additional information such as the email address, username and unique ID on the network. Most slave applications will then create a local user record with this data if the user account does not exist, or update the local user record if the account does exist (using the unique ID to match up user accounts if possible, or falling back to matching accounts by email address). This is not, however, a requirement. Note: After making a login request to the master installation, an IPS Connect slave should then call the crossLogin request at the master installation in order to ensure the user is logged in to all websites in the IPS Connect network. This method will result in the user being redirected to all installations in the network, and then finally back to a URL supplied by the local slave application. All account changes must be sent to the master installation to ensure data consistency across the network. If a user changes their email address and this account change is not sent to the master application, for instance, the user will no longer be able to login correctly on other sites in the network. Additionally, all slave applications must accept the same requests and respond accordingly just as the master would, as the master application must propagate requests to each slave in the network. For instance, consider the following work flow: User changes their email address on SLAVE 1. SLAVE 1 sends this request to the MASTER. The MASTER, in turn, sends the request to SLAVE 2 and then to SLAVE 3. In this case, even though SLAVE 2 and SLAVE 3 are slave applications, they must accept the same requests the master accepted in order to allow them to update their local databases.
  20. Your master IPS Connect application will be required to accept API calls from slave IPS Connect applications and respond to them accordingly, and to then propagate changes from those calls to any other slaves in the network. You must supply a unique key for your master application (which can be anything you want but must be unique to this installation) and the URL to the gateway API file that you will create to all slave applications. The master application secret key should be kept private and never be made public. You must create a gateway file that slave applications will call to. These applications will send requests to your gateway file and will expect a response in JSON format to those requests (except for certain requests which result in redirection as outlined later in this document). Your master application will need to implement the API requests outlined below, accepting the expected GET parameters and responding with the outlined Response status code and parameters. Finally, the master application will also need to propagate the requests to all slave applications in its network (and thus needs to maintain a database of all slave applications that have connected with it). The IPS Community Suite when acting as a master IPS connect application implements a queue system for requests to slave applications. If a request that should be propagated to a slave fails, it is inserted into a queue and reattempted at a later time. Any repeated failures are reported to the administrator via an ACP dashboard plugin. The requests will be automatically reattempted, or you can manually reattempt the requests from the dashboard. While this system is not necessary in a master application, it can help to ensure data consistency across an IPS Connect network. If the master application ignores failures to a slave application, then email changes, name changes, password changes and more may not be propagated if a temporary error occurs. Note that as the IPS Connect API is a public API, while the master key is required to communicate with it, you may wish to implement brute force protection against multiple subsequent login requests. This can help protect against brute force attempts made against slave applications that do not natively support such protection. If a slave application no longer wishes to communicate with a master application, it should respond with a status of DISABLED when a request is propagated to it. If this happens, the master application should disassociate the slave application and cease sending further requests to it.
  21. We are currently beta testing our next release, 4.1.12, which contains hundreds of bug fixes, dozens of improvements, as well as a handful of new features. In this post, I want to cover some improvements we've made to two key areas: activity streams and search. Activity Streams The first improvement we made is to change how the expanded/condensed toggles are displayed to improve their clarity. From studying the feedback from administrators and users, we discovered that many people did not realize the view could be changed. To improve this, the toggles now explicitly say 'Condensed' and 'Expanded', making it much clearer how the view can be toggled to your own preference. More clearly marked expand and condense options for Activity Streams Next, a common point of feedback about Activity Streams is that clicking a result and then hitting the Back button in the browser means you are put back at the first batch of results, losing your place in the stream. In 4.1.12 we improved this so that clicking Back will load the last batch of results you were viewing, enabling you to continue browsing from whence you left off. Finally, in the Content Types menu we added an Apply button. We discovered that users were not always sure how to save the selection of content types they had made (which automatically happened when that menu was closed). To alleviate this, the new Apply button will save the selection and close the menu, updating the stream results in real-time as expected. You can still simply click out of the menu to apply changes as well. The new Apply button in the Content Types menu Search While overall improvements to search (specifically the algorithms to match and return the results) are a matter of ongoing research and refinement which we will improve in the 4.2 series and beyond, 4.1.12 sees one small improvement to the options available to users. While you have always been able to search within a particular forum, category etc. while browsing that area, you were not able to retroactively filter into particular areas after performing a more general search. IPS Community Suite 4.1.12 adds this ability to the interface, allowing you to get more specific results from a particular area of the community. Version 4.1.12 is currently in beta testing and should be released in the next two weeks.
  22. We are currently beta testing our next release, 4.1.12, which contains hundreds of bug fixes, dozens of improvements, as well as a handful of new features. I wanted to introduce one of those new features: post preview. Long-time users of our software will know that a post preview function was a standard feature, but we took the decision to not include it in the initial IPS4 release. It had a couple of drawbacks: it only applied to certain pages, such as topic view - other WYSIWYG editors simply didn't get a preview the workflow wasn't very good for modern web apps, requiring a round-trip to the server and a full page refresh When IPS4 was released, we felt that the built-in rendering of the editor was a sufficient preview of how the end result would appear. However, while analyzing ongoing customer and user feedback for IPS4 in its first year of release, we have seen that a preview still has a use. There are some circumstances when a true WYSIWYG experience is just not possible such as using more advanced formatting (like LaTeX) or when admins create certain custom editor plugins. As a result, we rethought post preview. We wanted to ensure that all editors could be previewed, and that it didn't have a clunky workflow. In addition, since IPS4 uses a responsive theme, we wanted to give users the opportunity to preview how their post would look on different devices. Here's the result, and what will be available in 4.1.12: Post preview in IPS Community Suite 4.1.12 The preview is shown by clicking a new button on the toolbar (meaning it can be moved, removed, etc. just like the other default buttons). When the preview loads, the toolbar allows the user to resize it to different device sizes. If they are on desktop, they can also view it at tablet at phone sizes; on a tablet, it can also be viewed at phone size. So now we not only show a true preview of what content will look like when posted, but we also allow you to preview how it will look on other devices. Of course that preview is just a best-guess since different devices have different window sizes but it does give you an idea. We hope this reimagining of an old feature for a more modern web will please end-users and make posting content a more accurate process. Stay tuned for more updates on what's included in 4.1.12! Version 4.1.12 is currently in beta testing and should be released in the next two weeks.
  23. Recently, we had a post in our pre-sales forum that asked how to achieve a few different things with Pages. One of the questions asked was if it was possible to show topics from a particular forum in each database record. While Pages can create a topic for each record for you, there's no way to associate an entire forum with a record. In my reply, I indicate that you'd need to have a forum ID stored with each record in a custom field, and then use PHP to interact with our API to pull the topic list. As it turns out, however, there's an easier way that I discovered after some experimentation. In hindsight it's obvious, but I want to share it here because it could open up some other interesting possibilities with some creative uses. Setting up blocks The first thing we need to do is create our blocks. We're going to create a block for each of our forums. You can set whatever parameters you want here, but the important thing is that they're named consistently using the forum ID. So, for my forum ID 2, I've named the block forum_2. This will allow us to include our blocks later. Creating one of the blocks we'll need Adding the field Next we'll need to create a field in our Pages database that will be used to set the forum ID that is going to show in each record. For simplicity, I'm creating a Number field and I'll enter the forum ID manually, but if you wanted to go further, you could create a Select Box field, with the key being each forum and the value being the name. This would give you a friendlier input from which to select the forum for each record. Here, though, I've just created the Number field, and named it Forum ID. Setting up the database field Using the field formatter to show the correct block Finally, we'll use the Field Formatting options to show the correct block based on the forum ID entered for each record. On the Display Options tab, I'm going to hide the field from the listing template, but show it on the display template. I've selected Custom as the format, then entered this format: {{if $formValue}} {block="forum_{$formValue}"} {{endif}} That's it - that's all you need for this to work. It's very simple. All we're doing is passing the $formValue of the field (which is the raw value) into the {block} tag as a variable, so that the block that is rendered depends on this value. As long as a block exists with the correct key, it'll be shown in the display view: End result, with the correct block pulled in based on the ID we provided to the record Going further So, given that we know we can use variables in block names to pull in different content (providing the block has been created ahead of time), what other possibilities are there? For starters, we aren't just restricted to using field formatters. Instead, we could use blocks directly in the database templates, using some of the data available there. Here's one idea - if you have just a few staff members posting records, you could create a block for each staff member that lists their recent posts, status updates, etc. In your database template, you could include the correct block by doing this: {block="content_for_{$record->author()->member_id}"} I hope this relatively simple approach gives you some ideas for more creative ways to use blocks. If you have any suggestions for other ways to use this approach, please let us know in the comments!
  24. Occasionally you'll want to style a specific element on a specific page of your community - maybe you want to change how topic titles are shown inside a topic, or do something specific to the styles used in activity streams, without also altering other screens where the same elements are used. Your first instinct might be to open the template editor and add some custom classnames so you can style them. This would certainly work, but the downside is your template is now customized, so any future IPS4 updates would leave the template out of date. Not ideal by any means. Instead, you can use some helpful attributes that IPS4 adds to the body element, and then build a CSS selector around them. There's four attributes, and they always reference the current page the user is on: data-pageApp - The application key (e.g. core, forums, cms etc.) data-pageModule - The current module with the application (e.g. pages) data-pageController - The current controller within the module (e.g. topic, page etc.) data-pageLocation - Either admin or front. So let's say we want to change how the .ipsPageHeader element looks within topic view. Our selector would look like this: body[data-pageapp="forums"][data-pagemodule="forums"][data-pagecontroller="topic"] .ipsPageHeader { ...your styles } If you don't want to be that specific, you can just use the attributes you need. For example, if you want to change all .ipsPageHeader styles in the Forums app, you'd do: body[data-pageapp="forums"] .ipsPageHeader { ...your styles } Tip: If you don't know the correct app/module/controller for the page you're on, you can find out by visiting the page and then viewing the page source. You'll see these attributes in the body tag near the top. And as always, be sure you add your CSS to custom.css to keep your upgrades easy This theme tip is taken from our guides section.
  25. We frequently encourage people to use custom CSS files when designing their themes. The reason for this is simple: it makes upgrading your site much easier because IPS4 can apply any changes to its own CSS files, and will leave your custom CSS files untouched. If instead you made edits to IPS4's CSS directly, it wouldn't be able to upgrade them automatically, which means more work for you, and a potentially broken UI on each upgrade. Something that's not quite as common, but that we still strongly suggest, is using custom template bits as much as possible. The most common template you'd edit is globalTemplate, perhaps to include some extra resources in the <head>, a custom header, and maybe some footer pieces. The usual approach would be to simply add all of that custom HTML directly into globalTemplate, but my recommendation is that you instead create each piece as a custom template bit, and then include it. With templates, it's not quite as much of a clear-cut benefit as with CSS; you'll still need to modify the original template in order to include your custom pieces of course. But there's still good reasons for doing so; it keeps your template as clean as possible, meaning if in a later upgrade you have to revert it to get the latest changes, reapplying your custom pieces is easy - you just add the template includes back in. We've been taking this approach with all custom themes we've created since IPS4's release (dozens by my last count). We try and keep the naming convention consistent too. All custom templates are named _customABC.phtml and exist in the /front/global/ group in the core application. This puts them in an easy-to-find location, and because of the underscore prefix, they're shown at the top of the directory. Example custom template bits in a custom theme Using them is simple: {template="_customHeader" group="global" app="core"} I hope this approach helps you keep your templates clean and more manageable! If you have any tips for working with your templates, please share them in the comments!
×
×
  • Create New...