Introduction
Are you willing to create plugins for RISE CRM and sell them on CodeCanyon or create your own features based on your need? This guidelines will show you the way to do that.
RISE 2.8 comes with a built-in feature to create plugins and integrate additional features along with the core functionalities.
Plugins are packages of code that supports a form of code modularization to help you create reusable code. Which extends the core functionality of main app.
The plugins can consist of action hooks, filters, controllers, models, libraries, languages, views, config files, helpers and also can access the base code of RISE CRM.
Before starting a plugin
- Plugin maintenance: It’s your responsibility to maintain the plugins you're developing. When there will be RISE CRM major changes, it's your job to make them compatible with the latest version.
- Plugin security: While building your plugins, please make sure that it's fully secured and use existing functions and methods that exists in the Codeigniter PHP framework.
- Our support for your plugin: We won’t offer you support for creating plugins, if it seems to you that something doesn't work right or it’s a bug, feel free to send an email to support@fairsketch.com
- Selling plugins on CodeCanyon: If you want to build plugins and sell them as an Author on CodeCanyon, you are absolutely free to do so. But keep in mind that because of our exclusivity agreement, you can only sell the plugins exclusively on CodeCanyon. You can’t present us as a partner. It should be as an individual or company. Publishing a plugin from your Envato account, which means that you own all the copyright to the plugin and the code.
Build your first plugin
- Create a plugin with a unique folder name.
- Create a index.php file at the root directory of plugin folder.
-
Add meta data to index.php file as like below:
<?php //Prevent direct access defined('PLUGINPATH') or exit('No direct script access allowed'); /* Plugin Name: Your Plugin Name Plugin URL: https://codecanyon.net/item/your_item Description: Your plugin description Version: 1.0 Requires at least: 2.8 Supports at most: 2.9 Author: Author name Author URL: https://codecanyon.net/user/author_url */
Note: The meta data is needed to add only in index.php file.
- This is now indexed in RISE. Go to Settings > Plugins. There you can install and activate this plugin.
- You can also create a .zip file with your plugin folder and install it from Settings > Plugins.
Common working processes
Visit the official documentation of CodeIgniter for advanced information.
-
Routes: Specify the routes to detect plugin from main app.
<?php namespace Config; $routes = Services::routes(); $routes->get('my_plugin', 'my_plugin::index', ['namespace' => 'My_Plugin\Controllers']); $routes->get('my_plugin/(:any)', 'my_plugin::$1', ['namespace' => 'My_Plugin\Controllers']); $routes->post('my_plugin/(:any)', 'my_plugin::$1', ['namespace' => 'My_Plugin\Controllers']); //will be required if any data need to be posted
-
Controllers:
<?php namespace My_Plugin\Controllers; use App\Controllers\Security_Controller; //access main app's controller class My_Plugin extends Security_Controller { //your methods }
-
Models:
<?php namespace My_Plugin\Controllers; use App\Models\Crud_model; //access main app's models class My_Plugin_Model extends Crud_model { //your methods }
Calling your models:
$My_Plugin_Model = new \My_Plugin\Models\My_Plugin_Model();
-
Libraries:
<?php namespace My_Plugin\Libraries; class My_Plugin_Library { //your methods }
Calling your library:
$my_plugin_library = new \My_Plugin\Libraries\My_Plugin_Library();
-
Languages: Follow the same directory design for languages. Like My_Plugin/Language/english/custom_lang.php and default_lang.php. Remember to add an unique prefix with your language key. Here has an example:
<?php $lang["my_plugin_show_staff_members"] = "Show staff members";
Then you can use the RISE's
app_lang("my_plugin_show_staff_members");
to retrieve the language value. Add all language directories as like the main app if you wish to support all language for your plugin. -
Views:
$view_data['foo'] = "bar"; //$this->template->rander() function will show the view with left menu and topbar by default. return $this->template->rander('My_Plugin\Views\folder\index', $view_data); //$this->template->view() function will show only the view, but here you'll get the login user's info as $login_user variable if any login user exists return $this->template->view('My_Plugin\Views\folder\index', $view_data); //default view() function of CI return view('My_Plugin\Views\folder\index', $view_data);
-
Helpers: Add an helper file in My_Plugin/Helpers folder (like my_plugin_general_helper.php) and call it like this:
helper("my_plugin_general_helper");
-
ThirdParty: Load a third party file like this:
require_once(PLUGINPATH . "My_Plugin/ThirdParty/library_folder/vendor/autoload.php");
-
Loading assets:
<link rel='stylesheet' type='text/css' href='" . base_url(PLUGIN_URL_PATH . "my_plugin/assets/css/my_plugin_styles.css") . "' />
-
File management: Follow the same workflow as we're doing now for file management. Only change the target directory to this:
PLUGIN_URL_PATH. "My_Plugin/files/my_plugin_files/"
Available functions
Call this functions in your My_Plugin folder/index.php file.
-
Register installation hook. When the plugin is about to install, this function will be triggered. Execute required sql queries or purchase code validation here.
register_installation_hook("My_Plugin", function ($item_purchase_code) { //validate purchase code if you wish if (!validate_purchase_code($item_purchase_code)) { echo json_encode(array("success" => false, 'message' => "Invalid purchase code")); exit(); } //run necessary sql queries $db = db_connect('default'); $db_prefix = get_db_prefix(); $db->query("SET sql_mode = ''"); $db->query("CREATE TABLE IF NOT EXISTS `" . $db_prefix . "plugin_table` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL , `deleted` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;"); });
-
Register uninstallation hook. When the plugin is about to uninstall, this function will be triggered.
register_uninstallation_hook("My_Plugin", function () { //run something });
-
Register activation hook. When the plugin is about to activate, this function will be triggered.
register_activation_hook("My_Plugin", function () { //run something });
-
Register deactivation hook. When the plugin is about to deactivate, this function will be triggered.
register_deactivation_hook("My_Plugin", function () { //run something });
-
Register update hook. Provide information to update your plugin. Please note that, if your updates has any version comparability, check that while updating.
register_update_hook("My_Plugin", function () { //show necessary information to update this plugin in the modal });
-
Register data insert hook.
register_data_insert_hook(function ($data) { //some data has been inserted /* the $data array has 3 items id => inserted id table => associated table data => inserted data array */ });
-
Register data update hook.
register_data_update_hook(function ($data) { //some data has been updated /* the $data array has 3 items id => updated id table => associated table data => updated data array */ });
-
Register data delete hook.
register_data_delete_hook(function ($data) { //some data has been deleted /* the $data array has 2 items id => deleted id table => associated table */ });
Available hooks
Call this hooks in your My_Plugin folder/index.php file.
-
Add an url to the CSRF exclude uris.
app_hooks()->add_action('app_filter_app_csrf_exclude_uris', function ($app_csrf_exclude_uris) { if (!in_array("plugin_controller.*+", $app_csrf_exclude_uris)) { array_push($app_csrf_exclude_uris, "plugin_controller.*+"); } return $app_csrf_exclude_uris; });
-
Execute an script after running cron job.
app_hooks()->add_action('app_hook_after_cron_run', function() { //run something });
-
Add an widget to custom dashboard panel.
app_hooks()->add_filter('app_filter_dashboard_widget', function ($default_widgets_array) { array_push($default_widgets_array, array( "widget" => "widget_name", "widget_view" => view("My_Plugin\Views\widget") )); return $default_widgets_array; });
-
Add some action links in Settings > Plugins table.
app_hooks()->add_filter('app_filter_action_links_of_My_Plugin', function ($action_links_array) { $action_links_array = array( anchor(get_uri("my_plugin/settings"), "Settings"), anchor(get_uri("my_plugin/get_support"), "Supports") ); return $action_links_array; });
-
User logged in successfully/there has logged in user who is about to access the app. This is the state of right before that.
app_hooks()->add_action('app_hook_before_app_access', function ($data) { $login_user_id = get_array_value($data, "login_user_id"); //logged in user id if it's valid login $redirect = get_array_value($data, "redirect"); //either it'll redirect to signin page or not if there has invalid login //perform something before accessing to the main app });
-
Add a menu item for staff users.
app_hooks()->add_filter('app_filter_staff_left_menu', function ($sidebar_menu) { $sidebar_menu["left_menu_item"] = array( "name" => "item_name", "url" => "my_plugin/url", "class" => "feather-icon-class", //like book "position" => 5 //will be on the position after 4th item ); return $sidebar_menu; });
-
Add a menu item for client users.
app_hooks()->add_filter('app_filter_client_left_menu', function ($sidebar_menu) { $sidebar_menu["left_menu_item"] = array( "name" => "item_name", "url" => "my_plugin/url", "class" => "feather-icon-class", //like book "position" => 5 //will be on the position after 4th item ); return $sidebar_menu; });
-
A new notification created. Do something after that.
app_hooks()->add_action('app_hook_post_notification', function ($notification_id) { //do something });
-
Create notifications. First, add the notification setting to notification_settings table while installing the plugin.
Note: On deactivation of your plugin, the notification setting will still be shown in notification settings. Because of this, use register_deactivation_hook() and set deleted=1 and register_activation_hook() to revert it again by deleted=0.
INSERT INTO `notification_settings` (`event`, `category`, `enable_email`, `enable_web`, `notify_to_team`, `notify_to_team_members`, `notify_to_terms`, `deleted`) VALUES ('your_notification_key', 'your_category', 0, 0, '', '', '', 0);
Also add the required id field as you needed to notifications table.
Note: Please add prefix (plugin_) with your field and note that, on deactivation of your plugin, the notifications will still be shown in user's notifications area. Because of this, use register_deactivation_hook() and set deleted=1 and register_activation_hook() to revert it again by deleted=0.
ALTER TABLE `notifications` ADD `plugin_your_item_id` INT(11) NOT NULL AFTER `deleted`;
Add notification config hook.app_hooks()->add_filter('app_filter_notification_config', function () { $notification_link = function () { return array("url" => get_uri("notification_link")); }; $notification_config = array( "category_suggestion" => array("id" => "your_category", "text" => app_lang("your_category")), "notification_info" => array( "your_notification_key" => array( //check ...app/Helpers/notifications_helper.php > function get_notification_config() for better ideas "notify_to" => array("recipient"), //check available values below "info" => $notification_link ) ) ); return $notification_config; });
Supported notify_to array:array("team_members", "team", "project_members", "client_primary_contact", "client_all_contacts", "task_assignee", "task_collaborators", "comment_creator", "cusomer_feedback_creator", "leave_applicant", "ticket_creator", "ticket_assignee", "estimate_request_assignee", "recipient", "mentioned_members", "owner", "client_assigned_contacts", "post_creator", "order_creator_contact");
Prepare query of getting notify_to users:app_hooks()->add_filter('app_filter_create_notification_where_query', function ($data) { //prepare WHERE query and return //check ...app/Models/Notifications_model.php > function create_notification() for better ideas /* The $data variable has this key values: "event" => Event key (your_notification_key). "user_id" => Which user created the notification. If it's 0, then it's created by the app. "options" => It contains another array of available notification item ids. Check ...app/Models/Notifications_model.php > function create_notification() for better ideas. "notify_to_terms" => Notify to terms array as selected in notification setting for this event. Like array("team_members", "team", "project_members"). */ });
If you want to show more information with the notification, you'll have to add this hook:app_hooks()->add_filter('app_filter_notification_description', function ($notification) { $notification_config = array( "notification_description" => view("your_notification_description", array("notification" => $notification)), //check ...app/Views/notifications/notification_description.php "notification_description_for_slack" => view("your_notification_description_for_slack", array("notification" => $notification)) //check ...app/Views/notifications/notification_description_for_slack.php ); return $notification_config; });
-
Add a payment method in the Settings > Setup > Payment methods table.
app_hooks()->add_filter('app_filter_payment_method_settings', function($settings) { $settings["payment_method_name"] = array( array("name" => "pay_button_text", "text" => app_lang("pay_button_text"), "type" => "text", "default" => "Payment Method"), //required array("name" => "payment_method_setting_text", "text" => "Payment method setting (Text)", "type" => "text", "default" => ""), array("name" => "payment_method_setting_boolean", "text" => "Payment method setting (Boolean)", "type" => "boolean", "default" => "0"), array("name" => "payment_method_setting_readonly", "text" => "Payment method setting (Readonly)", "type" => "readonly", "default" => "This is the readonly value"), ); return $settings; });
This also requires to add this payment method in payment_methods table while installing the plugin.INSERT INTO `payment_methods` (`title`, `type`, `description`, `online_payable`, `available_on_invoice`, `minimum_payment_amount`, `settings`, `deleted`) VALUES ('Payment method name', 'payment_method_name', 'Payment method description', 1, 0, 0, '', 0);
Please note that, on deactivation of a payment method plugin, the payment method will still be shown in payment method settings. Because of this, use register_deactivation_hook() and set deleted=1 and register_activation_hook() to revert it again by deleted=0.
-
Show activated payment method in invoice view.
app_hooks()->add_action('app_hook_invoice_payment_extension', function($payment_method_variables) { if (get_array_value($payment_method_variables, "method_type") === "my_payment_method") { echo view("My_Payment_Method\Views\index", $payment_method_variables); } });
The $payment_method_variables array() has this key values:
- method_type: The current payment method type of available activated payment methods.
- payment_method: The information array of payment method.
- balance_due: Total due balance for the invoice.
- currency: Currency for this invoice.
- invoice_info: The info object of invoice.
- invoice_id: Current invoice id.
- contact_user_id: For which client contact this invoice has been shared. (This variable will be available for public invoices)
- verification_code: The code to verify if it's a valid public invoice. (This variable will be available for public invoices)
-
Add ajax tab to several places.
//add ajax tab in staff profile app_hooks()->add_filter('app_filter_staff_profile_ajax_tab', function ($user_id) { $ajax_tabs = array( array( "title" => app_lang("tab_title"), "url" => get_uri("my_plugin/my_tab"), "target" => "my-plugin-my-tab" ) ); return $ajax_tabs; });
Available hooks to add ajax tabs:app_hooks()->add_filter('app_filter_client_details_ajax_tab', 'function_to_add_tab'); //parameter: $client_id app_hooks()->add_filter('app_filter_client_profile_ajax_tab', 'function_to_add_tab'); //parameter: $client_contact_id app_hooks()->add_filter('app_filter_lead_details_ajax_tab', 'function_to_add_tab'); //parameter: $lead_id app_hooks()->add_filter('app_filter_lead_profile_ajax_tab', 'function_to_add_tab'); //parameter: $lead_contact_id app_hooks()->add_filter('app_filter_project_details_ajax_tab', 'function_to_add_tab'); //parameter: $project_id
-
Add an item to settings.
app_hooks()->add_filter('app_filter_admin_settings_menu', function($settings_menu) { $settings_menu["setup"][] = array("name" => "my_plugin_setting", "url" => "my_plugin/setting"); return $settings_menu; });
-
Show something in signin view.
app_hooks()->add_action('app_hook_signin_extension', function() { //show something });
-
Show something in signup view.
app_hooks()->add_action('app_hook_signup_extension', function() { //show something });
-
Show something in main view of page layout to a login user.
app_hooks()->add_action('app_hook_layout_main_view_extension', function() { //show something });
-
Include something inside head tag.
app_hooks()->add_action('app_hook_head_extension', function() { //include something });
-
Show something at the top of dashboard like announcement alerts.
app_hooks()->add_action('app_hook_dashboard_announcement_extension', function() { //show something });
-
An user signed in.
app_hooks()->add_action('app_hook_after_signin', function() { //do something });
-
An user signing out.
app_hooks()->add_action('app_hook_before_signout', function() { //do something });
Best practices
Please follow this best practices while developing your plugin:
- Always add a prefix on function names, class names and database tables.
- Don't load plugin assets (like css/js) on all places. Load it only where it needed.
- Don't insert anything in settings table for your plugin purposes. Create your own table to store settings for your own. But you can get data of settings table always.
-
To prevent showing directory listing, add an index.html file on all sub folders of your plugin. You can use this code:
<html> <head> <title>403 Forbidden</title> </head> <body> <p>Directory access is forbidden.</p> </body> </html>