RISE - Ultimate Project Manager & CRM

## 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

1. 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.
2. 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.
3. 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
4. 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

- Here is a demo plugin for more clarification.
- 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
          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.
Important: When you use add_filter hook function, always return the first parameter with/without modifying it as per needs.

- Add an url to the CSRF exclude uris.

          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;
          });

- 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 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;
          });

- 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:

  1. method_type: The current payment method type of available activated payment methods.
  2. payment_method: The information array of payment method.
  3. balance_due: Total due balance for the invoice.
  4. currency: Currency for this invoice.
  5. invoice_info: The info object of invoice.
  6. invoice_id: Current invoice id.
  7. contact_user_id: For which client contact this invoice has been shared. (This variable will be available for public invoices)
  8. 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 ($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

- 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
          });

- Add an option in role settings.
  1. Show role within a <li></li> block. Check .../app/Views/roles/permissions.php file for better ideas.
     You'll get your permission value with array key like this: $your_permission = get_array_value($permissions, "your_permission");

             app_hooks()->add_action('app_hook_role_permissions_extension', function () {
                 //show a role setting
             });

  2. Save 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;
             });

- Show something on the right panel of task view.

          app_hooks()->add_action('app_hook_task_view_right_panel_extension', function() {
              //show something
          });

- Add setting on several places.
  1. Add option in general settings.

             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');

  2. Add option in client permissions 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');

  3. Add option in team member's my preferences 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');

  4. Add option in client's my preferences 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');

  Example:
  Show option in general 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 on project details page.

          //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

- Add custom storage integration.
  1. First define the constant PLUGIN_CUSTOM_STORAGE on pre_system event.

             Events::on('pre_system', function () {
                 if (!defined('PLUGIN_CUSTOM_STORAGE')) {
                     define('PLUGIN_CUSTOM_STORAGE', TRUE);
                 }
             });

  2. While uploading a file, upload it to temporary directory first.

             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
             });

  3. Move temp file to permanent directory.

             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");

                 $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;
             });

  4. Download file by retrieving the content of file.

             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);
             });

  5. Get the source url of file to show preview.

             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);
             });

  6. Delete file.

             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);
             });

## 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>