diff --git a/SOC-Case-Management.xml b/SOC-Case-Management.xml new file mode 100644 index 0000000..9948a24 --- /dev/null +++ b/SOC-Case-Management.xml @@ -0,0 +1,9 @@ + + + + + 1.0.0 + ~10.0.0 + + + \ No newline at end of file diff --git a/hook.php b/hook.php index a1b1f4d..6e5724a 100644 --- a/hook.php +++ b/hook.php @@ -4,49 +4,156 @@ */ /** - * Hook called after a new item has been added + * Item display hook - adds tabs to items * - * @param CommonDBTM $item - * @return void + * @param CommonGLPI $item Object on which to add the tab + * @param integer $withtemplate + * @return array|string */ -function plugin_soc_item_add(CommonDBTM $item) { - // Add actions when a new item is created +function plugin_soc_getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { + if ($withtemplate) { + return ''; + } + + switch ($item->getType()) { + case 'Ticket': + if (Session::haveRight('plugin_soc_case', READ)) { + return PluginSocCase::getTypeName(Session::getPluralNumber()); + } + break; + + case 'Change': + if (Session::haveRight('plugin_soc_case', READ)) { + return PluginSocCase::getTypeName(Session::getPluralNumber()); + } + break; + } + + return ''; } /** - * Hook called after an item has been updated + * Item update hook * - * @param CommonDBTM $item + * @param CommonDBTM $item Item being updated * @return void */ function plugin_soc_item_update(CommonDBTM $item) { - // Add actions when an item is updated + // Handle item updates + switch ($item->getType()) { + case 'Ticket': + case 'Change': + // Custom update logic if needed + break; + } } /** - * Hook called after an item has been deleted + * Item add hook * - * @param CommonDBTM $item + * @param CommonDBTM $item Item being added + * @return void + */ +function plugin_soc_item_add(CommonDBTM $item) { + // Handle item additions + switch ($item->getType()) { + case 'Ticket': + case 'Change': + // Custom add logic if needed + break; + } +} + +/** + * Item delete hook + * + * @param CommonDBTM $item Item being deleted * @return void */ function plugin_soc_item_delete(CommonDBTM $item) { - // Add actions when an item is deleted + // Handle item deletions + switch ($item->getType()) { + case 'Ticket': + case 'Change': + // Custom delete logic if needed + break; + } } /** - * Hook called after an item has been purged + * Item purge hook * - * @param CommonDBTM $item + * @param CommonDBTM $item Item being purged * @return void */ function plugin_soc_item_purge(CommonDBTM $item) { - // Add actions when an item is purged + // Handle item purges + global $DB; + + switch ($item->getType()) { + case 'Ticket': + // Delete case-ticket relations + $DB->delete( + 'glpi_plugin_soc_case_tickets', + ['tickets_id' => $item->getID()] + ); + break; + + case 'Change': + // Delete case-change relations + $DB->delete( + 'glpi_plugin_soc_case_changes', + ['changes_id' => $item->getID()] + ); + break; + + case 'PluginSocCase': + // Delete related items + $DB->delete( + 'glpi_plugin_soc_case_tickets', + ['plugin_soc_cases_id' => $item->getID()] + ); + + $DB->delete( + 'glpi_plugin_soc_case_changes', + ['plugin_soc_cases_id' => $item->getID()] + ); + break; + } } /** - * Hook called when displaying the tabs for an item + * Display tab content for item * - * @param array $params + * @param CommonGLPI $item Object on which to display the tab + * @param integer $tabnum Tab number + * @param integer $withtemplate + * @return boolean + */ +function plugin_soc_displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { + if ($withtemplate) { + return false; + } + + switch ($item->getType()) { + case 'Ticket': + $case_ticket = new PluginSocCaseTicket(); + $case_ticket->showForTicket($item); + return true; + + case 'Change': + $case_change = new PluginSocCaseChange(); + $case_change->showForChange($item); + return true; + } + + return false; +} + +/** + * Hook to define additional search options for types + * + * @param string $itemtype Item type * @return array */ function plugin_soc_getAddSearchOptions($itemtype) { @@ -58,6 +165,27 @@ function plugin_soc_getAddSearchOptions($itemtype) { 'table' => 'glpi_plugin_soc_cases', 'field' => 'name', 'name' => __('SOC Case', 'soc'), + 'datatype' => 'itemlink', + 'itemlink_type' => 'PluginSocCase', + 'massiveaction' => false, + 'joinparams' => [ + 'beforejoin' => [ + 'table' => $itemtype == 'Ticket' ? 'glpi_plugin_soc_case_tickets' : 'glpi_plugin_soc_case_changes', + 'joinparams' => [ + 'jointype' => 'itemtype_item', + 'specific_itemtype' => $itemtype + ] + ] + ] + ]; + + $options[9001] = [ + 'table' => 'glpi_plugin_soc_cases', + 'field' => 'severity', + 'name' => __('SOC Case Severity', 'soc'), + 'datatype' => 'specific', + 'searchtype' => ['equals', 'notequals'], + 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ 'table' => $itemtype == 'Ticket' ? 'glpi_plugin_soc_case_tickets' : 'glpi_plugin_soc_case_changes', @@ -71,4 +199,170 @@ function plugin_soc_getAddSearchOptions($itemtype) { } return $options; +} + +/** + * Hook to add cron tasks + * + * @return array + */ +function plugin_soc_cron_info() { + return [ + 'soc' => [ + 'description' => __('Auto-close SOC cases', 'soc'), + 'parameter' => __('Delay in days', 'soc'), + 'state' => CronTask::STATE_WAITING, + 'mode' => CronTask::MODE_EXTERNAL, + 'frequency' => DAY_TIMESTAMP, + ] + ]; +} + +/** + * Execute cron task + * + * @param CronTask $task CronTask object + * @return integer + */ +function plugin_soc_cronSoc(CronTask $task) { + global $DB; + + // Get autoclose delay from config + $config = PluginSocConfig::getConfig(); + $delay = $config['autoclose_delay']; + + if ($delay <= 0) { + // Auto-closing is disabled + $task->log(__('Auto-closing SOC cases is disabled in plugin configuration.', 'soc')); + return 0; + } + + $time_limit = date('Y-m-d H:i:s', strtotime("-$delay days")); + + // Find resolved cases older than the delay + $cases = $DB->request([ + 'FROM' => 'glpi_plugin_soc_cases', + 'WHERE' => [ + 'status' => PluginSocCase::STATUS_RESOLVED, + 'date_mod' => ['<', $time_limit], + 'is_deleted' => 0 + ] + ]); + + $count = 0; + foreach ($cases as $case_data) { + $case = new PluginSocCase(); + $case->getFromDB($case_data['id']); + + // Update status to closed + $case->update([ + 'id' => $case_data['id'], + 'status' => PluginSocCase::STATUS_CLOSED + ]); + + $task->addVolume(1); + $count++; + } + + $task->log(sprintf(__('Closed %d SOC cases.', 'soc'), $count)); + + return ($count > 0) ? 1 : 0; +} + +/** + * Hook for database relations + * + * @return array + */ +function plugin_soc_getDatabaseRelations() { + return [ + 'glpi_entities' => [ + 'glpi_plugin_soc_cases' => 'entities_id' + ], + 'glpi_users' => [ + 'glpi_plugin_soc_cases' => 'users_id_tech' + ], + 'glpi_groups' => [ + 'glpi_plugin_soc_cases' => 'groups_id_tech' + ], + 'glpi_tickets' => [ + 'glpi_plugin_soc_case_tickets' => 'tickets_id' + ], + 'glpi_changes' => [ + 'glpi_plugin_soc_case_changes' => 'changes_id' + ], + 'glpi_plugin_soc_cases' => [ + 'glpi_plugin_soc_case_tickets' => 'plugin_soc_cases_id', + 'glpi_plugin_soc_case_changes' => 'plugin_soc_cases_id' + ] + ]; +} + +/** + * Hook for headings (used in massive actions) + * + * @param string $type Item type + * @return array + */ +function plugin_soc_getHaveItemtype($type) { + switch ($type) { + case 'PluginSocCase': + return ['Ticket' => PluginSocCase::getTypeName(Session::getPluralNumber()), + 'Change' => PluginSocCase::getTypeName(Session::getPluralNumber())]; + } + + return []; +} + +/** + * Hook for massive actions + * + * @param string $type Item type + * @return array + */ +function plugin_soc_getMassiveActions($type) { + switch ($type) { + case 'Ticket': + return [ + 'PluginSocCase:add_to_case' => __('Add to SOC case', 'soc'), + 'PluginSocCase:create_from_ticket' => __('Create SOC case from ticket', 'soc') + ]; + + case 'Change': + return [ + 'PluginSocCase:add_to_case' => __('Add to SOC case', 'soc'), + 'PluginSocCase:create_from_change' => __('Create SOC case from change', 'soc') + ]; + } + + return []; +} + +/** + * Define dropdown tables + * + * @return array + */ +function plugin_soc_getDropdowns() { + return [ + PluginSocCase::getTypeName(Session::getPluralNumber()) => [ + 'table' => 'glpi_plugin_soc_cases', + 'title' => PluginSocCase::getTypeName(Session::getPluralNumber()), + 'field' => 'name', + 'linkfield' => '', + ], + ]; +} + +/** + * Add event to notifications + * + * @return array + */ +function plugin_soc_getEvents() { + return [ + 'new_soc_case' => __('New SOC case', 'soc'), + 'update_soc_case' => __('SOC case updated', 'soc'), + 'close_soc_case' => __('SOC case closed', 'soc'), + ]; } \ No newline at end of file diff --git a/inc/casechange.class.php b/inc/casechange.class.php index 774825f..91d6adc 100644 --- a/inc/casechange.class.php +++ b/inc/casechange.class.php @@ -77,4 +77,101 @@ class PluginSocCaseChange extends CommonDBRelation { return $iterator; } + + /** + * Show cases for a change + * + * @param Change $change + * @return void + */ + function showForChange(Change $change) { + global $DB; + + $change_id = $change->getID(); + + if (!$change->can($change_id, READ)) { + return false; + } + + $cases = self::getCasesForChange($change_id); + $nb = count($cases); + + echo "
"; + + if ($nb > 0) { + echo ""; + + $header = ""; + $header .= ""; + $header .= ""; + $header .= ""; + $header .= ""; + $header .= ""; + + echo $header; + + foreach ($cases as $data) { + $case = new PluginSocCase(); + $case->getFromDB($data['id']); + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + + echo "
" . __('Name') . "" . __('Status') . "" . __('Severity', 'soc') . "" . __('Creation date') . "
".$data['name']."".$case->getStatusOptions()[$data['status']]."".$case->getSeverityOptions()[$data['severity']]."".Html::convDateTime($data['date_creation'])."
"; + } else { + echo "

".__('No SOC case associated with this change', 'soc')."

"; + } + + // If user has rights to create cases + if (Session::haveRight('plugin_soc_case', CREATE)) { + echo "
"; + echo ""; + echo __('Create SOC case from this change', 'soc'); + echo ""; + echo "
"; + } + + echo "
"; + } + + /** + * Show form for linking a change to a case + * + * @param integer $cases_id + * @param array $options + * @return void + */ + function showFormForCase($cases_id, $options = []) { + global $CFG_GLPI; + + $case = new PluginSocCase(); + $case->getFromDB($cases_id); + + echo "
"; + echo "
"; + echo ""; + echo ""; + + echo ""; + echo ""; + + echo ""; + echo ""; + + echo "
".__('Link a change to this case', 'soc')."
".__('Change').""; + + Change::dropdown(['name' => 'changes_id', 'entity' => $case->fields['entities_id']]); + + echo "
"; + echo ""; + echo ""; + echo "
"; + echo "
"; + Html::closeForm(); + } } \ No newline at end of file diff --git a/inc/caseticket.class.php b/inc/caseticket.class.php index 68b83c3..bcb1faa 100644 --- a/inc/caseticket.class.php +++ b/inc/caseticket.class.php @@ -77,4 +77,101 @@ class PluginSocCaseTicket extends CommonDBRelation { return $iterator; } + + /** + * Show cases for a ticket + * + * @param Ticket $ticket + * @return void + */ + function showForTicket(Ticket $ticket) { + global $DB; + + $ticket_id = $ticket->getID(); + + if (!$ticket->can($ticket_id, READ)) { + return false; + } + + $cases = self::getCasesForTicket($ticket_id); + $nb = count($cases); + + echo "
"; + + if ($nb > 0) { + echo ""; + + $header = ""; + $header .= ""; + $header .= ""; + $header .= ""; + $header .= ""; + $header .= ""; + + echo $header; + + foreach ($cases as $data) { + $case = new PluginSocCase(); + $case->getFromDB($data['id']); + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + + echo "
" . __('Name') . "" . __('Status') . "" . __('Severity', 'soc') . "" . __('Creation date') . "
".$data['name']."".$case->getStatusOptions()[$data['status']]."".$case->getSeverityOptions()[$data['severity']]."".Html::convDateTime($data['date_creation'])."
"; + } else { + echo "

".__('No SOC case associated with this ticket', 'soc')."

"; + } + + // If user has rights to create cases + if (Session::haveRight('plugin_soc_case', CREATE)) { + echo ""; + } + + echo "
"; + } + + /** + * Show form for linking a ticket to a case + * + * @param integer $cases_id + * @param array $options + * @return void + */ + function showFormForCase($cases_id, $options = []) { + global $CFG_GLPI; + + $case = new PluginSocCase(); + $case->getFromDB($cases_id); + + echo ""; + echo "
"; + echo ""; + echo ""; + + echo ""; + echo ""; + + echo ""; + echo ""; + + echo "
".__('Link a ticket to this case', 'soc')."
".__('Ticket').""; + + Ticket::dropdown(['name' => 'tickets_id', 'entity' => $case->fields['entities_id']]); + + echo "
"; + echo ""; + echo ""; + echo "
"; + echo "
"; + Html::closeForm(); + } } \ No newline at end of file diff --git a/inc/plugin_init_translations.php b/inc/plugin_init_translations.php new file mode 100644 index 0000000..17b4876 --- /dev/null +++ b/inc/plugin_init_translations.php @@ -0,0 +1,17 @@ +addTranslationFile('gettext', $locale_dir, $domain, $_SESSION['glpilanguage']); + } +} \ No newline at end of file diff --git a/setup.php b/setup.php index e6b8974..57ad92d 100644 --- a/setup.php +++ b/setup.php @@ -65,35 +65,51 @@ function plugin_init_soc() { global $PLUGIN_HOOKS; $PLUGIN_HOOKS['csrf_compliant']['soc'] = true; - $PLUGIN_HOOKS['menu_toadd']['soc'] = ['management' => 'PluginSocCase']; + + // Add JavaScript and CSS $PLUGIN_HOOKS['javascript']['soc'] = ['plugins/soc/js/soc.js']; $PLUGIN_HOOKS['add_css']['soc'] = ['plugins/soc/css/soc.css']; // Initialize translations + include_once(GLPI_ROOT . '/plugins/soc/inc/plugin_init_translations.php'); + $PLUGIN_HOOKS['init_translations']['soc'] = 'plugin_init_soc_translations'; + + // Register plugin classes Plugin::registerClass('PluginSocCase'); - + Plugin::registerClass('PluginSocProfile', ['addtabtypes' => ['Profile']]); + + // Add menu items if (Session::haveRight('plugin_soc_case', READ)) { $PLUGIN_HOOKS['menu_toadd']['soc'] = ['management' => 'PluginSocCase']; } // Add a tab to Changes if (Session::haveRight('change', READ)) { - Plugin::registerClass('PluginSocCase', [ - 'addtabtypes' => ['Change'] - ]); + $PLUGIN_HOOKS['add_tab']['soc'] = [ + 'Change' => 'PluginSocCase', + ]; } // Add a tab to Tickets if (Session::haveRight('ticket', READ)) { - Plugin::registerClass('PluginSocCase', [ - 'addtabtypes' => ['Ticket'] - ]); + $PLUGIN_HOOKS['add_tab']['soc'] = [ + 'Ticket' => 'PluginSocCase', + ]; } // Add config page if (Session::haveRight('config', UPDATE)) { $PLUGIN_HOOKS['config_page']['soc'] = 'front/config.form.php'; } + + // Hook for item actions + $PLUGIN_HOOKS['item_add']['soc'] = ['*' => 'plugin_soc_item_add']; + $PLUGIN_HOOKS['item_update']['soc'] = ['*' => 'plugin_soc_item_update']; + $PLUGIN_HOOKS['item_delete']['soc'] = ['*' => 'plugin_soc_item_delete']; + $PLUGIN_HOOKS['item_purge']['soc'] = ['*' => 'plugin_soc_item_purge']; + + // Display hooks + $PLUGIN_HOOKS['post_item_form']['soc'] = ['PluginSocCase', 'displayTabContentForItem']; } /**