× Table of Contents Feng Office Plugin Tutorial: Hello World Creating the plugin Structure Plugin Metadata Creating the Controller – The business layer Creating the View – The Presentation Layer Configuring the Tab Icon Custom CSS and JS Installing the plugin Example with the Email Plugin Plugin Metadata Creating the Controller – The business layer Example of how to create a new type of Content Object in a plugin Feng Office Plugin Tutorial: Hello World In this tutorial we will create a minimal plugin that shows a simple message in a Feng Office tab. This should provide a basic understanding of how the Feng Office Plugin System works. A tutorial for a less “trivial” example is being developed and can be found here. Creating the plugin Structure First, choose a plugin name, and create a folder with that name in the Feng Office plugins folder: FENGOFFICE_ROOT/plugins/PLUGIN_NAME where FENGOFFICE_ROOT is the document root for Feng Office on your server and PLUGIN_NAME is the name of your new plugin, in our case 'helloworld', for example: /public_html/fengoffice/plugins/helloworld All the necessary resources and code for your plugin will be placed in this folder, including javascripts, css, images and, of course, the application php (templates, views, controllers, models). One exception is the theme-specific CSS, which must be inserted in the relevant CSS stylesheet within your theme. Plugin Metadata The first file you need to create is the plugin metadata 'info.php'. Create this file under your plugin root folder 'plugins/PLUGIN_NAME/info.php' containing this php code: info.php <?php return array( "name" => "helloworld", // plugin name "version" => "1", // plugin version "author" => "Feng Office", // author name "website" => "http://fengoffice.com", // author website URL "description" => "My first Feng plugin" // brief description );?> This will provide information about the plugin version, description, order, dependencies, category, author and other metadata necessary for third party plugin management. However, if you want to create a new tab along with your plugin, you will need to specify it with a tab definition follows: info.php <?php return array( "name" => "helloworld", // plugin name "version" => "1", // plugin version "author" => "Feng Office", // author name "website" => "http://fengoffice.com", // author website URL "description" => "My first Feng plugin", // brief description "tabs" => array( // tab definition array( "id" => "helloworld-panel", //tab id "ordering" => 2, //tab ordering position "title" => "helloworld tab", //tab title (lookup key for lang file) "icon_cls" => "ico-world", //CSS class for tab's icon "refresh_on_context_change" => true, //re-run the controller if e.g. new workspace is chosen "default_controller" => "helloworld", //default controller "default_action" => "say_hello" , //default action(method) called when tab is selected or context changes - used to list objects, etc. "initial_controller" => "" , //initial controller "initial_action" => "" , //initial action(method) called once per session - can be used to load js scripts, etc. "type" => "plugin", //type - use "plugin" "object_type_id" => 0 //id of the content object if it is related to one, otherwise 0 ) ) );?> Note: Please look below at the 'Email' plugin example for further details regarding a content object association. This tells the installer that it will have to create a new tab in the system, specifying id, title and many other configuration options, but the most important ones are default_controller and default_action. The tab title will be looked up in a language file. In our case the lookup string will be “helloworld tab”. We need to provide a return string in the language file. Create the language folder 'plugins/PLUGIN_NAME/language/LANGUAGE', in our case 'plugins/helloworld/language/en_us'. Two files are placed into this folder: 'lang.js' and 'lang.php'. Simply copy the lang.js script from another plugin, and create 'lang.php' as follows: lang.php <?php return array( 'helloworld tab'=>'World', ); This will display 'World' as the our new tab's title. For multi-lingual support you may repeat this process for other languages. Creating the Controller – The business layer At this point we assume that you are familiar with MVC concepts. If not, we strongly suggest you explore the topic before proceeding. Create the folder 'application/controllers' under your plugin structure: FENGOFFICE_ROOT/plugins/PLUGIN_NAME/application/controllers Then create the following class under this folder: [PLUGIN_NAME]Controller.class.php That is, in our example (note the CamelCase): HelloworldController.class.php …with the following content: HelloworldController.class.php <?php class HelloworldController extends ApplicationController { var $plugin_name = "helloworld"; //name of the plugin function __construct() { parent::__construct(); prepare_company_website_controller($this, 'website'); } function say_hello() { $txt = "Hello World ! ! ! ! "; tpl_assign('message',$txt); } }?> In this case the controller loads the data, and assigns it to the view in a variable called 'message'. Notes: We have not spoken about models yet, so the data is hard-coded in order to simplify the example. This is not what the actual Mail plugin does, but it has been used this way to explain it better Creating the View – The Presentation Layer Create the folders 'application/views/helloworld' under your plugin structure so that it looks like: FENG_ROOT/plugin/PLUGIN_NAME/application/views/OBJECT_NAME i.e.: FENG_ROOT/plugin/helloworld/application/views/helloworld Within the last folder, create the following file: 'say_hello.php' with the following content: say_hello.php <div class="hello-world"> <h1><?php echo $message ?></h1> <p>Congratulations ! ! !</p> <p>This is your first hello world application. <p> </div> Note: When invoking a controller action (or method talking in OOP), the system automatically loads the view with the same name; controller function 'ACTION()' will invoke view 'ACTION.php'. Configuring the Tab Icon In info.php we set the array element "icon_cls" => "ico-world" and now we need to create a CSS class .ico-world that styles a background with the desired icon image. Create CSS stylesheet for the theme's tab at [FENG_ROOT]/plugins/helloworld/public/assets/css/helloworld.css and insert the class .ico-world. You may also create your an image folder for your plugin at [FENGOFFICE_ROOT]/plugins/helloworld/public/assets/images or, if suitable images are already available, simply use them instead, as we do here, i.e., the default theme's 16×16 sprites with a vertical offset of -357 pixels. helloworld.css .ico-world { background: transparent url('/public/assets/themes/default/images/16x16/all_16_16_vertical.png') no-repeat scroll 0 -357px !important; } Custom CSS and JS Custom CSS and JS can be done in many ways: INLINE (not recommended): Each view can make use of <script> and <style> tags, but this does not follow the best practices of web development Automatically load the css JS (recommended): While loading a page, Feng Office will include the JS and css on its head section if you create those files following this convention: plugins/PLG_NAME/public/assets/javascript/PLG_NAME.js plugins/PLG_NAME/public/assets/css/PLG_NAME.css At Runtime In any place of your controller method add the following line: require_javascript('WhateverYorJsIsNamed.js', $this→plugin_name); Sometimes you want to include JS libraries only on some user actions, so this way is better if that is your case. Installing the plugin If you don't see the 'Plugins' icon in the Administration panel, then enable it by editing 'config/config.php' and inserting the line: define('PLUGIN_MANAGER', true); Close and restart Feng Office Navigate to Adminstration → Plugins. You should now see an entry for 'helloworld' and an 'Install' link just below. Manually edit the default theme stylesheet to include the class for the tab icon. (See Configuring the Tab Icon above) Click the 'Install' link and, if successful, click the 'Activate' link. Now refresh the browser (Ctrl+F5) and you should see your new tab “World” appear. If not, check the tab ordering for conflicts (Administration → Tabs) and retry. To move the plugin to another instance of Feng Office simply: Upload the helloworld folder to the new server Make sure the plugin manager is enabled Manually edit the default theme CSS stylesheet Navigate to Administration → Plugins and 'Install' and 'Activate' If necessary, set the tab order in Administration → Tabs Refresh the browser Note on Installing: With each plugin we may supply scripts that run at install time only. They are placed in the 'plugins/PLUGIN_NAME/install/' folder and invoked in order: 'sql/mysql_schema.php', 'sql/mysql_initial_data.php' and 'install.php'. Note on Uninstalling. With each plugin we should supply a script called 'plugins/PLUGIN_NAME/uninstall.php', which is invoked by the Plugin Manager and whose function it is to remove the plugin's data and files. The Uninstall mechanism is not covered in this tutorial, and for now we will manually uninstall: Uncheck the tab in Administration → Tabs 'Deactivate' and 'Uninstall' in Administration → Plugins Remove your plugin folder from the plugins folder Launch phpMyAdmin and delete the relevant records in the 'plugins', 'tab_panels' and 'tab_panel_permissions' tables. Example with the Email Plugin In this case we will take the Email plugin as an example provided that anyone can access its source code. The plugin name is 'mail'. Create a folder named 'PLUGIN_NAME' under FENGOFFICE_ROOT/plugins. All the necessary resources and code for your plugin will be placed here: javascripts, css, images, php (templates, views, controllers, models). Plugin Metadata The first file you need to create is the plugin metadata 'info.php'. Create this file under you plugin root folder 'plugins/PLUGIN_NAME/info.php' containing this php code: <?php return array( "name" => "mail", //name of the plugin "version" => "1", //version of the plugin "author" => "Feng Office", //author "website" => "http://fengoffice.com", //website of the author "description" => "Email web client", //brief description of the plugin "dependences" => array('core_dimensions'), //if the plugin depends on another plugin to work correctly "order" => 1 );?> This will provide information about the plugin version, description, order, dependences, category, author and other metadata necessary for third party plugin management. If you want to create a new tab or associate it with a content object, you have to specify to the metadata file some extra configuration as shown below: <?php return array( "name" => "mail", "version" => "2", "author" => "Feng Office", "website" => "http://fengoffice.com", "description" => "Email web client", "dependences" => array('core_dimensions'),// Array of plugin names (TODO check dependences) "order" => 1, "types" => array ( //association with a content object array( "id" => 22, //id of the content object "name" => "mail", //content object name "handler_class" => "MailContents", //name of the handler class "table_name" => "mail_contents", //name of the table containing it "type" => "content_object", "icon" => "mail", //icon of the content object ) ), "tabs" => array ( //tab creation array( "id" => "mails-panel", //plugin id "ordering" => 2, //order of the tab when shown among the rest, 0 being the first "title" => "email tab", //lang that will be used to specify the tab name "icon_cls" => "ico-mail", //icon that the tab will have "refresh_on_context_change" => true, "default_controller" => "mail", //name of the controller "default_action" => "init" , //first action it will do when loading "initial_controller" => "" , "initial_action" => "" , "type" => "plugin", //type "object_type_id" => 22 //id of the content object if it is related to one ) ) );?> This tells the installer that it will have to create a new tab in the system, specifying id, title and many other configuration options, but the most important ones are default_controller and default_action. Creating the Controller – The business layer Create the folder 'application/controllers' under your plugin structure - so that it looks like FENG_ROOT/plugins/PLUGIN_NAME/application/controllers -, and create the following class under this folder: Name of the class: [PLUGIN_NAME]Controller.class.php ⇒ i.e.: MailController.class.php <?php class MailController extends ApplicationController { var $plugin_name = "mail"; //name of the plugin function __construct() { parent::__construct(); prepare_company_website_controller($this, 'website'); Env::useHelper('MailUtilities.class', $this->plugin_name); //in case it needs an extra class to handle some functions require_javascript("AddMail.js", $this->plugin_name); //in case it requires a JavaScript file to load certain data } function init() { //do something } }?> Example of how to create a new type of Content Object in a plugin New Plugin with new object type In this example we are creating the “objectives” plugin with the new object type “Objective”. The plugin needs to have the following structure: The first step is to build the installer script to: - create new tables - add more columns to existing tables That has to be done in the file install/sql/mysql_schema.php Example: CREATE TABLE IF NOT EXISTS `<?php echo $table_prefix ?>objectives` ( `object_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `description` text NOT NULL, PRIMARY KEY (`object_id`) ) ENGINE=<?php echo $engine ?> <?php echo $default_charset ?>; Note: for each object in this table we need to add a new record in “objects” table with the same id. To add new object type to the system then the sql queries will be placed in install/sql/mysql_initial_data.php Here we need to add the new object type in “object_types” table: </code> INSERT INTO `<?php echo $table_prefix ?>object_types` (`name`,`handler_class`,`table_name`,`type`,`icon`,`plugin_id`) VALUES ('objective', 'Objectives', 'objectives', 'content_object', 'objective', (SELECT `id` FROM `<?php echo $table_prefix ?>plugins` WHERE `name`='objectives')) ON DUPLICATE KEY UPDATE id=id; </code> Then we need to specify that this new object can be classified in the workspaces and tags dimensions: INSERT INTO `<?php echo $table_prefix ?>dimension_object_type_contents` (`dimension_id`,`dimension_object_type_id`,`content_object_type_id`, `is_required`, `is_multiple`) SELECT (SELECT `id` FROM `<?php echo $table_prefix ?>dimensions` WHERE `code`='workspaces'), `id`, (SELECT `id` FROM `<?php echo $table_prefix ?>object_types` WHERE `name`='objective'), 0, 1 FROM `<?php echo $table_prefix ?>object_types` WHERE `name`='workspace' ON DUPLICATE KEY UPDATE dimension_id=dimension_id; INSERT INTO `<?php echo $table_prefix ?>dimension_object_type_contents` (`dimension_id`,`dimension_object_type_id`,`content_object_type_id`, `is_required`, `is_multiple`) SELECT (SELECT `id` FROM `<?php echo $table_prefix ?>dimensions` WHERE `code`='tags'), `id`, (SELECT `id` FROM `<?php echo $table_prefix ?>object_types` WHERE `name`='objective'), 0, 1 FROM `<?php echo $table_prefix ?>object_types` WHERE `name`='tag' ON DUPLICATE KEY UPDATE dimension_id=dimension_id;'' Add the new tab to the system, so we can list the objects INSERT INTO `<?php echo $table_prefix ?>tab_panels` (`id`,`title`,`icon_cls`,`refresh_on_context_change`,`default_controller`,`default_action`,`initial_controller`,`initial_action`,`enabled`,`type`,`ordering`,`plugin_id`,`object_type_id`) VALUES ('objectives-panel', 'objectives', 'ico-objectives', 1, 'objective', 'init', '', '', 1, 'system', 11, (SELECT `id` FROM `<?php echo $table_prefix ?>plugins` WHERE `name`='objectives'), (SELECT id FROM <?php echo $table_prefix ?>object_types WHERE name='objective')) ON DUPLICATE KEY UPDATE id=id; Define permissions for the new tab and for the new object type for each user role INSERT INTO `<?php echo $table_prefix ?>tab_panel_permissions` (`permission_group_id`, `tab_panel_id`) SELECT `c`.`permission_group_id`, 'objectives-panel' FROM `<?php echo $table_prefix ?>contacts` `c` WHERE `c`.`user_type` IN ( SELECT `id` FROM `<?php echo $table_prefix ?>permission_groups` WHERE `type`='roles' AND `name` IN ('Super Administrator','Administrator','Manager','Executive') ) ON DUPLICATE KEY UPDATE tab_panel_id=tab_panel_id; INSERT INTO <?php echo $table_prefix ?>max_role_object_type_permissions (role_id, object_type_id, can_delete, can_write) SELECT p.id, o.id, 1, 1 FROM `<?php echo $table_prefix ?>object_types` o JOIN `<?php echo $table_prefix ?>permission_groups` p WHERE o.`name` IN ('objective') AND p.`name` IN ('Super Administrator','Administrator','Manager'); INSERT INTO <?php echo $table_prefix ?>max_role_object_type_permissions (role_id, object_type_id, can_delete, can_write) SELECT p.id, o.id, 0, 1 FROM `<?php echo $table_prefix ?>object_types` o JOIN `<?php echo $table_prefix ?>permission_groups` p WHERE o.`name` IN ('objective') AND p.`name` IN ('Executive'); INSERT INTO <?php echo $table_prefix ?>role_object_type_permissions (role_id, object_type_id, can_delete, can_write) SELECT p.id, o.id, 1, 1 FROM `<?php echo $table_prefix ?>object_types` o JOIN `<?php echo $table_prefix ?>permission_groups` p WHERE o.`name` IN ('objective') AND p.`name` IN ('Super Administrator','Administrator','Manager'); INSERT INTO <?php echo $table_prefix ?>role_object_type_permissions (role_id, object_type_id, can_delete, can_write) SELECT p.id, o.id, 0, 1 FROM `<?php echo $table_prefix ?>object_types` o JOIN `<?php echo $table_prefix ?>permission_groups` p WHERE o.`name` IN ('objective') AND p.`name` IN ('Executive'); -- add permissions to users where the users already have permissions (copy permissions from tasks) INSERT INTO <?php echo $table_prefix ?>contact_member_permissions (permission_group_id, member_id, object_type_id, can_delete, can_write) SELECT cmp.permission_group_id, cmp.member_id, (SELECT `id` FROM `<?php echo $table_prefix ?>object_types` WHERE `name`='objective'), cmp.can_delete, cmp.can_write FROM <?php echo $table_prefix ?>contact_member_permissions cmp WHERE cmp.object_type_id = (SELECT `id` FROM `<?php echo $table_prefix ?>object_types` WHERE `name`='task') ON DUPLICATE KEY UPDATE <?php echo $table_prefix ?>contact_member_permissions.member_id=<?php echo $table_prefix ?>contact_member_permissions.member_id; INSERT INTO <?php echo $table_prefix ?>tab_panel_permissions (permission_group_id, tab_panel_id) SELECT pg.id, 'objectives-panel' FROM <?php echo $table_prefix ?>permission_groups pg WHERE pg.type='roles' AND pg.name IN ('Super Administrator','Administrator','Manager','Executive') ON DUPLICATE KEY UPDATE tab_panel_id=tab_panel_id; To implement model, views and controller you can take a look at how it is done for the “Notes” module. Controller: application/controllers/MessageController.class.php Views: application/views/webpage Models: application/models/project_messages Important: - The controller must extend the class ApplicationController - The model base classes must extend the classes ContentDataObjects and ContentDataObject The CSS files must be placed under plugins/objectives/public/assets/css The css file with the same name of the plugin will be included automatically at startup, you can add more files and import them in the plugins/objectives/public/assets/css/<plugin_name>.css The javascript files must be placed under plugins/objectives/public/assets/javascript The js file with the same name of the plugin will be included at startup In that file you need to create an object with below “og” and the same name of the plugin If you add the “init” function, it will be executed at startup, this is used to make some initializations if needed. Example: plugins/objectives/public/assets/javascript/objectives.js og.objectives = { init: function() { // your code here } } To build the list of objects to show in the tab you can create a new js file registering a new grid class. As an example you can see how it is done for “Notes” module public/assets/javascript/og/MessageManager.js See init function at application/controllers/MessageController.class.php to know how to include it Log In