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.
<?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 Author: Author name Author URL: https://codecanyon.net/user/author_url */
Note: The meta data is needed to add only in index.php file.
Visit the official documentation of CodeIgniter for advanced information.
<?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
<?php namespace My_Plugin\Controllers; use App\Controllers\Security_Controller; //access main app's controller class My_Plugin extends Security_Controller { //your methods }
<?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();
<?php namespace My_Plugin\Libraries; class My_Plugin_Library { //your methods }
Calling your library:
$my_plugin_library = new \My_Plugin\Libraries\My_Plugin_Library();
<?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.
$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);
helper("my_plugin_general_helper");
require_once(PLUGINPATH . "My_Plugin/ThirdParty/library_folder/vendor/autoload.php");
<link rel='stylesheet' type='text/css' href='" . base_url(PLUGIN_URL_PATH . "My_Plugin/assets/css/my_plugin_styles.css") . "' />
PLUGIN_URL_PATH. "My_Plugin/files/my_plugin_files/"
Call this functions in your My_Plugin folder/index.php file.
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("My_Plugin", function () { //run something });
register_activation_hook("My_Plugin", function () { //run something });
register_deactivation_hook("My_Plugin", function () { //run something });
register_update_hook("My_Plugin", function () { //show necessary information to update this plugin in the modal });
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(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(function ($data) { //some data has been deleted /* the $data array has 2 items id => deleted id table => associated table */ });
Call this hooks in your My_Plugin folder/index.php file.
Important: When you use add_filter hook function, always return the first parameter with/without modifying it as per needs.
app_hooks()->add_filter('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; });
app_hooks()->add_action('app_hook_after_cron_run', function() { //run something });
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; });
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; });
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 });
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; });
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; });
app_hooks()->add_action('app_hook_post_notification', function ($notification_id) { //do something });
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 settings category hook.
app_hooks()->add_filter('app_filter_notification_category_suggestion', function ($category_suggestions) { $category_suggestions[] = array("id" => "your_category", "text" => app_lang("your_category")); return $category_suggestions; });Add notification config hook.
app_hooks()->add_filter('app_filter_notification_config', function ($events_of_hook) { $notification_link = function () { return array("url" => get_uri("notification_link")); }; $events_of_hook["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 $events_of_hook; });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 ($where_queries_from_hook, $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"). */ $where_queries_from_hook[] = "YOUR QUERIES"; return $where_queries_from_hook; });If you want to show more information with the notification, you'll have to add this hook:
//for web app_hooks()->add_filter('app_filter_notification_description', function ($notification_descriptions, $notification) { $notification_descriptions[] = view("your_notification_description", array("notification" => $notification)), //check ...app/Views/notifications/notification_description.php return $notification_descriptions; }); //for slack app_hooks()->add_filter('app_filter_notification_description_for_slack', function ($notification_descriptions, $notification) { $notification_descriptions[] = view("your_notification_description_for_slack", array("notification" => $notification)) //check ...app/Views/notifications/notification_description_for_slack.php return $notification_descriptions; });Add email templates.
app_hooks()->add_filter('app_filter_email_templates', function ($templates_array) { $templates_array["account"]["your_email_template"] = array("USER_FIRST_NAME", "USER_LAST_NAME", "LOGO_URL", "SIGNATURE", "OR_ANY_VARIABLE_YOU_WANT_TO_ADD"); return $templates_array; });This also requires to add this email template in email_templates table while installing the plugin.
INSERT INTO `email_templates` (`template_name`, `email_subject`, `default_message`, `custom_message`, `deleted`) VALUES ('your_email_template', 'Email subject', 'CONTENT OF YOUR EMAIL TEMPLATE', '', '0');Customize email notification.
app_hooks()->add_filter('app_filter_send_email_notification', function ($data) { //available values in $data //check .../app/Helpers/notifications_helper.php > function send_notification_emails() for better ideas $notification_info = get_array_value($data, "notification"); $parser_data = get_array_value($data, "parser_data"); $user_language = get_array_value($data, "user_language"); //check if it's your plugin's notification otherwise it'll send for all notifications if ($notification->event !== "your_notification_key") { return $data; } $Email_templates_model = model("App\Models\Email_templates_model"); $email_template = $Email_templates_model->get_final_template("your_email_template", true); $parser_data["ANY_VARIABLE"] = "value"; $parser = \Config\Services::parser(); $message = get_array_value($email_template, "message_$user_language") ? get_array_value($email_template, "message_$user_language") : get_array_value($email_template, "message_default"); $subject = get_array_value($email_template, "subject_$user_language") ? get_array_value($email_template, "subject_$user_language") : get_array_value($email_template, "subject_default"); $message = $parser->setData($parser_data)->renderString($message); //modify email message $subject = $parser->setData($parser_data)->renderString($subject); //modify email subject //add attachment //showing demo of sending an invoice pdf $invoice_data = get_invoice_making_data(invoice_id); $attachement_url = prepare_invoice_pdf($invoice_data, "send_email"); $email_options["attachments"] = array(array("file_path" => $attachement_url)); $info_array = array( "subject" => $subject, "message" => $message, "email_options" => $email_options, "attachement_url" => $attachement_url, ); return $info_array; });
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.
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:
//add ajax tab in staff profile app_hooks()->add_filter('app_filter_staff_profile_ajax_tab', function ($hook_tabs, $user_id) { $hook_tabs[] = array( "title" => app_lang("tab_title"), "url" => get_uri("my_plugin/my_tab"), "target" => "my-plugin-my-tab" ); return $hook_tabs; });Available hooks to add ajax tabs:
app_hooks()->add_filter('app_filter_client_details_ajax_tab', 'function_to_add_tab'); //parameter: $hook_tabs, $client_id app_hooks()->add_filter('app_filter_client_profile_ajax_tab', 'function_to_add_tab'); //parameter: $hook_tabs, $client_contact_id app_hooks()->add_filter('app_filter_lead_details_ajax_tab', 'function_to_add_tab'); //parameter: $hook_tabs, $lead_id app_hooks()->add_filter('app_filter_lead_profile_ajax_tab', 'function_to_add_tab'); //parameter: $hook_tabs, $lead_contact_id app_hooks()->add_filter('app_filter_integration_settings_tab', 'function_to_add_tab'); //parameter: $hook_tabs
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; });
app_hooks()->add_action('app_hook_signin_extension', function() { //show something });
app_hooks()->add_action('app_hook_signup_extension', function() { //show something });
app_hooks()->add_action('app_hook_layout_main_view_extension', function() { //show something });
app_hooks()->add_action('app_hook_head_extension', function() { //include something });
app_hooks()->add_action('app_hook_dashboard_announcement_extension', function() { //show something });
app_hooks()->add_action('app_hook_after_signin', function() { //do something });
app_hooks()->add_action('app_hook_before_signout', function() { //do something });
app_hooks()->add_action('app_hook_role_permissions_extension', function () { //show a role setting });
app_hooks()->add_filter('app_filter_role_permissions_save_data', function ($permissions) { $request = \Config\Services::request(); $permissions["your_permission"] = $request->getPost('your_permission'); return $permissions; });
app_hooks()->add_action('app_hook_task_view_right_panel_extension', function() { //show something });
app_hooks()->add_action('app_hook_general_settings_extension', 'function_to_add_setting'); app_hooks()->add_action('app_hook_general_settings_save_data', 'function_to_save_setting');
app_hooks()->add_action('app_hook_client_permissions_extension', 'function_to_add_setting'); app_hooks()->add_action('app_hook_client_permissions_save_data', 'function_to_save_setting');
app_hooks()->add_action('app_hook_team_members_my_preferences_extension', 'function_to_add_setting'); app_hooks()->add_action('app_hook_team_members_my_preferences_save_data', 'function_to_save_setting');
app_hooks()->add_action('app_hook_clients_my_preferences_extension', 'function_to_add_setting'); app_hooks()->add_action('app_hook_clients_my_preferences_save_data', 'function_to_save_setting');
app_hooks()->add_action('app_hook_general_settings_extension', function() { //show settings //show a setting block (<div class="form-group">) //check .../app/Views/settings/general.php for better ideas echo view("My_Plugin\Views\setting"); });Save setting:
app_hooks()->add_action('app_hook_general_settings_save_data', function () { $request = \Config\Services::request(); $your_setting_value = $request->getPost("your_setting"); $Settings_model = model("App\Models\Settings_model"); $Settings_model->save_setting("your_setting", $your_setting_value); });
//add ajax tab in staff profile app_hooks()->add_filter('app_filter_team_members_project_details_tab', function ($project_tabs_of_hook_of_staff, $project_id = 0) { $project_tabs_of_hook_of_staff["my_tab_title_with_available_language_key_value"] = "my_plugin/my_tab_url"; $project_tabs_of_hook_of_staff["my_tab_another_title_with_available_language_key_value"] = "my_plugin/my_another_tab_url"; return $project_tabs_of_hook_of_staff; }); //available filter for clients app_hooks()->add_filter('app_filter_clients_project_details_tab', 'function_to_add_tab'); //parameter: $project_tabs_of_hook_of_staff, $project_id
Events::on('pre_system', function () { if (!defined('PLUGIN_CUSTOM_STORAGE')) { define('PLUGIN_CUSTOM_STORAGE', TRUE); } });
app_hooks()->add_action('app_hook_upload_file_to_temp', function ($data) { $temp_file = get_array_value($data, "temp_file"); $file_name = get_array_value($data, "file_name"); \Your_Storage_Integration\Libraries\Your_Storage_Integration::upload_file_to_temp($temp_file, $file_name); //check upload_file_to_temp() function on .../app/Helpers/app_files_helper.php for better ideas });
app_hooks()->add_filter('app_filter_move_temp_file', function ($data) { $related_to = get_array_value($data, "related_to"); $file_name = get_array_value($data, "file_name"); $new_filename = get_array_value($data, "new_filename"); $file_content = get_array_value($data, "file_content"); $source_path = get_array_value($data, "source_path"); $target_path = get_array_value($data, "target_path"); $direct_upload = get_array_value($data, "direct_upload"); $files_data = \Your_Storage_Integration\Libraries\Your_Storage_Integration::move_temp_file($file_name, $new_filename); //use necessary parameters //check move_temp_file() function on .../app/Helpers/app_files_helper.php for better ideas return $files_data; });
app_hooks()->add_filter('app_filter_get_file_content', function ($data) { $file_info = get_array_value($data, "file_info"); $file_id = get_array_value($file_info, "file_id"); //you can use any key here instead of file_id which is needed to download the content //but you should pass that key and value on app_filter_move_temp_file hook (on returning $files_data array) //check download_app_files() function on .../app/Controllers/App_Controller.php for better ideas return \Your_Storage_Integration\Libraries\Your_Storage_Integration::get_file_content($file_id); });
app_hooks()->add_filter('app_filter_get_source_url_of_file', function ($data) { //check get_source_url_of_file() function on .../app/Helpers/app_files_helper.php for better ideas $file_info = get_array_value($data, "file_info"); $file_id = get_array_value($file_info, "file_id"); return \Your_Storage_Integration\Libraries\Your_Storage_Integration::get_source_url_of_file($file_id); });
app_hooks()->add_action('app_hook_delete_app_file', function ($file_info) { //check delete_app_files() function on .../app/Helpers/app_files_helper.php for better ideas $file_id = get_array_value($file_info, "file_id"); \Your_Storage_Integration\Libraries\Your_Storage_Integration::delete_file($file_id); });
app_hooks()->add_filter('app_filter_process_images_from_content', function ($content) { //sometimes you may need to update the source url of pasted images in rich text editor return $updated_content; Add a folder to Google Drive valid folders array.app_hooks()->add_filter('app_filter_add_folder_to_google_drive_valid_folders_array', function ($folders_array) { if (!in_array("your_folder", $folders_array)) { array_push($folders_array, "your_folder"); } return $folders_array; });
Please follow this best practices while developing your plugin:
<html> <head> <title>403 Forbidden</title> </head> <body> <p>Directory access is forbidden.</p> </body> </html>