-
Posts
163,911 -
Joined
-
Days Won
346
Content Type
Downloads
Release Notes
IPS4 Guides
IPS4 Developer Documentation
Invision Community Blog
Development Blog
Deprecation Tracker
Providers Directory
Projects
Release Notes v5
Invision Community 5 Bug Tracker
Forums
Events
Store
Gallery
Everything posted by bfarber
-
What it does OutputPlugins allow you to add additional template plugins that can be called from templates in order to perform an action. Examples of built in template plugins include {url="..."} and {lang="..."}. OutputPlugins can support an unlimited number of parameters. How to use The extension will be generated with one property and one method. /** * @brief Can be used when compiling CSS */ public static $canBeUsedInCss = FALSE; The $canBeUsedInCss property denotes whether or not the output plugin should be processed if used in CSS templates in the AdminCP. Some output plugins, such as hextorgb and url may be useful in CSS templates, while some may not be. /** * Run the plug-in * * @param string $data The initial data from the tag * @param array $options Array of options * @return string Code to eval ) */ public static function runPlugin( $data, $options ) { // return valid PHP code as a string } } The runPlugin() method accepts two parameters, and should return an array or a string. The first parameter is $data, which is the initial data for the tag (i.e. the value between the initial quotes). If your output plugin used was {myplugin="some value" option="other value"} then $data would be set to "some value". The $options parameter is an array of options in key => value pairs. In the above example, there would be a key 'option' with its value set to "other value". Your OutputPlugin extension should then return valid PHP code to insert into the compiled template. For example, the striptags plugin returns the following /** * Run the plug-in * * @param string $data The initial data from the tag * @param array $options Array of options * @return string Code to eval */ public static function runPlugin( $data, $options ) { return "strip_tags( $data )"; } Several output plugins exist out of the box. See our article on template plugins for more information on the existing plugins.
-
What it is The Notifications extension class allows your application to define new types of notifications that can be sent (and which users can configure how to receive), as well as process those notifications to define the values shown to users who have received such notifications. How to use When you create the extension, two methods will be present which you should define. /** * Get configuration * * @param \IPS\Member $member The member * @return array */ public function getConfiguration( $member ) { // Basically just return a list of the keys for the types of notification your app will send // keys can be anything you want. You can specify what should be the default value and any disabled values (acceptable values are "email" and "inline") // For each key, create a language string "notifications__<key>" which is what will display in the user's Notification Options screen return array( 'key' => array( 'default' => array( 'email' ), 'disabled' => array() ), ); } The comment in the above method outlines what you will need to do in the getConfiguration() method. Your extension should return an array with the keys as the notification keys and the value being an array defining which methods should be disabled and which methods should be enabled by default. /** * Parse notification: key * * @param \IPS\Notification\Inline $notification The notification * @return array * @code return array( 'title' => "Mark has replied to A Topic", // The notification title 'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to 'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text // explains what the notification is about - just include any appropriate content. // For example, if the notification is about a post, set this as the body of the post. 'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification ); * @endcode */ public function parse_key( \IPS\Notification\Inline $notification ) { return array( 'title' => "Mark has replied to A Topic", // The notification title 'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to 'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text // explains what the notification is about - just include any appropriate content. // For example, if the notification is about a post, set this as the body of the post. 'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification ); } For each 'key' defined previously, you will need to create a method "parse_{key}" to process notification data for that notification type. The method will accept an instance of \IPS\Notification\Inline and then return an array with keys 'title', 'url', 'content' and 'author' as appropriate. To send the notification, in your code you will create a new instance of \IPS\Notification, attach one or more recipients, and then send the notification. $notification = new \IPS\Notification( \IPS\Application::load('myapp'), 'key', $this->item(), array( $this ) ); $notification->recipients->attach( $member ); $notification->send();
-
What it is ModeratorPermissions extensions allow you to add additional moderator permissions that administrators will be able to set on a per-moderator basis when configuring moderators in the AdminCP. You could use this to allow administrators to specify which moderators can ban users from a chat room, for instance, or to specify which moderators can approve applications in your application. How to use When you create an instance of this extension you can define 3 methods. /** * Get Permissions * * @code return array( 'key' => 'YesNo', // Can just return a string with type 'key' => array( // Or an array for more options 'YesNo', // Type array( ... ), // Options (as defined by type's class) 'prefix', // Prefix 'suffix', // Suffix ), ... ); * @endcode * @return array */ public function getPermissions() { return array(); } The getPermissions() method returns the form elements to show when configuring the moderators in the AdminCP. /** * After change * * @param array $moderator The moderator * @param array $changed Values that were changed * @return void */ public function onChange( $moderator, $changed ) { } The onChange event is called when moderator permissions are actually changed. You can use this to update data within your application if needed. /** * After delete * * @param array $moderator The moderator * @return void */ public function onDelete( $moderator ) { } The onDelete() method is called when a moderator is deleted, giving your application an opportunity to perform any necessary actions when this situation occurs, if required. For instance if moderators are flagged special in your application, you may need to remove this flag when a user is removed as a moderator.
-
What it is The ModCpMemberManagement extension type allows your application to add a new tab to the ModeratorCP > Member Management page available to moderators (with permission). By default, there are tabs here in the ModeratorCP to manage banned, mod-queued and posting-restricted members, however your application may have a need to allow moderators to manage members with respect to your application as well. How to use When you create the extension, two basic methods are defined. /** * Returns the tab key for the navigation bar * * @return string|null */ public function getTab() { } The getTab() method returns a string key representing the tab. You will then need to create a language string with the prefix "modcp_members_" and the rest of the language string being the key returned from getTab() to define the string shown on the tab in the ModeratorCP. /** * Get content to display * * @return string */ public function manage() { } The manage() method should return the HTML to output, which will vary depending upon the needs of your extension. Commonly, you may use an \IPS\Helpers\Table\Db instance to facilitate pagination and generating the output, however you can return whatever HTML you need to ultimately.
-
What it is ModCp extensions allow you to add new pages to the moderator control panel (ModeratorCP) on the front end. This can be beneficial if there are special areas of your application that moderators may need to handle, for example banning users from a chat room or allowing moderators to process form submissions in your application. How to use it The extension defines two basic methods. /** * Returns the primary tab key for the navigation bar * * @return string|null */ public function getTab() { } The getTab() method returns a key for the navigation bar on the left hand side of the ModeratorCP. You will then need to define a language key with prefix modcp_ and the rest of the language key being the value returned by getTab() to represent the title of the tab in the ModeratorCP. /** * Manage * * @return void */ public function manage() { } The manage() method is expected to return the tab HTML. The HTML returned will naturally vary depending upon the intention and functionality of the extension itself. Additional Steps After creating the extension, you'll also need to register the friendly URL definition ( modcp_$extensionName ) in your applications furl.json file!
-
What it is The MFAArea extension allows you to protect certain resources within your application behind multi-factor authentication (MFA), if enabled for the site. Commerce uses this, for example, to require MFA when users access certain restricted areas. Using an extension allows administrators to explicitly require or not require MFA for your sensitive pages, giving them greater control and flexibility over the MFA system as a whole. How to use The extension defines one method, which allows administrators to enable or disable MFA being required in the area defined. /** * Is this area available and should show in the ACP configuration? * * @return bool */ public function isEnabled() { return TRUE; } Your application can return a hardcoded boolean value here, or return the result of an operation (e.g. you may want to check if a feature is enabled and only return TRUE if the feature is enabled). You will also need to add a language string that will be used when configuring MFA in the AdminCP to denote what this extension protects. 'MFA_appkey_ExtensionKey' => "Doing something in my application", The format of the language key is MFA_ followed by the application directory and an underscore, followed by the extension key you specified when creating the extension (the class and file name). Finally, in your application controllers where appropriate, you will need to check if the user has passed MFA yet, and if not show the appropriate form. This can often be done in the controller's execute() method, however you will also need to output the real page behind the MFA prompt so you will need to evaluate how your application behaves to determine the best spot to do this. An example from Commerce is shown here: /** * Execute * * @return void */ public function execute() { // Removed for brevity if ( $output = \IPS\MFA\MFAHandler::accessToArea( 'nexus', 'Alternatives', \IPS\Http\Url::internal( 'app=nexus&module=clients&controller=alternatives', 'front', 'clientsalternatives', array(), \IPS\Settings::i()->nexus_https ) ) ) { \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('clients')->alternatives( TRUE ) . $output; return; } parent::execute(); } While some other code not relevant to our example has been stripped here, you can see that we check accessToArea() against \IPS\MFA\MFAHandler, passing in the application key and the extension key, as well as a URL the user should be sent to afterwards. If any output is returned, we send the output immediately appended to the rest of the page. Note, however, that the regular page HTML does NOT contain any sensitive data (the TRUE parameter passed to the template results in the page header being output but not the normal body of the page.
-
What it is The MetaData extension type is a very generic extension type that, at its core, is designed to facilitate the addition of meta data associated with content items. If your application does not have any content items, then this extension type will not be of use to you. The best way to see how you might use this extension is to review some existing implementations, including the core extensions FeaturedComments (which allows moderators to feature comments within individual content items) and ContentMessages (which allows moderators to add generic messages to content items). Note, however, that the extension type is abstract and you can use it to add any type of meta data that will be associated with content items. How to use When you create a new instance of this extension there are no methods defined, and indeed you are not explicitly required to define any. It is often beneficial, however, to add helper methods related to the type of meta data you are adding. For instance, if you were creating a MetaData extension that would allow moderators to add regular members as moderators for individual content items, you may want to create helper methods for canAddModerator(), isModerator(), removeModerator() and so on. Ultimately, however, it is up to you. Content Items must implement the \IPS\Content\MetaData interface to make use of MetaData extensions. You must add a column to your content item database table to store meta data references, and then you must map this column in the $databaseColumnMap class property for your content item class with an array key 'meta_data'. Your content item class must implement the method supportedMetaDataTypes() which will define which meta data types it supports, as well. /** * Supported Meta Data Types * * @return array */ public static function supportedMetaDataTypes() { return array( 'core_ContentMessages' ); } Afterwards, the meta data will be maintained automatically (i.e. if the content item is deleted, the meta data will automatically be deleted as well). Several helper methods will become available to you within your content item class once you have taken these steps. /** * Check if this content has meta data * * @return bool * @throws \BadMethodCallException */ public function hasMetaData() This method, as implied, simply checks if the content item has any meta data and returns a boolean to indicate so. /** * Fetch Meta Data * * @return array * @throws \BadMethodCallException */ public function getMeta() The getData() method fetches all meta data for a content item, returning an array that with keys indexed by the extension. If your application myapp implements a MetaData extension called CustomSomething, then you can fetch the meta data stored for an individual content item via $item->getData()['myapp_CustomSomething'] Note that meta data is stored in the core_content_meta database table. /** * Add Meta Data * * @param string The type of data * @param array The data * @return in * @throws \BadMethodCallException */ public function addMeta( $type, $data ) Use the addMeta() method to store new meta data for a content item. /** * Edit Meta Data * * @param int The ID * @param array The data * @return void * @throws \BadMethodCallException */ public function editMeta( $id, $data ) And use the editMeta() method to update meta data for your content item records. /** * Delete Meta Data * * @param int The ID * @return void */ public function deleteMeta( $id ) The deleteMeta() method deletes meta data associated with a content item. It is important to highlight that because you are adding new content item functionality when you add new MetaData extensions, you will be required to perform additional changes beyond simply adding the extension. For example, if you were adding a MetaData extension that allowed moderators to add custom moderators to a content item, you would need to perform at least the following: Add links in the various associated HTML templates to add and remove the moderators (for users who have permission to do so) Add controller methods that will be called to perform these actions Add methods to the content item models to actually save the meta data changes using the helper methods outlined above And so on..
-
What it is MemberHistory extensions accept member history records that have been stored and translate the data for display. How to use The extension defines 2 methods, and then you will need to store member history records separately when appropriate. The parseLogType column accepts the log type as the $value parameter and the log data (the row from the database) as the $row parameter, and is expected to return a human-understandable representation of the log type. For instance, if a user is moved from Status A to Status B in your application, you will want to return a string similar to "Promoted from Status A to Status B". You can use a language string and sprintf arguments to accomplish this. /** * Parse LogType column * * @param string $value column value * @param array $row entire log row * @return string */ public function parseLogType( $value, $row ) { return $value; } The parseLogData() method accepts the log data as the $value parameter and the entire row as the $row parameter and is expected to return a human-understandable string representing the log data. You can return raw HTML (for instance, to return a font-awesome icon representing the data type) or a generic string. You can reference applications/core/dev/html/admin/members/logType.phtml as an example HTML template used to represent certain log data types in the core application. /** * Parse LogData column * * @param string $value column value * @param array $row entire log row * @return string */ public function parseLogData( $value, $row ) { return $value; } As previously mentioned, for this extension to have any use you will also need to log when a member's account has changed in some fashion. You can do this by calling the logHistory() method against the member. /** * Log Member Action * * @param mixed $app The application action applies to * @param string $type Log type * @param mixed $extra Any extra data for the type * @param mixed $by The member performing the action. NULL for currently logged in member or FALSE for no member * * @return void */ public function logHistory( $app, $type, $extra=NULL, $by=NULL ) \IPS\Member::loggedIn()->logHistory(... );
-
What it is A MemberSync extension allows your application an opportunity to perform actions when member accounts change. When members are deleted your application may need to remove data associated with the member, or when member's log out you may need to sync this state change to an external service. The MemberSync extension type provides several callback methods to accomplish these tasks. How to use When creating a MemberSync extension you need only use the methods that are relevant for your application. Any methods that are not relevant can be deleted. The possible methods are outlined below: /** * Member account has been created * * @param $member \IPS\Member New member account * @return void */ public function onCreateAccount( $member ) { } /** * Member has validated * * @param \IPS\Member $member Member validated * @return void */ public function onValidate( $member ) { } /** * Member has logged on * * @param \IPS\Member $member Member that logged in * @param \IPS\Http\Url $redirectUrl The URL to send the user back to * @return void */ public function onLogin( $member, $returnUrl ) { } /** * Member has logged out * * @param \IPS\Member $member Member that logged out * @param \IPS\Http\Url $redirectUrl The URL to send the user back to * @return void */ public function onLogout( $member, $returnUrl ) { } /** * Member account has been updated * * @param $member \IPS\Member Member updating profile * @param $changes array The changes * @return void */ public function onProfileUpdate( $member, $changes ) { } /** * Member is flagged as spammer * * @param $member \IPS\Member The member * @return void */ public function onSetAsSpammer( $member ) { } /** * Member is unflagged as spammer * * @param $member \IPS\Member The member * @return void */ public function onUnSetAsSpammer( $member ) { } /** * Member is merged with another member * * @param \IPS\Member $member Member being kept * @param \IPS\Member $member2 Member being removed * @return void */ public function onMerge( $member, $member2 ) { } /** * Member is deleted * * @param $member \IPS\Member The member * @return void */ public function onDelete( $member ) { } /** * Email address is changed * * @param \IPS\Member $member The member * @param string $new New email address * @param string $old Old email address * @return void */ public function onEmailChange( $member, $new, $old ) { } /** * Password is changed * * @param \IPS\Member $member The member * @param string $new New password * @return void */ public function onPassChange( $member, $new ) { } As you can see there are several event-based methods available to perform actions when specific member account changes occur. The most common uses of a MemberSync extension are to update content author IDs to 0 when a member is deleted (or to delete their contributions), and to change a member ID to a different member ID in onMerge when two accounts are merged. It should be noted that if your application uses content items and defines a ContentRouter extension, your content item data is handled automatically.
-
What it is MemberFilter extensions allow your application to create filter form elements to be used when bulk emailing members, and when configuring group promotion steps. MemberFilter extensions may be used elsewhere by the Suite in future releases. If your application has a filterable status that may be useful for identifying members (e.g. to promote or send emails to the member based upon), a MemberFilter extension may be beneficial. For example, Blog has a MemberFilter extension that will allow the administrator to promote the member or email the member based upon whether the member has created a blog or not. How to use When you create a MemberFilter extension, the extension template has 6 methods you will want to define. /** * Determine if the filter is available in a given area * * @param string $area Area to check * @return bool */ public function availableIn( $area ) { return in_array( $area, array( 'bulkmail', 'group_promotions' ) ); } Some MemberFilter extensions may be appropriate to promote based upon but not send emails, or vice-versa. If your extension may only be appropriate in one area you can perform that check in the availableIn() method, or you can return a blanket boolean TRUE or FALSE. /** * Get Setting Field * * @param mixed $criteria Value returned from the save() method * @return array Array of form elements */ public function getSettingField( $criteria ) { return array(); } The getSettingField() method accepts an array of existing setting values and is expected to return an array of form helper elements which will be added to the respective forms being generated. /** * Save the filter data * * @param array $post Form values * @return mixed False, or an array of data to use later when filtering the members * @throws \LogicException */ public function save( $post ) { return FALSE; } The save() method accepts the form values (from the form elements returned by getSettingField()) and is expected to return either FALSE, or an array of data that will be saved, and then later used for filtering (and for the default form values if the form is later edited). /** * Get where clause to add to the member retrieval database query * * @param mixed $data The array returned from the save() method * @return string|array|NULL Where clause */ public function getQueryWhereClause( $data ) { return NULL; } When searching for members that match the filter options, the getQueryWhereClause() will be called and is expected to return either a NULL value (indicating that this filter was not used, which can be verified by checking the $data array), a string value to be used in the where clause, or an array where clause. /** * Callback for member retrieval database query * Can be used to set joins * * @param mixed $data The array returned from the save() method * @param \IPS\Db\Query $query The query * @return void */ public function queryCallback( $data, &$query ) { return NULL; } The queryCallback() method can be used, if necessary, to adjust the database query before it is ran. This may be necessary, for instance, to join other database tables onto the query. /** * Determine if a member matches specified filters * * @note This is only necessary if availableIn() includes group_promotions * @param \IPS\Member $member Member object to check * @param array $filters Previously defined filters * @return bool */ public function matches( \IPS\Member $member, $filters ) { return TRUE; } If your member filter extension supports group_promotions (through the availableIn() method as outlined above), then you should define the matches() method which will accept an \IPS\Member object and the filters that have been defined for the extension, and will check if the member matches the filters and return a boolean TRUE or FALSE to indicate as such.
-
What it is A LiveSearch extension allows administrators to search through your application in the AdminCP using the global live search at the top of every page. This can be useful if your application has data which an administrator may be attempting to find (such as category names or department titles). How to use The extension will contain 4 methods you will define /** * Constructor * * @return void */ public function __construct() { /* Check Permissions */ } Any setup you need to perform, such as checking permissions, can be done in the constructor. /** * Check we have access * * @return bool */ public function hasAccess() { /* Check Permissions */ return TRUE; } The hasAccess() method is called to determine if the user has access to use the live search extension. If administrator restrictions may prevent the admin from being able to access your application, for instance, those permissions should be checked here to ensure searches do not reveal data to an administrator they otherwise would not be able to access. /** * Is default for current page? * * @return bool */ public function isDefault() { return \IPS\Dispatcher::i()->application->directory == 'myapp'; } The isDefault() method should return a boolean to indicate if your live search extension should be the default extension shown when a live search is performed on the current page. Most often this is done by checking the current application and module, if necessary, to determine if your application is currently being accessed (and thus, live search should show results from it first, if any are available). /** * Get the search results * * @param string Search Term * @return array Array of results */ public function getResults( $searchTerm ) { return array(); } The getResults() method accepts a string search term, and should return an array of search results to display. The array keys are irrelevant, however the values should be fully formatted HTML list items. A generic template is available at \IPS\Theme::i()->getTemplate( 'livesearch', 'core', 'admin' )->generic(), however in most cases you will be best served by creating a custom template and using that instead. The generic template you will want to use is akin to the following: <ips:template parameters="$url, $lang" /> <li class='ipsPad_half ipsClearfix' data-role='result'> <a href='{$url}' class='ipsPos_left'>{lang="$lang"}</a> </li>
-
What it is IpAddresses extensions allow your application to tie into the AdminCP and ModeratorCP IP address lookup areas to show when an IP address has been registered in your application, or all IP addresses used by a specific member. If your application does not store any IP addresses, you will not need to use this extension. If your application stores IP addresses outside of Content Item classes it is recommended that you create one or more IpAddresses extensions to allow administrators better visibility over site usage. Note that if your application has Content Items and you have properly defined your ContentRouter extension, you do not need to create IpAddresses extensions for those content item classes as they will automatically be searched. This extension can be used for supplemental storage of IP addresses, however (for instance, Downloads stores the IP address for each downloader and uses an IpAddresses extension to allow administrators to search these records). How to use The extension, when created, has 4 methods you will need to set. /** * Supported in the ACP IP address lookup tool? * * @return bool * @note If the method does not exist in an extension, the result is presumed to be TRUE */ public function supportedInAcp() { return TRUE; } /** * Supported in the ModCP IP address lookup tool? * * @return bool * @note If the method does not exist in an extension, the result is presumed to be TRUE */ public function supportedInModCp() { return TRUE; } The supportedInAcp() and supportedInModCp() methods are self-explanatory: they return a boolean flag to indicate if the extension should be loaded and checked in the AdminCP and ModeratorCP respectively. Some extensions may not wish to show data in one or the other area. For example, Commerce purchases cannot be managed by moderators and is considered more sensitive data, so IP addresses associated with commerce products are only available to look up in the AdminCP. /** * Find Records by IP * * @param string $ip The IP Address * @param \IPS\Http\Url $baseUrl URL table will be displayed on or NULL to return a count * @return \IPS\Helpers\Table|int|null */ public function findByIp( $ip, \IPS\Http\Url $baseUrl = NULL ) { /* Return count */ if ( $baseUrl === NULL ) { return \IPS\Db::i()->select( 'COUNT(*)', 'database_table_name', array( "ip_address LIKE ?", $ip ) )->first(); } /* Init Table */ // Replace database_table_name with the database table $table = new \IPS\Helpers\Table\Db( 'database_table_name', $baseUrl, array( "ip_address LIKE ?", $ip ) ); /* Return */ return (string) $table; } The findByIp() method expects to receive an IP address (or partial IP address) and return an instance of \IPS\Helpers\Table that will be output. Most commonly you will us the \IPS\Helpers\Table\Db method for this purpose. This automatically implements pagination and so on, while allowing you to control which columns are shown in the lookups and how they are formatted. /** * Find IPs by Member * * @code return array( '::1' => array( 'ip' => '::1'// string (IP Address) 'count' => ... // int (number of times this member has used this IP) 'first' => ... // int (timestamp of first use) 'last' => ... // int (timestamp of most recent use) ), ... ); * @endcode * @param \IPS\Member $member The member * @return array|NULL */ public function findByMember( $member ) { return array(); } The findByMember() method accepts an \IPS\Member object and should subsequently return an array formatted as keys being the IP addresses found (within your application) and the values being arrays with keys 'ip' (the IP address again), 'count' (the number of instances the IP address was found for this member), 'first' (the timestamp of the first found instance of this IP address used by this member) and 'last' (the timestamp of the last time this IP address was used by this member).
-
What it is IncomingEmail extensions allow your application an opportunity to parse incoming emails and perform an action based on this. For example, Commerce has the ability to parse incoming emails in order to allow clients to email replies to tickets which will automatically be stored as replies within Commerce to the associated support request. You must set up POP3 fetching of emails or piping of incoming emails into the software in order for the incoming emails to be processed by Invision Community. Setting this up is described in our help guides. How to use An IncomingEmail extension has a single method named process(), which will accept an instance of \IPS\Email\Incoming\Email and should return a boolean TRUE or FALSE to indicate if the extension has processed the email or not. All IncomingEmail extensions are looped through when processing incoming emails so it is imperative that you check parameters of the email to ensure it is an email that you need to process. One way to do this is to set up different email addresses for each incoming email extension, and then check the email address within the process method like so /** * Handle email * * @param \IPS\Email\Incoming $email The email * @return bool */ public function process( \IPS\Email\Incoming\Email $email ) { /* Was this emailed to the right address? */ $expectedEmail = \IPS\Settings::i()->my_app_incoming_email; if( !in_array( $expectedEmail, $email->to ) AND !in_array( $expectedEmail, $email->cc ) ) { return FALSE; } } // Now process the email You can refer to \IPS\Email\Incoming\Email for details about the class properties and methods, but briefly the most important among these are: $email->to: (array) The address(es) that were emailed $email->cc: (array) Any email addresses that were CC'd $email->from: (string) The sender email address (note that this can be spoofed) $email->subject: (string) The subject of the email $email->message: (string) The body of the email (HTML sanitized for security purposes) $email->quote: (string) The quoted part of the email, such as when a user hits reply to an existing email (HTML sanitized) $email->attachments: (array) Any file attachments as \IPS\File objects - note that the attachments are automatically also appended to the email body $email->raw: (string) Raw original email (be careful attempting to use this, as the contents have not been sanitized so you could inadvertently introduce security issues) $email->headers: (array) Raw original email headers
-
What it is GroupLimits extensions allow your application an opportunity to take a group setting and then determine what ultimate value a user who belongs to multiple groups should be given. For instance, if a user is in a group who is allowed to post content and in a group who is not allowed to post content, then the user should be permitted to post content (because the group setting merges take the best value possible). While this simple use case scenario is handled out of the box, other slightly more complex scenarios will require you to define how the user's permission should be set. Generally speaking, if you create a GroupForm extension you will want to create a GroupLimits extension as well. How it works The extension requires you to define a single getLimits() method /** * Get group limits by priority * * @return array */ public function getLimits() This method should return an array that specifies how to process your group settings. The valid array keys are as follows: exclude: An array of group setting keys that should NOT be merged, no matter what. For instance, there is no better or worse group icon, so a user's group icon will always be the one representing their primary group, and group icons are excluded when merging member group permissions. lessIsMore: An array of group setting keys where the LOWER the value, the better. For instance, if you have a timeout period, the lower the timeout the less restrictive, and thus the better the value. neg1IsBest: An array of group setting keys where a -1 value is considered best, followed by the HIGHEST value. -1 is commonly used to indicate "unlimited" throughout the Invision Community software. zeroIsBest: An array of group setting keys where a 0 value is considered best, followed by the HIGHEST value. If you have an on/off field, for instance, and off would be a less restrictive setting for the member, then you would want to define this in zeroIsBest. callback: An array with group setting keys as the keys and the values being a callback function accepting 3 parameters: the first parameter is the first group we are checking, the second parameter is the second group we are checking, and the third parameter is the group setting key being checked. More complex group configuration options may require you to manually determine which value to use. For instance, signature configurations store complex values and the two groups must be manually checked against each other.
-
What it is GroupForm extensions allow you to add form elements to the group configuration form in the AdminCP, and then process the values supplied for those form elements in order to format and/or store the configuration. This allows you to easily add additional per-group settings for your application, if needed. How to use /** * Process Form * * @param \IPS\Helpers\Form $form The form * @param \IPS\Member\Group $group Existing Group * @return void */ public function process( &$form, $group ) The process() method allows you to add new form elements to the group form in the AdminCP. A new tab will be added automatically so you do not need to do that, however you can add headers and separators to the form if desired. As the $form parameter is passed by reference, you can simply add new elements to $form directly. /** * Save * * @param array $values Values from form * @param \IPS\Member\Group $group The group * @return void */ public function save( $values, &$group ) The save() method is called in order to save the values submitted in the form. Typically based on the amount of the fields you'll either create a new setting in your application and use it to save the values, or create a new database table to save the data. It is important that, keeping in line with the rest of the Community Suite, you follow some guidelines when creating settings on the group configuration page. Settings should always be labeled in the affirmative, rather than the negative. For instance, instead of "Block access to application" you should label the setting "Can access application" (and simply negate the submitted value if the backend code is still expecting this to be an off setting instead of an on setting). Setting labels should be clear and concise, and setting descriptions should elaborate on anything that may not be immediately understood by the label alone. Form toggles should be used to show and hide form elements that become important or unimportant based on other selections. Settings that will never apply to a guest should be hidden if the group being adjusted is equal to \IPS\Settings::i()->guest_group in order to prevent confusion.
-
What it is FrontNavigation extensions are used to add new tabs to the front end navigation system, tying in directly with the menu manager in the AdminCP. Navigation tabs can show or hide based on permissions and other criteria, activate (or not) based on custom factors, and can support submenus as well. How to use Most front navigation extensions are fairly simple, and represent a single link to an area of the site. We will start by outlining how these work: you will need 5 basic methods for this type of front navigation extension. /** * Get Type Title which will display in the AdminCP Menu Manager * * @return string */ public static function typeTitle() The typeType() shows the title for this type of menu entry and is primarily used in the AdminCP menu manager. A language string value should be returned typically. /** * Can the currently logged in user access the content this item links to? * * @return bool */ public function canAccessContent() The canAccessContent() method allows you to dynamically check if the current viewing member an access the page or not. Often this will come down to checking if the member can access the module or not, however you can perform whatever checks you want, returning TRUE if the member can access the tab and FALSE if not. /** * Get Title * * @return string */ public function title() The title() method returns the tab title to display on the front end. /** * Get Link * * @return \IPS\Http\Url */ public function link() The link() method, as you might expect, returns the link that the tab should point to. A full \IPS\Http\Url object should be returned. /** * Is Active? * * @return bool */ public function active() The active() method returns TRUE to indicate that the user is currently viewing this tab, and FALSE otherwise. Most often, this is accomplished by checking which application, module and controller the user is viewing. For example: /** * Is Active in the online users list? * * @return bool */ public function active() { return \IPS\Dispatcher::i()->application->directory === 'core' and \IPS\Dispatcher::i()->module->key === 'online'; } While the above methods represent the 5 most common methods to define, the extension supports some other capabilities as well. /** * Children * * @param bool $noStore If true, will skip datastore and get from DB (used for ACP preview) * @return array */ public function children( $noStore=FALSE ) The children() method returns an array of child elements for the menu item, used to create a submenu. The sole parameter passed to this method is a boolean flag to indicate whether or not the child items should be loaded directly from the database, bypassing caching. /** * Permissions can be inherited? * * @return bool */ public static function permissionsCanInherit() By default permissions can be inherited by menu items (e.g. if you cannot access any menu items, do not show the tab), however you can disable this if you wish by overriding this method and returning FALSE. /** * Allow multiple instances? * * @return string */ public static function allowMultiple() By default, only one instance of a menu item is available to set up (so you cannot create two 'Gallery' tabs by choosing Gallery in the menu manager), however if your menu class would benefit from supporting multiple instances this method can be overridden and return TRUE. This is used for the base generic Menu front navigation extension, for instance, as you may want to create multiple menus. /** * Get configuration fields * * @param array $configuration The existing configuration, if editing an existing item * @param int $id The ID number of the existing item, if editing * @return array */ public static function configuration( $existingConfiguration, $id = NULL ) If your menu requires special configuration, you can define a static configuration() method to return an array of form helper elements to display in order to configure the menu. /** * Parse configuration fields * * @param array $configuration The values received from the form * @return array */ public static function parseConfiguration( $configuration, $id ) If your menu requires special configuration, you can define a static parseConfiguration() method to process the form helper elements returned with the configuration() method described above. /** * Can this item be used at all? * For example, if this will link to a particular feature which has been diabled, it should * not be available, even if the user has permission * * @return bool */ public static function isEnabled() As the docblock states, you can return FALSE from the isEnabled() method if you need to completely disable the menu item regardless of user permissions. It is worth noting that FrontNavigation extensions extend \IPS\core\FrontNavigation\FrontNavigationAbstract so it is worth taking a look at this class to understand the methods being extended and how they interact if there is any confusion.
-
What it is FileStorage extensions allow you to define a type of file that will be stored, which in turn allows administrators determine where and how to store them (for instance, to store some files on Amazon S3 and some files locally in a specific directory). It is imperative that every type of file has its own storage extension to prevent files from getting orphaned later, or being removed by the software because the extension used does not properly map to where the files are being stored. How to use When you create a FileStorage extension, you will need to define 5 methods within the class. /** * Count stored files * * @return int */ public function count() The count() method should return the number of files total that are currently being stored by the file storage extension. Often, this will be a database COUNT() query result. /** * Move stored files * * @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension * @param int $storageConfiguration New storage configuration ID * @param int|NULL $oldConfiguration Old storage configuration ID * @throws \UnderflowException When file record doesn't exist. Indicating there are no more files to move * @return void|int An offset integer to use on the next cycle, or nothing */ public function move( $offset, $storageConfiguration, $oldConfiguration=NULL ) The move method is called when files are being moved from one storage configuration (e.g. disk storage) to another (e.g. Amazon S3). The method will generally need to do the following: Select the next row to be moved. Typically this will be a database query, using limit $offset, 1 (pull 1 file starting at $offset), with first() called against the query. This may result in an UnderflowException if there are no more rows to pull, however that is ok and will be caught by the move files process. $emoticon = \IPS\Db::i()->select( '*', 'core_emoticons', array(), 'id', array( $offset, 1 ) )->first(); Next, you will load the file from the old storage configuration, and move it to the new configuration. $file = \IPS\File::get( $oldConfiguration ?: 'core_Emoticons', $emoticon['image'] )->move( $storageConfiguration ); This process can result in an exception if the file cannot be found (for instance), which should be caught and ignored (as the underlying error is already logged). Finally, you will typically need to update the database row to reflect the new location of the file. if ( (string) $file != $row['location'] ) { \IPS\Db::i()->update( 'database_table', array( 'location' => (string) $file ), array( 'id=?', $row['id'] ) ); } You can optionally return an integer offset if you prefer, which can allow you to return (for example) the last ID processed and start at that ID on the next cycle, instead of using limit( $offset, 1 ). This will provide much better performance if the table is expected to get very large. /** * Check if a file is valid * * @param string $file The file path to check * @return bool */ public function isValidFile( $file ) The isValidFile() method is used to determine if a file is valid for the storage engine. You will want to query your database table (or otherwise check where you are storing file mappings) to determine if the file is valid or not. If so, return TRUE, otherwise return FALSE. /** * Delete all stored files * * @return void */ public function delete() The delete method is used to delete all files in the storage extension. You will need to loop over all of the files and call the delete method against the \IPS\File object representing the file. An example for the image proxy would be /** * Delete all stored files * * @return void */ public function delete() { foreach( \IPS\Db::i()->select( '*', 'core_image_proxy', 'location IS NOT NULL' ) as $cache ) { try { \IPS\File::get( 'core_Imageproxycache', $cache['location'] )->delete(); } catch( \Exception $e ){} } } /** * Fix all URLs * * @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension * @return void */ public function fixUrls( $offset ) Finally, the fixUrls() method is designed to loop through all files and fix the URLs that are stored for the files. There is a handy method in \IPS\File named repairUrl() which will do the bulk of the work for you. The general template is as follows /** * Fix all URLs * * @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension * @return void */ public function fixUrls( $offset ) { $cache = \IPS\Db::i()->select( '*', 'database_table', array(), 'id ASC', array( $offset, 1 ) )->first(); try { if ( $new = \IPS\File::repairUrl( $cache['location'] ) ) { \IPS\Db::i()->update( 'database_table', array( 'location' => (string) $new ), array( 'id=?', $cache['location'] ) ); } } catch( \Exception $e ) { /* Any issues are logged and the \IPS\Db::i()->update not run as the exception is thrown */ } }
-
What it is An EditorMedia extension is designed to allow users to share content from your application within any editor in the suite using the "Insert other media" menu at the bottom left. By default, the user's existing attachments are available for them to share, and if Gallery is installed then any images the user has uploaded in Gallery are also available to be easily shared. How to use The extension must define two methods /** * Get Counts * * @param \IPS\Member $member The member * @param string $postKey The post key * @param string|null $search The search term (or NULL for all) * @return int */ public function count( $member, $postKey, $search=NULL ) { // Get the count and return it as an int } The count method is passed the member who is posting, the post key, and any search term (the insert other media feature supports searching) that you should filter by. You must return a count of the total number of insertable media files that match the parameters. /** * Get Files * * @param \IPS\Member $member The member * @param string|null $search The search term (or NULL for all) * @param string $postKey The post key * @param int $page Page * @param int $limit Number to get * @return array array( 'Title' => array( (IPS\File, \IPS\File, ... ), ... ) */ public function get( $member, $search, $postKey, $page, $limit ) { // Get files and return } The get() method accepts an object $member representing the member who is posting, the search string as $search, the post key of the current submission, a $page parameter representing what page of media objects the user is viewing, and a $limit parameter that represents how many files per page are being displayed. The method should then return an array of media objects that the user may insert. The most common implementation will formulate a database query restricting submissions to the member and the search term supplied (if any), limiting by ( $page -1 ) * $limit, $limit, loop over the results and store them in an array, and then return the array. The following example from Gallery will help illustrate how this is done /** * Get Files * * @param \IPS\Member $member The member * @param string|null $search The search term (or NULL for all) * @param string $postKey The post key * @param int $page Page * @param int $limit Number to get * @return array array( 'Title' => array( (IPS\File, \IPS\File, ... ), ... ) */ public function get( $member, $search, $postKey, $page, $limit ) { $where = array( array( "image_member_id=? AND image_approved=?", $member->member_id, 1 ), ); if ( $search ) { $where[] = array( "image_caption LIKE ( CONCAT( '%', ?, '%' ) )", $search ); } $return = array(); foreach ( \IPS\Db::i()->select( '*', 'gallery_images', $where, 'image_date DESC', array( ( $page - 1 ) * $limit, $limit ) ) as $row ) { $image = \IPS\gallery\Image::load( $row['image_id'] ); $obj = \IPS\File::get( 'gallery_Images', $row['image_masked_file_name'] ); $obj->contextInfo = $image->caption; $return[ (string) $image->url() ] = $obj; } return $return; } A where clause is crafted to return images that the current member has submitted and which are approved, and then restricted based on the search term if applicable. We then craft a database query to fetch images based on this where clause, newest to oldest, and limited as previously described. The image is loaded, and then the \IPS\File object is fetched for this image. Some additional context information is passed to the file object (in this case, the image caption), and then a return entry is stored with the URL that clicking the image should take you to as the key, and the \IPS\File object as the value. The array is then returned.
-
What it is An EditorLocations extension allows your application to define a location that an editor instance may be used in. This is important for multiple reasons: An administrator may want to allow certain editor buttons to be available in some areas but not others You may want to allow HTML to be used in some editors but not others, independent of group permissions You may want to allow the administrator to control whether attachments can be uploaded in some editors An EditorLocations extension is necessary to properly map where attachment files are being used How to use The extension has several required methods, and a few optional methods that you can define. You will also then need to pass this extension classname to your editor form helper instances later. /** * Can we use HTML in this editor? * * @param \IPS\Member $member The member * @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that. */ public function canUseHtml( $member ) { return NULL; } The canUseHtml() method is used to determine if a member can post HTML in the editor instance. Typically you will want to return NULL to let the software determine automatically if the member has permission to post HTML, however in some instances you may wish to explicitly enable or disable the ability to use HTML in an editor. /** * Can we use attachments in this editor? * * @param \IPS\Member $member The member * @param \IPS\Helpers\Form\Editor $field The editor field * @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that. */ public function canAttach( $member, $field ) { return NULL; } The canAttach() method determines if the user can upload attachments to this editor. You may wish to explicitly disable attachments in specific editors (e.g. if you provide a separate image upload field, in order to prevent confusion), in which case you can return FALSE from this method (or TRUE to explicitly enable attachments). NULL will allow the Invision Community software to determine automatically if the member has permission to upload attachments. /** * Permission check for attachments * * @param \IPS\Member $member The member * @param int|null $id1 Primary ID * @param int|null $id2 Secondary ID * @param string|null $id3 Arbitrary data * @param array $attachment The attachment data * @return bool */ public function attachmentPermissionCheck( $member, $id1, $id2, $id3, $attachment ) { return TRUE; } The attachmentPermissionCheck() is passed the $member being checked against, the ID values stored with the attachment, and the attachment data itself, and expects a boolean response to indicate if the member has permission to view the attachment. For private messages, for example, we need to verify if the member is a party to the conversation before permitting them to access the attachment, while for reports in the report center we need to verify the member can access reports before showing them the attachment. /** * Attachment lookup * * @param int|null $id1 Primary ID * @param int|null $id2 Secondary ID * @param string|null $id3 Arbitrary data * @return \IPS\Http\Url|\IPS\Content|\IPS\Node\Model * @throws \LogicException */ public function attachmentLookup( $id1, $id2, $id3 ) { // .... } The attachmentLookup() method is passed the ID values stored with the attachment and expects, in turn, an instance of \IPS\Http\Url, \IPS\Content or \IPS\Node\Model to be returned. The method is designed to accept some data about an attachment and then return the content that the attachment is attached to. The method is designed to throw a LogicException, so you can call load() against a content item without attempting to catch it. e.g. return \IPS\myapp\ContentItem::load( $id1 ); If the content item does not exist, an exception will be thrown automatically. /** * Rebuild content post-upgrade * * @param int|null $offset Offset to start from * @param int|null $max Maximum to parse * @return int Number completed * @note This method is optional and will only be called if it exists */ public function rebuildContent( $offset, $max ) { return $this->performRebuild( $offset, $max, array( 'IPS\Text\LegacyParser', 'parseStatic' ) ); } When a user upgrades to 4.x or higher, all content previously stored must be rebuilt. The rebuildContent() method allows you to control how your legacy content is rebuilt. Most extensions define a central performRebuild() method as you see here (outlined later in this article), so if you do the same you can likely copy the method above verbatim. /** * Rebuild attachment images in non-content item areas * * @param int|null $offset Offset to start from * @param int|null $max Maximum to parse * @return int Number completed */ public function rebuildAttachmentImages( $offset, $max ) { return $this->performRebuild( $offset, $max, array( 'IPS\Text\Parser', 'rebuildAttachmentUrls' ) ); } When a user upgrades to 4.x or higher, attachments in non-content areas need to rebuilt. The rebuildAttachmentImages() method allows you control how this occurs, however most extensions define a central performRebuild() method (outlined later) and if you do the same you can likely copy this method verbatim. /** * Rebuild content to add or remove image proxy * * @param int|null $offset Offset to start from * @param int|null $max Maximum to parse * @param bool $status Enable/Disable Image Proxy * @return int Number completed * @note This method is optional and will only be called if it exists */ public function rebuildImageProxy( $offset, $max, $status=TRUE ) { return $this->performRebuild( $offset, $max, array( 'IPS\Text\Parser', 'parseImageProxy' ) ); } When toggling the image proxy feature on and off in the AdminCP, the administrator is afforded an opportunity to rebuild their existing URLs. The rebuildImageProxy() method allows you to define how this occurs. As with the previously described two methods, most extensions use a centralized performRebuild() method for this purpose. /** * Total content count to be used in progress indicator * * @return int Total Count */ public function contentCount() { return X; } When content is being rebuilt through one of the previously described operations, the total number of content to be rebuilt is needed in order to ensure we show an accurate progress indicator. Most times, this method simply selects a count from a database table. /** * Perform rebuild - abstracted as the call for rebuildContent() and rebuildAttachmentImages() is nearly identical * * @param int|null $offset Offset to start from * @param int|null $max Maximum to parse * @param callable $callback Method to call to rebuild content * @return int Number completed */ protected function performRebuild( $offset, $max, $callback ) { // Execute here... } While this method is not part of the extension definition, most extensions process rebuilding attachment URLs, older content and proxy image rebuilds sufficiently similar that the extension defines one central method that the other methods simply call to. Generally when used, this method loops over the content records (i.e. by looping over records in a database table, using the $offset and $max values as the LIMIT offsets for the database query), and then passes the content to the callback that was specified. It is important to catch any InvalidArgument exceptions that may be thrown, as old content can be unpredictable and may contain formatting issues that cause exceptions to be thrown when we attempt to rebuild it. The rebuilt content should then be stored back in the database by updating the record that is being processed. Finally, the number of records completed should be returned (when 0 is returned, the rebuild process is considered completed). Specifying the EditorLocations extension to use The final step to using this extension is to define it when you create an Editor form helper instance. $form->add( new \IPS\Helpers\Form\Editor( 'content', NULL, TRUE, array( 'app' => 'core', 'key' => 'ExtensionKey', 'autoSaveKey' => '...' ) ) ); Here, the editor instance will look for an EditorLocations key named "ExtensionKey" in the core application. The methods outlined above will then affect the behavior of the editor as previously noted.
-
What it is A Dashboard extension allows you to add a widget to the AdminCP dashboard, which the administrator can optionally enable or hide, and reposition on their AdminCP dashboard to the location they want the widget to display. How to use The class defines two methods that you must implement. /** * Can the current user view this dashboard item? * * @return bool */ public function canView() { return TRUE; } The canView() method is called to determine if the currently logged in administrator has permission to view the widget. It is important to remember that administrator restrictions may mean that the admin cannot access your application or a specific area of your application, and you should check appropriate permissions here to ensure that data is not displayed to the administrator which they should not be able to see. /** * Return the block to show on the dashboard * * @return string */ public function getBlock() { return \IPS\Theme::i()->getTemplate( 'xxxxx' )->template(); } The getBlock() method returns the HTML to display. This should typically be an HTML template with the data that you pass to it. There is no standard HTML template to utilize as each dashboard block will have different needs and different output.
-
What it is The CreateMenu extension allows you to add extra submission links to the global "Create" menu at the top of every page. How to use it The class should contain a single method, getItems(), which will return an array of entries for the create menu. The array key should be the language key used for the content item, and the value should be an array with the following keys: link: (required) This is the link that clicking the menu item entry will take you to title: (optional) This is the title of the dialog window if one is shown (see extraData) extraData: (optional) This is an array of additional attributes to add to the link. Most commonly this is used to show a dialog window asking the end user where they want to submit the content item to (e.g. if submitting a new topic you must first choose which forum to submit into) 'extraData' => array( 'data-ipsDialog' => true, 'data-ipsDialog-size' => "narrow" ), flashMessage: (optional) Flash message shown to the end user when they submit the dialog window. This is typically omitted, as most creation extensions send the user to a form to finish submitting the content. You should verify the currently logged in user has permission to submit to your application before adding a link to the menu, and return an empty array() otherwise.
-
What it is A ContactUs extension is designed to allow you to handle contact form submissions in a special manner (based on the administrator configuration). For example, Commerce uses a ContactUs form extension to allow administrators to send contact form submissions to a support request (rather than emailing the administrator). How it works The extension has 3 methods defined. /** * Process Form * * @param \IPS\Helpers\Form $form The form * @param array $formFields Additional Configuration Formfields * @param array $options Type Radio Form Options * @param array $toggles Type Radio Form Toggles * @param array $disabled Type Radio Form Disabled Options * @return void */ public function process( &$form, &$formFields, &$options, &$toggles, &$disabled ) { } The process() method is called when an administrator attempts to configure the contact us form within the AdminCP. You can us this method to return additional form fields, and even adjust existing form field toggles. The form field keys must be setting keys within the internal settings system, as the form submission simply calls $form->saveAsSettings() afterwards. You should add your options to the $options array (passed by reference), and then add any additional configuration form field helper objects to the $formFields array. You can adjust the $disabled and $toggles arrays if appropriate (for instance, if there are no Support departments in Commerce, the option to submit contact us form submissions as a support request will be shown, but disabled). /** * Allows extensions to do something before the form is shown... e.g. add your own custom fields, or redirect the page * * @param \IPS\Helpers\Form $form The form * @return void */ public function runBeforeFormOutput( &$form ) { } If your contact us form submission must do anything special when your extension is used (for instance, add extra form fields to the page or redirect to a different page) you can perform that work in runBeforeFormOutput(). /** * Handle the Form * * @param array $values Values from form * @return bool */ public function handleForm( $values ) { return false; } When the contact us form is submitted by an end user, handleForm() will be called for each extension. You should confirm if your extension is configured to be used (e.g. by checking a setting property that was set from process() previously) and then perform whatever action your extension needs to perform. If your extension has handled the form submission, you should return TRUE from handleForm(), otherwise you should return FALSE so that other extensions will be processed.
-
What it does The CommunityEnhancements extension is designed to allow you to highlight third party integrations that users of your application can optionally take advantage of to further enhance their community. If your application requires a specific integration, then that integration is not strictly an "enhancement" and should not be highlighted in the Community Enhancements page. Extensions of this type will be shown under System > Community Enhancements and administrators will be able to review, enable and disable, and configure the enhancements as needed from this area. Some examples included with Invision Community are Viglink integration and Sendgrid integration. How it works The extension class defines four properties and four methods that allow you to configure the extension. /** * @brief Enhancement is enabled? */ public $enabled = FALSE; The $enabled property specifies if the enhancement is enabled or not. Typically, you will want to have a setting stored in the database to determine if this enhancement is enabled or not, and then reset this property in the constructor (see below). /** * @brief IPS-provided enhancement? */ public $ips = FALSE; The $ips property determines if the enhancement is provided by IPS. For all third party code, this property should be set to FALSE (which is the default). /** * @brief Enhancement has configuration options? */ public $hasOptions = TRUE; If your enhancement has configuration settings that will need to be filled in, the $hasOptions property should be set to TRUE, otherwise this property can be set to FALSE. Most enhancements have one or more settings (e.g. to supply an API key or similar token value). /** * @brief Icon data */ public $icon = ""; You can specify the name of a file stored in the resources system for each theme to show an icon on the Community Enhancements page. See our article on adding resources for more information on adding the icon when your application is installed. /** * Constructor * * @return void */ public function __construct() { //$this->enabled = \IPS\Settings::i()->xxxxxx; } More often than not, you will want to reset the $enabled property based on a system setting, and you will do this in the constructor. You can perform any other setup work necessary in the constructor as well. /** * Edit * * @return void */ public function edit() { $form = new \IPS\Helpers\Form; $form->add( new \IPS\Helpers\Form\YesNo( 'my_setting_key', \IPS\Settings::i()->my_setting_key ) ); if ( $form->values() ) { $form->saveAsSettings(); \IPS\Session::i()->log( 'acplog__myappce_edited', array( 'enhancements__myapp_ExtensionKey' => TRUE ) ); \IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved'); } \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'enhancements__myapp_ExtensionKey', $form ); } The edit() method is designed to allow you to generate a form in order to allow the administrator to configure your community enhancement. You can include as many form fields as you need/like, and you can also output additional sidebar buttons/links if you have any helpful links you would like to present to the administrator (for instance, you may wish to link to documentation about how the enhancement works, or link to a third party website to sign up for an API key in order to use the enhancement). During the save, it is recommended to store an admin log entry for future administrator reference. You use regular form helper objects in the edit() method and then pass the form to the output property, just as you would within a controller. /** * Enable/Disable * * @param $enabled bool Enable/Disable * @return void * @throws \LogicException */ public function toggle( $enabled ) { if ( $enabled ) { $this->testSettings(); } //\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $enabled ), array( 'conf_key=?', 'xxxxxx' ) ); //unset( \IPS\Data\Store::i()->settings ); } The toggle() method is called when an administrator attempts to toggle your enhancement enabled or disabled within the AdminCP. It is important to note that the administrator may not have yet configured your enhancement, so it is recommended to call testSettings() (described further below) if the administrator is attempting to enable the enhancement to be sure it will function correctly. Typically, as outlined above discussing the $enabled class property, you will store the enabled/disabled flag in a setting, so you will then want to update the setting appropriately here and clear the settings data store cache. /** * Test Settings * * @return void * @throws \LogicException */ protected function testSettings() { if ( FALSE ) { throw new \LogicException( 'error' ); } } When your enhancement configuration is edited or toggled, the testSettings() method should be called in order to test the settings that have been supplied. This method should throw a LogicException exception if the test fails, which you should then catch and show an appropriate error message for. This is most frequently used to test configuration values supplied by the administrator, e.g. to call an API with a supplied API key to verify that the key is indeed valid and configured correctly. Doing so allows you to alert the administrator to any problems with the configuration right away instead of having to diagnose why something may not be working (or may be causing strange errors) later.
-
What it does The BulkMail extension allows you to define macros (replacement tags) available for use when a bulk mail is being created or edited. For example, your application may wish to allow tags to be used to insert certain statistics (number of records in the application) or certain member data (how many times a member has submitted to your application). How it works The extension defines two very simple methods. The first method is called to return an array of tags that are available. /** * Get tags that can be used in bulk mail * * @return array */ public function getTags() { return array( '{some_tag}' => 'Explanation of tag', ); } The second method performs the actual replacements. You would need to loop through the tags you support, and then return the appropriate replacement for the individual member being emailed. /** * Get value for tags * * @param string $content Bulk Mail Content (passed in case a particular tag is computationally expensive so that the extension may "sniff" for it and elect not to perform the computation if it is not used) * @param int $type 0=All, 1=Global, 2=Member-specific * @param NULL|\IPS\Member $member Member object if $type is 0 or 2 * @return array */ public function returnTagValues( $content, $type, $member ) { return array( '{some_tag}' => 'Value', ); } The $content parameter is ONLY to be used to determine if fetching the replacement value is worthwhile. If, for instance, a resource intensive query must be ran to replace a specific tag however the tag was not used in the body of the email, you should skip running the intensive query since the value will not be needed. The $type parameter specifies whether all tags should be returned, global tags (i.e. tags which return the same value regardless of which member is being emailed) should be returned, or only per-member tags should be returned. Finally, the member being emailed is passed to the method as $member. The replacements will then be performed by the bulk mailer when bulk mailing the membership of the site.
-
What it does A 'Build' extension allows you to specify extra steps to take when your application is built. This may be necessary, for example, if any data must be compiled or otherwise modified, or if any data needs to be imported into the database during the application build process. Build extensions are used to compile CKEditor and to minify CodeMirror files, for example. How it works The extension supports two methods. The first method, build(), performs the actual processing during the build process. /** * Build * * @return void * @throws \RuntimeException */ public function build() { // Do your work here } The second method, finish(), allows you to do any post-processing cleanup if necessary. For instance, if you copied some files temporarily to perform some processing on them, you may wish to use this method to delete them; or you may want to set a flag when the build process starts and then remove that flag when the process has ended.