From 3f629c0ace12d35e84751afa6117e48dca9f3ec7 Mon Sep 17 00:00:00 2001 From: Francisco Mancardi Date: Sun, 12 Jun 2016 09:48:49 +0200 Subject: [PATCH] TICKET 0007595: Event Signal System (by Collabnet) TICKET 0007596: Plugin System (by Collabnet) --- README.plugins | 68 +++ cfg/const.inc.php | 22 +- config.inc.php | 14 + gui/templates/mainPage.tpl | 35 +- gui/templates/mainPageLeft.tpl | 64 ++- gui/templates/mainPageRight.tpl | 65 ++- install/sql/mysql/testlink_create_tables.sql | 20 + lib/functions/common.php | 13 +- lib/functions/object.class.php | 6 +- lib/functions/plugin_api.php | 543 +++++++++++++++++++ lib/functions/testcase.class.php | 88 +-- lib/functions/testsuite.class.php | 17 +- lib/functions/tlPlugin.class.php | 100 ++++ lib/general/mainPage.php | 15 +- locale/en_GB/strings.txt | 2 +- plugin.php | 43 ++ plugins/TLTest/TLTest.php | 89 +++ plugins/TLTest/pages/config.php | 33 ++ plugins/TLTest/pages/config.tpl | 34 ++ 19 files changed, 1212 insertions(+), 59 deletions(-) create mode 100644 README.plugins create mode 100644 lib/functions/plugin_api.php create mode 100644 lib/functions/tlPlugin.class.php create mode 100644 plugin.php create mode 100644 plugins/TLTest/TLTest.php create mode 100644 plugins/TLTest/pages/config.php create mode 100644 plugins/TLTest/pages/config.tpl diff --git a/README.plugins b/README.plugins new file mode 100644 index 0000000000..feaf70fcc6 --- /dev/null +++ b/README.plugins @@ -0,0 +1,68 @@ +=============================================================== + TestLink - README.plugins +=============================================================== + +Writing Plugins: + +To create a new plugin, create a new folder with the name of the plugin under +"plugins" folder at the base dir, for e.g. TLTest as provided in the +distribution. This should be a unique named plugin in the system. The name of +the folder acts as the name of the plugin. + +The structure inside the plugin folder will be ++ plugins ++--< Name of Plugin > ++------ ++------ pages ++---------< Smarty Template files > ++---------< PHP Files to process Smarty templates> + +Plugin Main Class: +The plugin main class should reside directly under the plugins/ +folder. This class should extend from core TestlinkPlugin class and expose +the following properties +* name +* description +* version +* author +* url +These information will be displayed in the Plugins page, which can be used to +enable or disable a plugin in the current Testlink installation (This has not +yet been implemented). + +The plugin main class also needs to have the following methods: +* register: The basic information about the plugin including name, description, + version, author, url are maintained here. This method is mandatory +* config: Returns an array of the default configuration fields that are required + for the plugin. These values are stored in Globals against the plugin and act + as defaults whenever they are requested using plugin_config_get (See below) +* hooks: Returns an array of the hooks that this plugin listens to. The list of + hooks are defined in events_inc.php. The array value will be the method in the + plugin class that defines what needs to be done when that plugin happens. + +Utilities that will help in writing the plugin class and support files: +* $db : This variable will be available for the plugins to do any DB related + operations. The users can directly do a `doDBConnect($db)` to connect to the + $db and then use any of the methods available in database.class.php +* plugin_file_path: Function to get an absolute path to a file inside of the + plugin. For e.g. any reference to a file from one php file to another can be + done using this. This avoids the user understanding how to reference files + inside the plugin. +* plugin_config_set: Set a configuration value in the Database. The values are + stored in the plugins_configuration table. You can set a value at two levels: + Either at a testproject level or available to all test projects +* plugin_config_get: Get a configuration value from the database. The value will + be fetched from the requested testprojectId (Passed in as a paramter) and if its + not there, it will fetch a value available at "All projects" level. If its not + available there, then it picks it up from the Global Defaults (Setup through the + `config` method of the plugin) and then default to the "default" parameter provided + in the method definition. See `plugin_config_get` in `plugin_api.php` for details + +Writing Templates that will help in Plugin Configuration: +The user might want to create pages that will help in configuring a plugin at a +testproject level or for all projects. These template files will need to reside +inside `pages` folder. We have exported the tlSmarty class so that users can write +smarty templates. The php file to support the smarty templates are also inside the +`pages` folder. Please see config.php and config.tpl inside +plugins/TLTest/pages for an example + diff --git a/cfg/const.inc.php b/cfg/const.inc.php index 6782fa6bed..48d0f100fc 100644 --- a/cfg/const.inc.php +++ b/cfg/const.inc.php @@ -9,7 +9,7 @@ * @filesource const.inc.php * @package TestLink * @author Martin Havlat - * @copyright 2007-2015, TestLink community + * @copyright 2007-2016, TestLink community * @see config.inc.php * * @internal revisions @@ -35,7 +35,7 @@ // want to point to root install dir, need to remove fixed part if (!defined('TL_ABS_PATH')) { - define('TL_ABS_PATH', str_replace('cfg','',dirname(__FILE__))); + define('TL_ABS_PATH', str_replace('cfg','',dirname(__FILE__))); } /** Setting up the global include path for testlink */ @@ -181,6 +181,24 @@ define('REMOVEME', 'd8ba8cfb-ca92-4fa5-83c2-551977d405fb'); +/** Constants for plugins */ +/** Plugin configuration types */ +define('CONFIG_TYPE_STRING', 0); +define('CONFIG_TYPE_INT', 1); +define('CONFIG_TYPE_FLOAT', 2); +define('CONFIG_TYPE_COMPLEX', 3); + +/** To indicate the plugin configuration belongs to ANY PROJECT */ +define('TL_ANY_PROJECT', -1); + +/** Constants for events */ +define('EVENT_TYPE_CREATE', 1); +define('EVENT_TYPE_UPDATE', 2); +define('EVENT_TYPE_DELETE', 3); +define('EVENT_TYPE_OUTPUT', 4); + + + // -------------------------------------------------------------------------------------- /* [GUI] */ diff --git a/config.inc.php b/config.inc.php index 72a6a98370..6b211f422c 100644 --- a/config.inc.php +++ b/config.inc.php @@ -1661,6 +1661,8 @@ $tlCfg->proxy->password = null; +/** Plugins feature */ +define('TL_PLUGIN_PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR); // ----- End of Config ------------------------------------------------------------------ // -------------------------------------------------------------------------------------- @@ -1785,4 +1787,16 @@ $tlCfg->gui->title_separator_2 = $tlCfg->gui_title_separator_2; $tlCfg->gui->role_separator_open = $tlCfg->gui_separator_open; $tlCfg->gui->role_separator_close = $tlCfg->gui_separator_close; + + +/** + * Globals for Events storage + */ +$g_event_cache = array(); + +/** + * Globals for Plugins + */ +$g_plugin_config_cache = array(); + // ----- END OF FILE -------------------------------------------------------------------- \ No newline at end of file diff --git a/gui/templates/mainPage.tpl b/gui/templates/mainPage.tpl index f3c1d8e35d..92017de199 100644 --- a/gui/templates/mainPage.tpl +++ b/gui/templates/mainPage.tpl @@ -10,31 +10,41 @@ {include file="inc_ext_js.tpl"} diff --git a/gui/templates/mainPageLeft.tpl b/gui/templates/mainPageLeft.tpl index 3d6519897e..780135bb33 100644 --- a/gui/templates/mainPageLeft.tpl +++ b/gui/templates/mainPageLeft.tpl @@ -16,7 +16,7 @@ href_search_req, href_search_req_spec,href_inventory, href_platform_management, href_inventory_management, href_print_tc,href_keywords_assign, href_req_overview, - href_print_req, title_documentation,href_issuetracker_management, + href_print_req,title_plugins,title_documentation,href_issuetracker_management, href_reqmgrsystem_management,href_req_monitor_overview'} {$menuLayout=$tlCfg->gui->layoutMainPageLeft} @@ -27,6 +27,8 @@ {$display_left_block_3=false} {$display_left_block_4=false} {$display_left_block_5=$tlCfg->userDocOnDesktop} +{$display_left_block_top = false} +{$display_left_block_bottom = false} {if $gui->testprojectID && ($gui->grants.project_edit == "yes" || @@ -122,12 +124,20 @@ {/if} +{if $gui->plugins.EVENT_LEFTMENU_TOP } + {$display_left_block_top=true} +{/if} +{if $gui->plugins.EVENT_LEFTMENU_BOTTOM } + {$display_left_block_bottom=true} +{/if}
+




+

{if $display_left_block_1} @@ -235,6 +245,56 @@
{/if} + {if $display_left_block_top} + +
+ {foreach from=$gui->plugins.EVENT_LEFTMENU_TOP item=menu_item} + {$menu_item} +
+ {/foreach} +
+ {/if} + + + {if $display_left_block_bottom} + +
+ {foreach from=$gui->plugins.EVENT_LEFTMENU_BOTTOM item=menu_item} + {$menu_item} +
+ {/foreach} +
+ {/if} {if $display_left_block_5} +
+ {foreach from=$gui->plugins.EVENT_RIGHTMENU_TOP item=menu_item} + {$menu_item} +
+ {/foreach} +
+ {/if} + + {if $display_right_block_bottom} + +
+ {foreach from=$gui->plugins.EVENT_RIGHTMENU_BOTTOM item=menu_item} + {$menu_item} +
+ {/foreach} +
+ {/if} {* ------------------------------------------------------------------------------------------ *} diff --git a/install/sql/mysql/testlink_create_tables.sql b/install/sql/mysql/testlink_create_tables.sql index 528fcfd8ba..88da0cc229 100644 --- a/install/sql/mysql/testlink_create_tables.sql +++ b/install/sql/mysql/testlink_create_tables.sql @@ -713,4 +713,24 @@ CREATE TABLE /*prefix*/req_monitor ( `user_id` int(11) NOT NULL, `testproject_id` int(11) NOT NULL, PRIMARY KEY (`req_id`,`user_id`,`testproject_id`) +) DEFAULT CHARSET=utf8; + +CREATE TABLE /*prefix*/plugins ( + `id` int(11) NOT NULL auto_increment, + `basename` varchar(100) NOT NULL, + `enabled` tinyint(1) NOT NULL default '0', + `author_id` int(10) unsigned default NULL, + `creation_ts` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) DEFAULT CHARSET=utf8; + +CREATE TABLE /*prefix*/plugins_configuration ( + `id` int(11) NOT NULL auto_increment, + `testproject_id` int(11) NOT NULL, + `config_key` varchar(255) NOT NULL, + `config_type` int(11) NOT NULL, + `config_value` varchar(255) NOT NULL, + `author_id` int(10) unsigned default NULL, + `creation_ts` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/lib/functions/common.php b/lib/functions/common.php index a1a254692d..dca2748f69 100644 --- a/lib/functions/common.php +++ b/lib/functions/common.php @@ -13,7 +13,7 @@ * @filesource common.php * @package TestLink * @author TestLink community - * @Copyright 2005,2015 TestLink community + * @Copyright 2005,2016 TestLink community * @link http://www.testlink.org * @since 1.5 * @@ -43,6 +43,12 @@ /** Testlink Smarty class wrapper sets up the default smarty settings for testlink */ require_once('tlsmarty.inc.php'); +/** Initialize the Event System */ +require_once('event_api.php' ); + +/** Testlink Plugin API helper methods */ +require_once('plugin_api.php'); + // Needed to avoid problems with Smarty 3 spl_autoload_register('tlAutoload'); @@ -470,7 +476,10 @@ function testlinkInitPage(&$db, $initProject = FALSE, $dontCheckSession = false, { checkUserRightsFor($db,$userRightsCheckFunction,$onFailureGoToLogin); } - + + // Init plugins + plugin_init_installed(); + // adjust Product and Test Plan to $_SESSION if ($initProject) { diff --git a/lib/functions/object.class.php b/lib/functions/object.class.php index 48061e1f52..36d467977d 100644 --- a/lib/functions/object.class.php +++ b/lib/functions/object.class.php @@ -5,11 +5,11 @@ * * @filesource object.class.php * @package TestLink - * @copyright 2007-2015, TestLink community + * @copyright 2007-2016, TestLink community * @link http://www.testlink.org * * @internal revisions - * @since 1.9.14 + * @since 1.9.15 **/ /** @@ -269,6 +269,8 @@ static public function getDBTables($tableNames = null) 'nodes_hierarchy' => DB_TABLE_PREFIX . 'nodes_hierarchy', 'object_keywords' => DB_TABLE_PREFIX . 'object_keywords', 'platforms' => DB_TABLE_PREFIX . 'platforms', + 'plugins' => DB_TABLE_PREFIX . 'plugins', + 'plugins_configuration' => DB_TABLE_PREFIX . 'plugins_configuration', 'req_coverage' => DB_TABLE_PREFIX . 'req_coverage', 'req_relations' => DB_TABLE_PREFIX . 'req_relations', 'req_specs' => DB_TABLE_PREFIX . 'req_specs', diff --git a/lib/functions/plugin_api.php b/lib/functions/plugin_api.php new file mode 100644 index 0000000000..b360bbeaa4 --- /dev/null +++ b/lib/functions/plugin_api.php @@ -0,0 +1,543 @@ +prepare_string($full_option); + + $sql = "/* $debugMsg */ " . + " SELECT config_value FROM " . $tables['plugins_configuration'] . + " where config_key = '" . $full_option . "' AND testproject_id = "; + + $value = $dbHandler->fetchOneValue($sql . intval($project)); + + if (is_null($value) && $project != TL_ANY_PROJECT) + { + // Check if its in the Global Project + $value = $dbHandler->fetchOneValue($sql . TL_ANY_PROJECT); + } + + if (is_null($value)) + { + // Fetch from the Global list, and if not, fetch from default value + global $g_plugin_config_cache; + $value = array_key_exists($full_option, $g_plugin_config_cache) ? $g_plugin_config_cache[$full_option] : $default; + } + return $value; +} + +/** + * Set a plugin configuration option in the database. + * @param string Configuration option name + * @param multi Option value + * @param int User ID + * @param int Project ID + * @param int Access threshold + */ +function plugin_config_set($option, $value, $project = TL_ANY_PROJECT) +{ + doDBConnect($dbHandler); + $tables = tlObjectWithDB::getDBTables(array('plugins_configuration')); + $plugin_config_table = $tables['plugins_configuration']; + + $basename = plugin_get_current(); + $full_option = 'plugin_' . $basename . '_' . $option; + + if (is_array($value) || is_object($value)) + { + $config_type = CONFIG_TYPE_COMPLEX; + $value = serialize($value); + } + else if (is_float($value)) + { + $config_type = CONFIG_TYPE_FLOAT; + $value = (float)$value; + } + else if (is_int($value) || is_numeric($value)) + { + $config_type = CONFIG_TYPE_INT; + $value = $dbHandler->prepare_int($value); + } + else + { + $config_type = CONFIG_TYPE_STRING; + } + + + $safe_id = intval($project); + $sql = " SELECT COUNT(*) from $plugin_config_table " . + " WHERE config_key = '" . $dbHandler->prepare_string($full_option) . "' " . + " AND testproject_id = {$safe_id} "; + $rows_exist = $dbHandler->fetchOneValue($sql); + + if ($rows_exist > 0) + { + // Update the existing record + $sql = " UPDATE $plugin_config_table " . + " SET config_value = '" . $dbHandler->prepare_string($value) . "'," . + " config_type = " . $config_type . + " WHERE config_key = '" . $dbHandler->prepare_string($full_option) . "' " . + " AND testproject_id = {$safe_id} "; + } + else + { + // Insert new config value + $sql = " INSERT $plugin_config_table " . + " (config_key, config_type, config_value, testproject_id, author_id) " . + " VALUES (" . + "'" . $dbHandler->prepare_string($full_option) . "', " . + $config_type . "," . + "'" . $dbHandler->prepare_string($value) . "', " . + $safe_id . ", " . $_SESSION['currentUser']->dbID . ")"; + } + $dbHandler->exec_query($sql); +} + +/** + * Set plugin default values to global values without overriding anything. + * @param array Array of configuration option name/value pairs. + */ +function plugin_config_defaults($options) +{ + global $g_plugin_config_cache; + if (!is_array($options)) + { + return; + } + + $basename = plugin_get_current(); + $option_base = 'plugin_' . $basename . '_'; + + foreach ($options as $option => $value) + { + $full_option = $option_base . $option; + $g_plugin_config_cache[$full_option] = $value; + } +} + +/** + * Get a language string for the plugin. + * Automatically prepends plugin_ to the string requested. + * @param string Language string name + * @param string Plugin basename + * @return string Language string + */ +function plugin_lang_get($p_name, $p_basename = null) +{ + if (!is_null($p_basename)) + { + plugin_push_current($p_basename); + } + + $t_basename = plugin_get_current(); + $t_name = 'plugin_' . $t_basename . '_' . $p_name; + $t_string = lang_get($t_name); + + if (!is_null($p_basename)) + { + plugin_pop_current(); + } + return $t_string; +} + +/** + * Hook a plugin's callback function to an event. + * @param string Event name + * @param string Callback function + */ +function plugin_event_hook($p_name, $p_callback) +{ + $t_basename = plugin_get_current(); + event_hook($p_name, $p_callback, $t_basename); +} + +/** + * Hook multiple plugin callbacks at once. + * @param array Array of event name/callback key/value pairs + */ +function plugin_event_hook_many($p_hooks) +{ + if (!is_array($p_hooks)) + { + return; + } + + $t_basename = plugin_get_current(); + + foreach ($p_hooks as $t_event => $t_callbacks) + { + if (!is_array($t_callbacks)) + { + event_hook($t_event, $t_callbacks, $t_basename); + continue; + } + + foreach ($t_callbacks as $t_callback) + { + event_hook($t_event, $t_callback, $t_basename); + } + } +} + +# ## Plugin Management Helpers + +/** + * Checks if a given plugin has been registered and initialized, + * and returns a boolean value representing the "loaded" state. + * @param string Plugin basename + * @return boolean Plugin loaded + */ +function plugin_is_loaded($p_basename) +{ + global $g_plugin_cache_init; + + return (isset($g_plugin_cache_init[$p_basename]) && $g_plugin_cache_init[$p_basename]); +} + +# ## Plugin management functions +/** + * Determine if a given plugin is installed. + * @param string Plugin basename + * @return boolean True if plugin is installed + */ +function plugin_is_installed($p_basename) +{ + doDBConnect($dbHandler); + $tables = tlObjectWithDB::getDBTables(array('plugins')); + + $sql = " SELECT COUNT(*) count FROM {$tables['plugins']} " . + " WHERE basename='" . $dbHandler->prepare_string($p_basename) . "'"; + + $t_result = $dbHandler->exec_query($sql); + return (0 < $t_result['count']); +} + +/** + * Install a plugin to the database. + * @param string Plugin basename + */ +function plugin_install($p_plugin) +{ + $debugMsg = "Function: " . __FUNCTION__; + + if (plugin_is_installed($p_plugin->basename)) + { + trigger_error('Plugin ' . $p_plugin->basename . ' already installed', E_USER_WARNING); + return null; + } + + plugin_push_current($p_plugin->basename); + + if (!$p_plugin->install()) + { + plugin_pop_current($p_plugin->basename); + return null; + } + + doDBConnect($dbHandler); + $tables = tlObjectWithDB::getDBTables(array('plugins')); + $sql = "/* $debugMsg */ INSERT INTO {$tables['plugins']} (basename,enabled) " . + " VALUES ('" . $dbHandler->prepare_string($p_plugin->basename) . "',1)"; + $dbHandler->exec_query($sql); + + plugin_pop_current(); +} + +/** + * Uninstall a plugin from the database. + * @param string Plugin basename + */ +function plugin_uninstall($p_plugin) +{ + $debugMsg = "Function: " . __FUNCTION__; + + if (!plugin_is_installed($p_plugin->basename)) + { + return; + } + + doDBConnect($dbHandler); + $tables = tlObjectWithDB::getDBTables(array('plugins')); + $sql = "/* $debugMsg */ DELETE FROM {$tables['plugins']} " . + " WHERE basename='" . $dbHandler->prepare_string($p_plugin->basename) . "'"; + $dbHandler->exec_query($sql); + + plugin_push_current($p_plugin->basename); + + $p_plugin->uninstall(); + + plugin_pop_current(); +} + +# ## Core usage only. +/** + * Search the plugins directory for plugins. + * @return array Plugin basename/info key/value pairs. + */ +function plugin_find_all() +{ + $t_plugin_path = TL_PLUGIN_PATH; + + if ($t_dir = opendir($t_plugin_path)) + { + while (($t_file = readdir($t_dir)) !== false) + { + if ('.' == $t_file || '..' == $t_file) + { + continue; + } + if (is_dir($t_plugin_path . $t_file)) + { + $t_plugin = plugin_register($t_file, true); + + if (!is_null($t_plugin)) + { + $t_plugins[$t_file] = $t_plugin; + } + } + } + closedir($t_dir); + } + return $t_plugins; +} + +/** + * Load a plugin's core class file. + * @param string Plugin basename + */ +function plugin_include($p_basename) +{ + $t_plugin_file = TL_PLUGIN_PATH . $p_basename . DIRECTORY_SEPARATOR . $p_basename . '.php'; + + $t_included = false; + if (is_file($t_plugin_file)) + { + include_once($t_plugin_file); + $t_included = true; + } + + return $t_included; +} + +/** + * Register a plugin with TestLink. + * The plugin class must already be loaded before calling. + * @param string Plugin classname without 'Plugin' postfix + */ +function plugin_register($p_basename, $p_return = false) +{ + global $g_plugin_cache; + + if (!isset($g_plugin_cache[$p_basename])) + { + $t_classname = $p_basename . 'Plugin'; + + # Include the plugin script if the class is not already declared. + if (!class_exists($t_classname)) + { + if (!plugin_include($p_basename)) + { + return null; + } + } + + # Make sure the class exists and that it's of the right type. + if (class_exists($t_classname) && is_subclass_of($t_classname, 'TestlinkPlugin')) + { + plugin_push_current($p_basename); + + doDBConnect($dbHandler); + $t_plugin = new $t_classname($dbHandler, $p_basename); + + plugin_pop_current(); + + # Final check on the class + if (is_null($t_plugin->name) || is_null($t_plugin->version)) + { + return null; + } + + if ($p_return) + { + return $t_plugin; + } + else + { + $g_plugin_cache[$p_basename] = $t_plugin; + } + } + } + + return $g_plugin_cache[$p_basename]; +} + +/** + * Find and register all installed plugins. + */ +function plugin_register_installed() +{ + doDBConnect($dbHandler); + $tables = tlObjectWithDB::getDBTables(array('plugins')); + $sql = "/* debugMsg */ " . + " SELECT basename FROM {$tables['plugins']} WHERE enabled=1 "; + + $t_result = $dbHandler->exec_query($sql); + while ($t_row = $dbHandler->fetch_array($t_result)) + { + $t_basename = $t_row['basename']; + plugin_register($t_basename); + } +} + +/** + * Initialize all installed plugins. + */ +function plugin_init_installed() +{ + + global $g_plugin_cache, $g_plugin_current, $g_plugin_cache_init; + $g_plugin_cache = array(); + $g_plugin_current = array(); + $g_plugin_cache_init = array(); + + plugin_register_installed(); + + $t_plugins = array_keys($g_plugin_cache); + + foreach ($t_plugins as $t_basename) + { + plugin_init($t_basename); + } + +} + +/** + * Initialize a single plugin. + * @param string Plugin basename + * @return boolean True if plugin initialized, false otherwise. + */ +function plugin_init($p_basename) +{ + global $g_plugin_cache, $g_plugin_cache_init; + + $ret = false; + if (isset($g_plugin_cache[$p_basename])) + { + $t_plugin = $g_plugin_cache[$p_basename]; + + plugin_push_current($p_basename); + + # finish initializing the plugin + $t_plugin->__init(); + $g_plugin_cache_init[$p_basename] = true; + + plugin_pop_current(); + $ret = true; + } + return $ret; +} \ No newline at end of file diff --git a/lib/functions/testcase.class.php b/lib/functions/testcase.class.php index 9ee9b6cef2..d54dc91bcd 100644 --- a/lib/functions/testcase.class.php +++ b/lib/functions/testcase.class.php @@ -6,7 +6,7 @@ * @filesource testcase.class.php * @package TestLink * @author Francisco Mancardi (francisco.mancardi@gmail.com) - * @copyright 2005-2015, TestLink community + * @copyright 2005-2016, TestLink community * @link http://www.testlink.org/ * * @internal revisions @@ -19,6 +19,7 @@ require_once( dirname(__FILE__) . '/assignment_mgr.class.php' ); require_once( dirname(__FILE__) . '/attachments.inc.php' ); require_once( dirname(__FILE__) . '/users.inc.php' ); +require_once( dirname(__FILE__) . '/event_api.php'); /** list of supported format for Test case import/export */ $g_tcFormatStrings = array ("XML" => lang_get('the_format_tc_xml_import')); @@ -315,22 +316,27 @@ function create($parent_id,$name,$summary,$preconditions,$steps,$author_id, $ret['msg'] = $op['status_ok'] ? $ret['msg'] : $op['msg']; $ret['tcversion_id'] = $op['status_ok'] ? $op['id'] : -1; + + $ctx = array('test_suite_id' => $parent_id,'id' => $id,'name' => $name, + 'summary' => $summary,'preconditions' => $preconditions, + 'steps' => $steps,'author_id' => $author_id, + 'keywords_id' => $keywords_id, + 'order' => $tc_order, 'exec_type' => $execution_type, + 'importance' => $importance,'options' => $options); + event_signal('EVENT_TEST_CASE_CREATE', $ctx); } return $ret; } /* - 20061008 - franciscom - added [$check_duplicate_name] - [$action_on_duplicate_name] - - 20060725 - franciscom - interface changes - [$order] + [$check_duplicate_name] + [$action_on_duplicate_name] + [$order] - [$id] - 0 -> the id will be assigned by dbms - x -> this will be the id - Warning: no check is done before insert => can got error. + [$id] + 0 -> the id will be assigned by dbms + x -> this will be the id + Warning: no check is done before insert => can got error. return: $ret['id'] @@ -1131,6 +1137,14 @@ function update($id,$tcversion_id,$name,$summary,$preconditions,$steps, { $this->updateKeywordAssignment($id,$keywords_id); } + + $ctx = array('id' => $id,'version_id' => $tcversion_id,'name' => $name, + 'summary' => $summary,'preconditions' => $preconditions, + 'steps' => $steps,'user_id' => $user_id, + 'keywords_id' => $keywords_id,'order' => $tc_order, + 'exec_type' => $execution_type, 'importance' => $importance, + 'attr' => $attr,'options' => $opt); + event_signal('EVENT_TEST_CASE_UPDATE', $ctx); } return $ret; @@ -1250,46 +1264,48 @@ function check_link_and_exec_status($id) function delete($id,$version_id = self::ALL_VERSIONS) { $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__; - $children=null; - $do_it=true; + $children=null; + $do_it=true; - // I'm trying to speedup the next deletes - $sql="/* $debugMsg */ " . + // I'm trying to speedup the next deletes + $sql = "/* $debugMsg */ " . " SELECT NH_TCV.id AS tcversion_id, NH_TCSTEPS.id AS step_id " . " FROM {$this->tables['nodes_hierarchy']} NH_TCV " . " LEFT OUTER JOIN {$this->tables['nodes_hierarchy']} NH_TCSTEPS " . " ON NH_TCSTEPS.parent_id = NH_TCV.id "; - if($version_id == self::ALL_VERSIONS) + if($version_id == self::ALL_VERSIONS) + { + if( is_array($id) ) { - if( is_array($id) ) - { - $sql .= " WHERE NH_TCV.parent_id IN (" .implode(',',$id) . ") "; - } - else - { - $sql .= " WHERE NH_TCV.parent_id={$id} "; - } + $sql .= " WHERE NH_TCV.parent_id IN (" .implode(',',$id) . ") "; } else { - $sql .= " WHERE NH_TCV.parent_id={$id} AND NH_TCV.id = {$version_id}"; + $sql .= " WHERE NH_TCV.parent_id={$id} "; } + } + else + { + $sql .= " WHERE NH_TCV.parent_id={$id} AND NH_TCV.id = {$version_id}"; + } - $children_rs=$this->db->get_recordset($sql); - $do_it = !is_null($children_rs); - if($do_it) + $children_rs=$this->db->get_recordset($sql); + $do_it = !is_null($children_rs); + if($do_it) + { + foreach($children_rs as $value) { - foreach($children_rs as $value) - { - $children['tcversion'][]=$value['tcversion_id']; - $children['step'][]=$value['step_id']; - } - $this->_execution_delete($id,$version_id,$children); - $this->deleteAllRelations($id); - $this->_blind_delete($id,$version_id,$children); + $children['tcversion'][]=$value['tcversion_id']; + $children['step'][]=$value['step_id']; } + $this->_execution_delete($id,$version_id,$children); + $this->deleteAllRelations($id); + $this->_blind_delete($id,$version_id,$children); + } + $ctx = array('id' => $id); + event_signal('EVENT_TEST_CASE_DELETE', $ctx); return 1; } diff --git a/lib/functions/testsuite.class.php b/lib/functions/testsuite.class.php index 5db015ee94..f111d94e6f 100644 --- a/lib/functions/testsuite.class.php +++ b/lib/functions/testsuite.class.php @@ -6,17 +6,18 @@ * @filesource testsuite.class.php * @package TestLink * @author franciscom - * @copyright 2005-2015, TestLink community + * @copyright 2005-2016, TestLink community * @link http://www.testlink.org/ * * @internal revisions - * @since 1.9.14 + * @since 1.9.15 * */ /** include support for attachments */ require_once( dirname(__FILE__) . '/attachments.inc.php'); require_once( dirname(__FILE__) . '/files.inc.php'); +require_once( dirname(__FILE__) . '/event_api.php'); /** * Test Suite CRUD functionality @@ -196,6 +197,8 @@ function create($parent_id,$name,$details,$order=null, if ($result) { $ret['id'] = $tsuite_id; + $ctx = array('id' => $tsuite_id,'name' => $name,'details' => $details); + event_signal('EVENT_TEST_SUITE_CREATE', $ctx); } } @@ -239,6 +242,11 @@ function update($id, $name, $details, $parent_id=null, $node_order=null) if (!$result) { $ret['msg'] = $this->db->error_msg(); + } + else + { + $ctx = array('id' => $id,'name' => $name,'details' => $details); + event_signal('EVENT_TEST_SUITE_UPDATE', $ctx); } } else @@ -293,6 +301,11 @@ function delete($unsafe_id) $sql = "DELETE FROM {$this->tables['nodes_hierarchy']} " . "WHERE id={$id} AND node_type_id=" . $this->my_node_type; $result = $this->db->exec_query($sql); + if ($result) + { + $ctx = array('id' => $id); + event_signal('EVENT_TEST_SUITE_DELETE', $ctx); + } } diff --git a/lib/functions/tlPlugin.class.php b/lib/functions/tlPlugin.class.php new file mode 100644 index 0000000000..eac16d7c8a --- /dev/null +++ b/lib/functions/tlPlugin.class.php @@ -0,0 +1,100 @@ +db = $db; + parent::__construct($this->db); + + $this->basename = $p_basename; + $this->register(); + } + + final public function __init() + { + plugin_config_defaults($this->config()); + plugin_event_hook_many($this->hooks()); + + $this->init(); + } + +} diff --git a/lib/general/mainPage.php b/lib/general/mainPage.php index 5a47b6d7e2..b019b49eb8 100644 --- a/lib/general/mainPage.php +++ b/lib/general/mainPage.php @@ -14,7 +14,7 @@ * There is also some javascript that handles the form information. * * @internal revisions - * @since 1.9.10 + * @since 1.9.15 * **/ @@ -153,6 +153,19 @@ $gui->opt_requirements = isset($_SESSION['testprojectOptions']->requirementsEnabled) ? $_SESSION['testprojectOptions']->requirementsEnabled : null; + +$gui->plugins = array(); +foreach(array('EVENT_LEFTMENU_TOP', + 'EVENT_LEFTMENU_BOTTOM', + 'EVENT_RIGHTMENU_TOP', + 'EVENT_RIGHTMENU_BOTTOM') as $menu_item) +{ + if (!empty($menu_content = event_signal($menu_item))) + { + $gui->plugins[$menu_item] = $menu_content; + } +} + $smarty->assign('gui',$gui); $smarty->display('mainPage.tpl'); diff --git a/locale/en_GB/strings.txt b/locale/en_GB/strings.txt index d3088608aa..779e6fd1dc 100644 --- a/locale/en_GB/strings.txt +++ b/locale/en_GB/strings.txt @@ -2443,7 +2443,7 @@ $TLS_href_platform_management = "Platform Management"; $TLS_href_issuetracker_management = "Issue Tracker Management"; $TLS_href_reqmgrsystem_management = "Req. Management System Management"; $TLS_system_config = 'System'; - +$TLS_title_plugins = 'Plugins'; $TLS_href_search_req = "Search Requirements"; $TLS_href_search_req_spec = "Search Requirement Specifications"; diff --git a/plugin.php b/plugin.php new file mode 100644 index 0000000000..94da5c3c78 --- /dev/null +++ b/plugin.php @@ -0,0 +1,43 @@ +name = 'TLTest'; + $this->description = 'Test Plugin'; + + $this->version = '1.0'; + + $this->author = 'Testlink'; + $this->contact = 'raja@star-systems.in'; + $this->url = 'http://www.collab.net'; + } + + function config() + { + return array( + 'config1' => '', + 'config2' => 0 + ); + } + + function hooks() + { + $hooks = array( + 'EVENT_TEST_SUITE_CREATE' => 'testsuite_create', + 'EVENT_LEFTMENU_TOP' => 'top_link', + 'EVENT_LEFTMENU_BOTTOM' => 'bottom_link', + 'EVENT_RIGHTMENU_TOP' => 'right_top_link', + 'EVENT_RIGHTMENU_BOTTOM' => 'right_bottom_link' + ); + return $hooks; + } + + function testsuite_create($args) + { + $arg = func_get_args(); // To get all the arguments + $db = $this->db; // To show how to get a Database Connection + echo "This is a test"; + } + + function bottom_link() + { + return "Left Bottom Link"; + } + + function top_link() + { + return "Configure My Plugin"; + } + + function right_top_link() + { + return "Right Top Link"; + } + + function right_bottom_link() + { + return "Right Bottom Link"; + } + +} diff --git a/plugins/TLTest/pages/config.php b/plugins/TLTest/pages/config.php new file mode 100644 index 0000000000..8dbf594eb0 --- /dev/null +++ b/plugins/TLTest/pages/config.php @@ -0,0 +1,33 @@ +message = "Configuration Saved"; // Confirm message + + // Assign to Smarty + $smarty->assign('gui',$gui); + $smarty->display(plugin_file_path('config.tpl')); + return; +} + +$gui->headerMessage = "Sample Configuration"; +$gui->config1 = plugin_config_get('config1', '', $_SESSION['testprojectID']); +$gui->config2 = plugin_config_get('config2', '', $_SESSION['testprojectID']); + +$smarty->assign('gui',$gui); +$smarty->display(plugin_file_path('config.tpl')); \ No newline at end of file diff --git a/plugins/TLTest/pages/config.tpl b/plugins/TLTest/pages/config.tpl new file mode 100644 index 0000000000..0b79fd2c42 --- /dev/null +++ b/plugins/TLTest/pages/config.tpl @@ -0,0 +1,34 @@ +{* + Testlink Open Source Project - http://testlink.sourceforge.net/ + @filesource config.tpl + Purpose: smarty template - plugin configuration +*} + +{include 'inc_head.tpl'} +

Configuration for TLTest Plugin

+
+ {if $gui->message } +
+ {$gui->message} +
+ {/if} +

{$gui->headerMessage}

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
\ No newline at end of file