%PDF- %PDF-
Direktori : /var/www/projetos/suporte.iigd.com.br/src/ |
Current File : /var/www/projetos/suporte.iigd.com.br/src/CommonITILValidation.php |
<?php /** * --------------------------------------------------------------------- * * GLPI - Gestionnaire Libre de Parc Informatique * * http://glpi-project.org * * @copyright 2015-2024 Teclib' and contributors. * @copyright 2003-2014 by the INDEPNET Development Team. * @licence https://www.gnu.org/licenses/gpl-3.0.html * * --------------------------------------------------------------------- * * LICENSE * * This file is part of GLPI. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * * --------------------------------------------------------------------- */ use Glpi\Application\View\TemplateRenderer; use Glpi\RichText\RichText; /** * CommonITILValidation Class * * @since 0.85 **/ abstract class CommonITILValidation extends CommonDBChild { // From CommonDBTM public $auto_message_on_action = false; public static $log_history_add = Log::HISTORY_LOG_SIMPLE_MESSAGE; public static $log_history_update = Log::HISTORY_LOG_SIMPLE_MESSAGE; public static $log_history_delete = Log::HISTORY_LOG_SIMPLE_MESSAGE; const VALIDATE = 1024; // STATUS const NONE = 1; // none const WAITING = 2; // waiting const ACCEPTED = 3; // accepted const REFUSED = 4; // rejected public function getItilObjectItemType() { return str_replace('Validation', '', $this->getType()); } public static function getIcon() { return 'ti ti-thumb-up'; } public static function getCreateRights() { return [CREATE]; } public static function getPurgeRights() { return [PURGE]; } public static function getValidateRights() { return [static::VALIDATE]; } public function getForbiddenStandardMassiveAction() { $forbidden = parent::getForbiddenStandardMassiveAction(); $forbidden[] = 'update'; return $forbidden; } public static function getTypeName($nb = 0) { return _n('Approval', 'Approvals', $nb); } public static function canCreate() { return Session::haveRightsOr(static::$rightname, static::getCreateRights()); } /** * Is the current user have right to delete the current validation ? * * @return boolean **/ public function canCreateItem() { if ( ($this->fields["users_id"] == Session::getLoginUserID()) || Session::haveRightsOr(static::$rightname, static::getCreateRights()) ) { return true; } return false; } public static function canView() { return Session::haveRightsOr( static::$rightname, array_merge( static::getCreateRights(), static::getValidateRights(), static::getPurgeRights() ) ); } public static function canUpdate() { return Session::haveRightsOr( static::$rightname, array_merge( static::getCreateRights(), static::getValidateRights() ) ); } /** * Is the current user have right to delete the current validation ? * * @return boolean **/ public function canDeleteItem() { if ( ($this->fields["users_id"] == Session::getLoginUserID()) || Session::haveRight(static::$rightname, DELETE) ) { return true; } return false; } /** * Is the current user have right to update the current validation ? * * @return boolean */ public function canUpdateItem() { if ( !Session::haveRightsOr(static::$rightname, static::getCreateRights()) && ($this->fields["users_id_validate"] != Session::getLoginUserID()) ) { return false; } return true; } /** * @param integer $items_id ID of the item **/ public static function canValidate($items_id) { /** @var \DBmysql $DB */ global $DB; $iterator = $DB->request([ 'SELECT' => ['users_id_validate'], 'FROM' => static::getTable(), 'WHERE' => [ static::$items_id => $items_id, 'users_id_validate' => Session::getLoginUserID() ], 'START' => 0, 'LIMIT' => 1 ]); if (count($iterator) > 0) { return true; } return false; } public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { /** @var CommonDBTM $item */ $hidetab = false; // Hide if no rights on validations if (!static::canView()) { $hidetab = true; } // No right to create and no validation for current object if ( !$hidetab && !Session::haveRightsOr(static::$rightname, static::getCreateRights()) && !static::canValidate($item->getID()) ) { $hidetab = true; } if (!$hidetab) { $nb = 0; if ($_SESSION['glpishow_count_on_tabs']) { $restrict = [static::$items_id => $item->getID()]; // No rights for create only count asign ones if (!Session::haveRightsOr(static::$rightname, static::getCreateRights())) { $restrict['users_id_validate'] = Session::getLoginUserID(); } $nb = countElementsInTable(static::getTable(), $restrict); } return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb); } return ''; } public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { $validation = new static(); $validation->showSummary($item); return true; } public function post_getEmpty() { $this->fields["users_id"] = Session::getLoginUserID(); $this->fields["status"] = self::WAITING; } public function prepareInputForAdd($input) { $input["users_id"] = 0; // Only set requester on manual action if ( !isset($input['_auto_import']) && !isset($input['_auto_update']) && !Session::isCron() ) { $input["users_id"] = Session::getLoginUserID(); } $input["submission_date"] = $_SESSION["glpi_currenttime"]; $input["status"] = self::WAITING; if (!isset($input["users_id_validate"]) || ($input["users_id_validate"] <= 0)) { return false; } $itemtype = static::$itemtype; $input['timeline_position'] = $itemtype::getTimelinePosition($input[static::$items_id], $this->getType(), $input["users_id"]); return parent::prepareInputForAdd($input); } public function post_addItem() { /** @var array $CFG_GLPI */ global $CFG_GLPI; // Handle rich-text images foreach (['comment_submission', 'comment_validation'] as $content_field) { $this->input = $this->addFiles($this->input, [ 'force_update' => true, 'name' => $content_field, 'content_field' => $content_field, ]); } // Handle uploaded documents $this->input = $this->addFiles($this->input); $item = new static::$itemtype(); $mailsend = false; if ($item->getFromDB($this->fields[static::$items_id])) { // Set global validation to waiting if ( ($item->fields['global_validation'] == self::ACCEPTED) || ($item->fields['global_validation'] == self::NONE) ) { $input = [ 'id' => $this->fields[static::$items_id], 'global_validation' => self::WAITING, '_from_itilvalidation' => true, ]; // to fix lastupdater if (isset($this->input['_auto_update'])) { $input['_auto_update'] = $this->input['_auto_update']; } // to know update by rules if (isset($this->input["_rule_process"])) { $input['_rule_process'] = $this->input["_rule_process"]; } // No update ticket notif on ticket add if (isset($this->input["_ticket_add"])) { $input['_disablenotif'] = true; } $item->update($input); } if (!isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"]) { $options = ['validation_id' => $this->fields["id"], 'validation_status' => $this->fields["status"] ]; $mailsend = NotificationEvent::raiseEvent('validation', $item, $options); } if ($mailsend) { $user = new User(); $user->getFromDB($this->fields["users_id_validate"]); $email = $user->getDefaultEmail(); if (!empty($email)) { Session::addMessageAfterRedirect(sprintf(__('Approval request sent to %s'), $user->getName())); } else { Session::addMessageAfterRedirect( sprintf( __('The selected user (%s) has no valid email address. The request has been created, without email confirmation.'), $user->getName() ), false, ERROR ); } } } parent::post_addItem(); } public function prepareInputForUpdate($input) { $forbid_fields = []; if ($this->fields["users_id_validate"] == Session::getLoginUserID() && isset($input["status"])) { if ( ($input["status"] == self::REFUSED) && (!isset($input["comment_validation"]) || ($input["comment_validation"] == '')) ) { Session::addMessageAfterRedirect( __('If approval is denied, specify a reason.'), false, ERROR ); return false; } if ($input["status"] == self::WAITING) { // $input["comment_validation"] = ''; $input["validation_date"] = 'NULL'; } else { $input["validation_date"] = $_SESSION["glpi_currenttime"]; } $forbid_fields = ['entities_id', 'users_id', static::$items_id, 'users_id_validate', 'comment_submission', 'submission_date', 'is_recursive' ]; } else if (Session::haveRightsOr(static::$rightname, $this->getCreateRights())) { // Update validation request $forbid_fields = ['entities_id', static::$items_id, 'status', 'comment_validation', 'validation_date', 'is_recursive' ]; } if (count($forbid_fields)) { foreach (array_keys($forbid_fields) as $key) { if (isset($input[$key])) { unset($input[$key]); } } } return parent::prepareInputForUpdate($input); } public function post_updateItem($history = true) { /** @var array $CFG_GLPI */ global $CFG_GLPI; $item = new static::$itemtype(); $donotif = $CFG_GLPI["use_notifications"]; if (isset($this->input['_disablenotif'])) { $donotif = false; } // Handle rich-text images foreach (['comment_submission', 'comment_validation'] as $content_field) { $this->input = $this->addFiles($this->input, [ 'force_update' => true, 'name' => $content_field, 'content_field' => $content_field, ]); } // Handle uploaded documents $this->input = $this->addFiles($this->input); if ($item->getFromDB($this->fields[static::$items_id])) { if ( count($this->updates) && $donotif ) { $options = ['validation_id' => $this->fields["id"], 'validation_status' => $this->fields["status"] ]; NotificationEvent::raiseEvent('validation_answer', $item, $options); } //if status is updated, update global approval status if (in_array("status", $this->updates)) { $input = [ 'id' => $this->fields[static::$items_id], 'global_validation' => self::computeValidationStatus($item), '_from_itilvalidation' => true, ]; $item->update($input); } } parent::post_updateItem($history); } public function pre_deleteItem() { $item = new static::$itemtype(); if ($item->getFromDB($this->fields[static::$items_id])) { if (($item->fields['global_validation'] == self::WAITING)) { $input = [ 'id' => $this->fields[static::$items_id], 'global_validation' => self::NONE, '_from_itilvalidation' => true, ]; $item->update($input); } } return true; } /** * @see CommonDBConnexity::getHistoryChangeWhenUpdateField **/ public function getHistoryChangeWhenUpdateField($field) { $result = []; if ($field == 'status') { $username = getUserName($this->fields["users_id_validate"]); $result = ['0', '', '']; if ($this->fields["status"] == self::ACCEPTED) { //TRANS: %s is the username $result[2] = sprintf(__('Approval granted by %s'), $username); } else { //TRANS: %s is the username $result[2] = sprintf(__('Update the approval request to %s'), $username); } } return $result; } /** * @see CommonDBChild::getHistoryNameForItem **/ public function getHistoryNameForItem(CommonDBTM $item, $case) { $username = getUserName($this->fields["users_id_validate"]); switch ($case) { case 'add': return sprintf(__('Approval request sent to %s'), $username); case 'delete': return sprintf(__('Cancel the approval request to %s'), $username); } return ''; } /** * get the Ticket validation status list * * @param $withmetaforsearch boolean (false by default) * @param $global boolean (true for global status, with "no validation" option) * (false by default) * * @return array **/ public static function getAllStatusArray($withmetaforsearch = false, $global = false) { $tab = [ self::WAITING => __('Waiting for approval'), self::REFUSED => _x('validation', 'Refused'), self::ACCEPTED => __('Granted') ]; if ($global) { $tab[self::NONE] = __('Not subject to approval'); if ($withmetaforsearch) { $tab['can'] = __('Granted + Not subject to approval'); } } if ($withmetaforsearch) { $tab['all'] = __('All'); } return $tab; } /** * Dropdown of validation status * * @param string $name select name * @param array $options possible options: * - value : default value (default waiting) * - all : boolean display all (default false) * - global : for global validation (default false) * - display : boolean display or get string ? (default true) * * @return string|integer Output string if display option is set to false, * otherwise random part of dropdown id **/ public static function dropdownStatus($name, $options = []) { $p = [ 'value' => self::WAITING, 'global' => false, 'all' => false, 'display' => true, 'disabled' => false, 'templateResult' => "templateValidation", 'templateSelection' => "templateValidation", 'width' => '100%', 'required' => false, ]; if (is_array($options) && count($options)) { foreach ($options as $key => $val) { $p[$key] = $val; } } $tab = self::getAllStatusArray($p['all'], $p['global']); unset($p['all']); unset($p['global']); return Dropdown::showFromArray($name, $tab, $p); } /** * Get Ticket validation status Name * * @param integer $value * @param bool $decorated **/ public static function getStatus($value, bool $decorated = false) { $statuses = self::getAllStatusArray(true, true); $label = $statuses[$value] ?? $value; if ($decorated) { $color = self::getStatusColor($value); $classes = null; switch ($value) { case self::WAITING: $classes = 'waiting far fa-clock'; break; case self::ACCEPTED: $classes = 'accepted fas fa-check'; break; case self::REFUSED: $classes = 'refused fas fa-times'; break; } return sprintf('<span><i class="validationstatus %s"></i> %s</span>', $classes, $label); } return $label; } /** * Get Ticket validation status Color * * @param integer $value status ID **/ public static function getStatusColor($value) { switch ($value) { case self::WAITING: $style = "#FFC65D"; break; case self::REFUSED: $style = "#cf9b9b"; break; case self::ACCEPTED: $style = "#9BA563"; break; default: $style = "#cf9b9b"; } return $style; } /** * Get item validation demands count for a user * * @param $users_id integer User ID **/ public static function getNumberToValidate($users_id) { /** @var \DBmysql $DB */ global $DB; $row = $DB->request([ 'FROM' => static::getTable(), 'COUNT' => 'cpt', 'WHERE' => [ 'status' => self::WAITING, 'users_id_validate' => $users_id ] ])->current(); return $row['cpt']; } /** * Get the number of validations attached to an item having a specified status * * @param integer $items_id item ID * @param integer $status status **/ public static function getTicketStatusNumber($items_id, $status) { /** @var \DBmysql $DB */ global $DB; $row = $DB->request([ 'FROM' => static::getTable(), 'COUNT' => 'cpt', 'WHERE' => [ static::$items_id => $items_id, 'status' => $status ] ])->current(); return $row['cpt']; } /** * Check if validation already exists * * @param $items_id integer item ID * @param $users_id integer user ID * * @since 0.85 * * @return boolean **/ public static function alreadyExists($items_id, $users_id) { /** @var \DBmysql $DB */ global $DB; $iterator = $DB->request([ 'FROM' => static::getTable(), 'WHERE' => [ static::$items_id => $items_id, 'users_id_validate' => $users_id ], 'START' => 0, 'LIMIT' => 1 ]); if (count($iterator) > 0) { return true; } return false; } /** * Form for Followup on Massive action **/ public static function showFormMassiveAction() { /** @var array $CFG_GLPI */ global $CFG_GLPI; $types = ['user' => User::getTypeName(1), 'group' => Group::getTypeName(1) ]; $rand = Dropdown::showFromArray( "validatortype", $types, ['display_emptychoice' => true] ); $paramsmassaction = ['validatortype' => '__VALUE__', 'entity' => $_SESSION['glpiactive_entity'], 'right' => ['validate_request', 'validate_incident'] ]; Ajax::updateItemOnSelectEvent( "dropdown_validatortype$rand", "show_massiveaction_field", $CFG_GLPI["root_doc"] . "/ajax/dropdownMassiveActionAddValidator.php", $paramsmassaction ); echo "<br><span id='show_massiveaction_field'> </span>\n"; } /** * @since 0.85 * * @see CommonDBTM::showMassiveActionsSubForm() **/ public static function showMassiveActionsSubForm(MassiveAction $ma) { switch ($ma->getAction()) { case 'submit_validation': static::showFormMassiveAction(); return true; } return parent::showMassiveActionsSubForm($ma); } /** * @since 0.85 * * @see CommonDBTM::processMassiveActionsForOneItemtype() **/ public static function processMassiveActionsForOneItemtype( MassiveAction $ma, CommonDBTM $item, array $ids ) { switch ($ma->getAction()) { case 'submit_validation': $input = $ma->getInput(); $valid = new static(); foreach ($ids as $id) { if ($item->getFromDB($id)) { $input2 = [static::$items_id => $id, 'comment_submission' => $input['comment_submission'] ]; if ($valid->can(-1, CREATE, $input2)) { $users = $input['users_id_validate']; if (!is_array($users)) { $users = [$users]; } $ok = true; foreach ($users as $user) { $input2["users_id_validate"] = $user; if (!$valid->add($input2)) { $ok = false; } } if ($ok) { $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK); } else { $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO); $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION)); } } else { $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT); $ma->addMessage($item->getErrorMessage(ERROR_RIGHT)); } } else { $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO); $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND)); } } return; } parent::processMassiveActionsForOneItemtype($ma, $item, $ids); } /** * Print the validation list into item * * @param CommonDBTM $item **/ public function showSummary(CommonDBTM $item) { /** * @var array $CFG_GLPI * @var \DBmysql $DB */ global $CFG_GLPI, $DB; if ( !Session::haveRightsOr( static::$rightname, array_merge( static::getCreateRights(), static::getValidateRights(), static::getPurgeRights() ) ) ) { return false; } $tID = $item->fields['id']; $tmp = [static::$items_id => $tID]; $canadd = $this->can(-1, CREATE, $tmp); $rand = mt_rand(); if ($canadd) { $itemtype = static::$itemtype; echo "<form method='post' name=form action='" . $itemtype::getFormURL() . "'>"; } echo "<table class='tab_cadre_fixe'>"; echo "<tr>"; echo "<th colspan='3'>" . self::getTypeName(Session::getPluralNumber()) . "</th>"; echo "</tr>"; echo "<tr class='tab_bg_1'>"; echo "<td>" . __('Global approval status') . "</td>"; echo "<td colspan='2'>"; echo TicketValidation::getStatus($item->fields["global_validation"], true); echo "</td></tr>"; echo "<tr>"; echo "<th colspan='2'>" . _x('item', 'State') . "</th>"; echo "<th colspan='2'>"; echo self::getValidationStats($tID); echo "</th>"; echo "</tr>"; echo "<tr class='tab_bg_1'>"; echo "<td>" . __('Minimum validation required') . "</td>"; if ($canadd) { echo "<td>"; echo $item->getValueToSelect( 'validation_percent', 'validation_percent', $item->fields["validation_percent"] ); echo "</td>"; echo "<td><input type='submit' name='update' class='btn btn-outline-secondary' value='" . _sx('button', 'Save') . "'>"; if (!empty($tID)) { echo "<input type='hidden' name='id' value='$tID'>"; } echo "</td>"; } else { echo "<td colspan='2'>"; echo Dropdown::getValueWithUnit($item->fields["validation_percent"], "%"); echo "</td>"; } echo "</tr>"; echo "</table>"; if ($canadd) { Html::closeForm(); } $iterator = $DB->Request([ 'FROM' => $this->getTable(), 'WHERE' => [static::$items_id => $item->getField('id')], 'ORDER' => 'submission_date DESC' ]); $colonnes = ['', _x('item', 'State'), __('Request date'), __('Approval requester'), __('Request comments'), __('Approval status'), __('Approver'), __('Approval comments'), __('Documents') ]; $nb_colonnes = count($colonnes); echo "<table class='tab_cadre_fixehov'>"; echo "<tr class='noHover'><th colspan='" . $nb_colonnes . "'>" . __('Approvals for the ticket') . "</th></tr>"; if ($canadd) { /** @var CommonITILObject $item */ if ( !in_array($item->fields['status'], array_merge( $item->getSolvedStatusArray(), $item->getClosedStatusArray() )) ) { echo "<tr class='tab_bg_1 noHover'><td class='center' colspan='" . $nb_colonnes . "'>"; echo "<a class='btn btn-outline-secondary' href='javascript:viewAddValidation" . $tID . "$rand();'>"; echo __('Send an approval request') . "</a></td></tr>\n"; } } if (count($iterator)) { $header = "<tr>"; foreach ($colonnes as $colonne) { $header .= "<th>" . $colonne . "</th>"; } $header .= "</tr>"; echo $header; Session::initNavigateListItems( $this->getType(), //TRANS : %1$s is the itemtype name, %2$s is the name of the item (used for headings of a list) sprintf( __('%1$s = %2$s'), $item->getTypeName(1), $item->fields["name"] ) ); foreach ($iterator as $row) { $canedit = $this->canEdit($row["id"]); Session::addToNavigateListItems($this->getType(), $row["id"]); $bgcolor = self::getStatusColor($row['status']); $status = self::getStatus($row['status']); echo "<tr class='tab_bg_1'>"; echo "<td>"; if ($canedit) { echo "<span class='far fa-edit' style='cursor:pointer' title='" . __('Edit') . "' "; echo "onClick=\"viewEditValidation" . $item->fields['id'] . $row["id"] . "$rand();\""; echo " id='viewvalidation" . $this->fields[static::$items_id] . $row["id"] . "$rand'"; echo "></span>"; echo "\n<script type='text/javascript' >\n"; echo "function viewEditValidation" . $item->fields['id'] . $row["id"] . "$rand() {\n"; $params = ['type' => $this->getType(), 'parenttype' => static::$itemtype, static::$items_id => $this->fields[static::$items_id], 'id' => $row["id"] ]; Ajax::updateItemJsCode( "viewvalidation" . $item->fields['id'] . "$rand", $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php", $params ); echo "};"; echo "</script>\n"; } echo "</td>"; echo "<td><div style='background-color:" . $bgcolor . ";'>" . $status . "</div></td>"; echo "<td>" . Html::convDateTime($row["submission_date"]) . "</td>"; echo "<td>" . getUserName($row["users_id"]) . "</td>"; $comment_submission = RichText::getEnhancedHtml($this->fields['comment_submission'], ['images_gallery' => true]); echo "<td><div class='rich_text_container'>" . $comment_submission . "</div></td>"; echo "<td>" . Html::convDateTime($row["validation_date"]) . "</td>"; echo "<td>" . getUserName($row["users_id_validate"]) . "</td>"; $comment_validation = RichText::getEnhancedHtml($this->fields['comment_validation'] ?? '', ['images_gallery' => true]); echo "<td><div class='rich_text_container'>" . $comment_validation . "</div></td>"; $doc_item = new Document_Item(); $docs = $doc_item->find(["itemtype" => $this->getType(), "items_id" => $this->getID(), "timeline_position" => ['>', CommonITILObject::NO_TIMELINE] ]); $out = ""; foreach ($docs as $docs_values) { $doc = new Document(); $doc->getFromDB($docs_values['documents_id']); $out .= "<a "; $out .= "href=\"" . Document::getFormURLWithID($docs_values['documents_id']) . "\">"; $out .= $doc->getField('name') . "</a><br>"; } echo "<td>" . $out . "</td>"; echo "</tr>"; } echo $header; } else { //echo "<div class='center b'>".__('No item found')."</div>"; echo "<tr class='tab_bg_1 noHover'><th colspan='" . $nb_colonnes . "'>"; echo __('No item found') . "</th></tr>\n"; } echo "</table>"; echo "<div id='viewvalidation" . $tID . "$rand'></div>\n"; if ($canadd) { echo "<script type='text/javascript' >\n"; echo "function viewAddValidation" . $tID . "$rand() {\n"; $params = ['type' => $this->getType(), 'parenttype' => static::$itemtype, static::$items_id => $tID, 'id' => -1 ]; Ajax::updateItemJsCode( "viewvalidation" . $tID . "$rand", $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php", $params ); echo "};"; echo "</script>"; } } /** * Print the validation form * * @param $ID integer ID of the item * @param $options array options used **/ public function showForm($ID, array $options = []) { if ($ID > 0) { $this->canEdit($ID); } else { $options[static::$items_id] = $options['parent']->fields["id"]; $this->check(-1, CREATE, $options); } TemplateRenderer::getInstance()->display('components/itilobject/timeline/form_validation.html.twig', [ 'item' => $options['parent'], 'subitem' => $this, 'scroll' => true, ]); return true; } public function rawSearchOptions() { $tab = []; $tab[] = [ 'id' => 'common', 'name' => CommonITILValidation::getTypeName(1) ]; $tab[] = [ 'id' => '1', 'table' => $this->getTable(), 'field' => 'comment_submission', 'name' => __('Request comments'), 'datatype' => 'text', 'htmltext' => true ]; $tab[] = [ 'id' => '2', 'table' => $this->getTable(), 'field' => 'comment_validation', 'name' => __('Approval comments'), 'datatype' => 'text', 'htmltext' => true ]; $tab[] = [ 'id' => '3', 'table' => $this->getTable(), 'field' => 'status', 'name' => __('Status'), 'searchtype' => 'equals', 'datatype' => 'specific' ]; $tab[] = [ 'id' => '4', 'table' => $this->getTable(), 'field' => 'submission_date', 'name' => __('Request date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '5', 'table' => $this->getTable(), 'field' => 'validation_date', 'name' => __('Approval date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '6', 'table' => 'glpi_users', 'field' => 'name', 'name' => __('Approval requester'), 'datatype' => 'itemlink', 'right' => [ 'create_incident_validation', 'create_request_validation' ] ]; $tab[] = [ 'id' => '7', 'table' => 'glpi_users', 'field' => 'name', 'linkfield' => 'users_id_validate', 'name' => __('Approver'), 'datatype' => 'itemlink', 'right' => [ 'validate_request', 'validate_incident' ] ]; return $tab; } public static function rawSearchOptionsToAdd() { $tab = []; $tab[] = [ 'id' => 'validation', 'name' => CommonITILValidation::getTypeName(1) ]; $tab[] = [ 'id' => '51', 'table' => getTableForItemType(static::$itemtype), 'field' => 'validation_percent', 'name' => __('Minimum validation required'), 'datatype' => 'number', 'unit' => '%', 'min' => 0, 'max' => 100, 'step' => 50 ]; $tab[] = [ 'id' => '52', 'table' => getTableForItemType(static::$itemtype), 'field' => 'global_validation', 'name' => CommonITILValidation::getTypeName(1), 'searchtype' => 'equals', 'datatype' => 'specific' ]; $tab[] = [ 'id' => '53', 'table' => static::getTable(), 'field' => 'comment_submission', 'name' => __('Request comments'), 'datatype' => 'text', 'htmltext' => true, 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'child' ] ]; $tab[] = [ 'id' => '54', 'table' => static::getTable(), 'field' => 'comment_validation', 'name' => __('Approval comments'), 'datatype' => 'text', 'htmltext' => true, 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'child' ] ]; $tab[] = [ 'id' => '55', 'table' => static::getTable(), 'field' => 'status', 'datatype' => 'specific', 'name' => __('Approval status'), 'searchtype' => 'equals', 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'child' ] ]; $tab[] = [ 'id' => '56', 'table' => static::getTable(), 'field' => 'submission_date', 'name' => __('Request date'), 'datatype' => 'datetime', 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'child' ] ]; $tab[] = [ 'id' => '57', 'table' => static::getTable(), 'field' => 'validation_date', 'name' => __('Approval date'), 'datatype' => 'datetime', 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'child' ] ]; $tab[] = [ 'id' => '58', 'table' => 'glpi_users', 'field' => 'name', 'name' => _n('Requester', 'Requesters', 1), 'datatype' => 'itemlink', 'right' => (static::$itemtype == 'Ticket' ? 'create_ticket_validate' : 'create_validate'), 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ 'table' => static::getTable(), 'joinparams' => [ 'jointype' => 'child' ] ] ] ]; $tab[] = [ 'id' => '59', 'table' => 'glpi_users', 'field' => 'name', 'linkfield' => 'users_id_validate', 'name' => __('Approver'), 'datatype' => 'itemlink', 'right' => (static::$itemtype == 'Ticket' ? ['validate_request', 'validate_incident'] : 'validate' ), 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ 'table' => static::getTable(), 'joinparams' => [ 'jointype' => 'child' ] ] ] ]; return $tab; } /** * @param $field * @param $values * @param $options array **/ public static function getSpecificValueToDisplay($field, $values, array $options = []) { if (!is_array($values)) { $values = [$field => $values]; } switch ($field) { case 'status': return self::getStatus($values[$field]); } return parent::getSpecificValueToDisplay($field, $values, $options); } /** * @param $field * @param $name (default '') * @param $values (default '') * @param $options array **/ public static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) { if (!is_array($values)) { $values = [$field => $values]; } $options['display'] = false; switch ($field) { case 'status': $options['value'] = $values[$field]; return self::dropdownStatus($name, $options); } return parent::getSpecificValueToSelect($field, $name, $values, $options); } /** * @see commonDBTM::getRights() **/ public function getRights($interface = 'central') { $values = parent::getRights(); unset($values[UPDATE], $values[READ]); $values[self::VALIDATE] = __('Validate'); return $values; } /** * Dropdown of validator * * @param $options array of options * - name : select name * - id : ID of object > 0 Update, < 0 New * - entity : ID of entity * - right : validation rights * - groups_id : ID of group validator * - users_id_validate : ID of user validator * - applyto * * @return void Output is printed **/ public static function dropdownValidator(array $options = []) { /** @var array $CFG_GLPI */ global $CFG_GLPI; $params = [ 'name' => '' , 'id' => 0, 'entity' => $_SESSION['glpiactive_entity'], 'right' => ['validate_request', 'validate_incident'], 'groups_id' => 0, 'users_id_validate' => [], 'applyto' => 'show_validator_field', 'display' => true, 'disabled' => false, 'width' => '100%', 'required' => false, 'rand' => mt_rand(), ]; foreach ($options as $key => $val) { $params[$key] = $val; } $type = ''; if (isset($params['users_id_validate']['groups_id'])) { $type = 'group'; } else if (!empty($params['users_id_validate'])) { $type = 'user'; } $out = Dropdown::showFromArray("validatortype", [ 'user' => User::getTypeName(1), 'group' => Group::getTypeName(1) ], [ 'value' => $type, 'display_emptychoice' => true, 'display' => false, 'disabled' => $params['disabled'], 'rand' => $params['rand'], 'width' => $params['width'], 'required' => $params['required'], ]); if ($type) { $params['validatortype'] = $type; $out .= Ajax::updateItem( $params['applyto'], $CFG_GLPI["root_doc"] . "/ajax/dropdownValidator.php", $params, "", false ); } $params['validatortype'] = '__VALUE__'; $out .= Ajax::updateItemOnSelectEvent( "dropdown_validatortype{$params['rand']}", $params['applyto'], $CFG_GLPI["root_doc"] . "/ajax/dropdownValidator.php", $params, false ); if (!isset($options['applyto'])) { $out .= "<br><span id='" . $params['applyto'] . "'> </span>\n"; } if ($params['display']) { echo $out; return $params['rand']; } else { return $out; } } /** * Get list of users from a group which have validation rights * * @param $options array possible: * groups_id * right * entity * * @return array **/ public static function getGroupUserHaveRights(array $options = []) { $params = [ 'entity' => $_SESSION['glpiactive_entity'], ]; if (static::$itemtype == 'Ticket') { $params['right'] = ['validate_request', 'validate_incident']; } else { $params['right'] = ['validate']; } $params['groups_id'] = 0; foreach ($options as $key => $val) { $params[$key] = $val; } $list = []; $restrict = []; $res = User::getSqlSearchResult(false, $params['right'], $params['entity']); foreach ($res as $data) { $list[] = $data['id']; } if (count($list) > 0) { $restrict = ['glpi_users.id' => $list]; } $users = Group_User::getGroupUsers($params['groups_id'], $restrict); return $users; } /** * Compute the validation status * * @param $item CommonITILObject * * @return integer **/ public static function computeValidationStatus(CommonITILObject $item) { // Percent of validation $validation_percent = $item->fields['validation_percent']; $statuses = [self::ACCEPTED => 0, self::WAITING => 0, self::REFUSED => 0 ]; $validations = getAllDataFromTable( static::getTable(), [ static::$items_id => $item->getID() ] ); if ($total = count($validations)) { foreach ($validations as $validation) { $statuses[$validation['status']]++; } } return self::computeValidation( round($statuses[self::ACCEPTED] * 100 / $total), round($statuses[self::REFUSED] * 100 / $total), $validation_percent ); } /** * Compute the validation status from the percentage of acceptation, the * percentage of refusals and the target acceptation threshold * * @param int $accepted 0-100 (percentage of acceptation) * @param int $refused 0-100 (percentage of refusals) * @param int $validation_percent 0-100 (target accepation threshold) * * @return int the validation status : ACCEPTED|REFUSED|WAITING */ public static function computeValidation( int $accepted, int $refused, int $validation_percent ): int { if ($validation_percent > 0) { if ($accepted >= $validation_percent) { // We have reached the acceptation threshold return self::ACCEPTED; } else if ($refused + $validation_percent > 100) { // We can no longer reach the acceptation threshold return self::REFUSED; } } else { // No validation threshold set, one approval or denial is enough if ($accepted > 0) { return self::ACCEPTED; } else if ($refused > 0) { return self::REFUSED; } } return self::WAITING; } /** * Get the validation statistics * * @param integer $tID tickets id * * @return string **/ public static function getValidationStats($tID) { $tab = self::getAllStatusArray(); $nb = countElementsInTable(static::getTable(), [static::$items_id => $tID]); $stats = []; foreach (array_keys($tab) as $status) { $validations = countElementsInTable(static::getTable(), [static::$items_id => $tID, 'status' => $status ]); if ($validations > 0) { if (!isset($stats[$status])) { $stats[$status] = 0; } $stats[$status] = $validations; } } $list = ""; foreach ($stats as $stat => $val) { $list .= $tab[$stat]; $list .= sprintf(__('%1$s (%2$d%%) '), " ", Html::formatNumber($val * 100 / $nb)); } return $list; } /** * @param $item CommonITILObject * @param $type */ public static function alertValidation(CommonITILObject $item, $type) { /** @var array $CFG_GLPI */ global $CFG_GLPI; // No alert for new item if ($item->isNewID($item->getID())) { return; } $status = array_merge($item->getClosedStatusArray(), $item->getSolvedStatusArray()); $message = __s("This item is waiting for approval, do you really want to resolve or close it?"); switch ($type) { case 'status': $jsScript = " $(document).ready( function() { $('[name=\"status\"]').change(function() { var status_ko = 0; var input_status = $(this).val(); if (input_status != undefined) { if (("; $first = true; foreach ($status as $val) { if (!$first) { $jsScript .= "||"; } $jsScript .= "input_status == $val"; $first = false; } $jsScript .= " ) && input_status != " . $item->fields['status'] . "){ status_ko = 1; } } if ((status_ko == 1) && ('" . ($item->fields['global_validation'] ?? '') . "' == '" . self::WAITING . "')) { alert('" . $message . "'); } }); } );"; echo Html::scriptBlock($jsScript); break; case 'solution': if ( !in_array($item->fields['status'], $status) && isset($item->fields['global_validation']) && $item->fields['global_validation'] == self::WAITING ) { $title = __s("This item is waiting for approval."); $message = __s("Do you really want to resolve or close it?"); ; $html = <<<HTML <div class="alert alert-warning" role="alert"> <div class="d-flex"> <div class="me-2"> <i class="fas fa-2x fa-exclamation-triangle"></i> </div> <div> <h4 class="alert-title">$title</h4> <div class="text-muted">$message</div> </div> </div> </div> HTML; echo $html; } break; } } /** * Get the ITIL object can validation status list * * @since 0.85 * * @return array **/ public static function getCanValidationStatusArray() { return [self::NONE, self::ACCEPTED]; } /** * Get the ITIL object all validation status list * * @since 0.85 * * @return array **/ public static function getAllValidationStatusArray() { return [self::NONE, self::WAITING, self::REFUSED, self::ACCEPTED]; } }