Linux server.jmdstrack.com 3.10.0-1160.119.1.el7.tuxcare.els10.x86_64 #1 SMP Fri Oct 11 21:40:41 UTC 2024 x86_64
/ home/ jmdstrac/ public_html/ devices/ src/ |
|
<?php /** * --------------------------------------------------------------------- * * GLPI - Gestionnaire Libre de Parc Informatique * * http://glpi-project.org * * @copyright 2015-2023 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\Event; use Glpi\RichText\RichText; use Glpi\Toolbox\Sanitizer; /** * KnowbaseItem Class **/ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria { // From CommonDBTM public $dohistory = true; protected $items = []; const KNOWBASEADMIN = 1024; const READFAQ = 2048; const PUBLISHFAQ = 4096; const COMMENTS = 8192; public static $rightname = 'knowbase'; public static function getTypeName($nb = 0) { return __('Knowledge base'); } /** * @see CommonGLPI::getMenuShorcut() * * @since 0.85 **/ public static function getMenuShorcut() { return 'b'; } public function getName($options = []) { if (KnowbaseItemTranslation::canBeTranslated($this)) { return KnowbaseItemTranslation::getTranslatedValue($this); } return parent::getName(); } /** * @see CommonGLPI::getMenuName() * * @since 0.85 **/ public static function getMenuName() { if (!Session::haveRight('knowbase', READ)) { return __('FAQ'); } else { return static::getTypeName(Session::getPluralNumber()); } } public static function getMenuContent() { $menu = parent::getMenuContent(); if (isset($menu['links']['lists'])) { unset($menu['links']['lists']); } return $menu; } public static function canCreate() { return Session::haveRightsOr(self::$rightname, [CREATE, self::PUBLISHFAQ]); } /** * @since 0.85 **/ public static function canUpdate() { return Session::haveRightsOr(self::$rightname, [UPDATE, self::KNOWBASEADMIN]); } public static function canView() { global $CFG_GLPI; return (Session::haveRightsOr(self::$rightname, [READ, self::READFAQ]) || ((Session::getLoginUserID() === false) && $CFG_GLPI["use_public_faq"])); } public function canViewItem() { if ($this->fields['users_id'] == Session::getLoginUserID()) { return true; } if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) { return true; } if ($this->fields["is_faq"]) { return ((Session::haveRightsOr(self::$rightname, [READ, self::READFAQ]) && $this->haveVisibilityAccess()) || ((Session::getLoginUserID() === false) && $this->isPubliclyVisible())); } return (Session::haveRight(self::$rightname, READ) && $this->haveVisibilityAccess()); } public function canUpdateItem() { // Personal knowbase or visibility and write access return (Session::haveRight(self::$rightname, self::KNOWBASEADMIN) || (Session::getCurrentInterface() == "central" && $this->fields['users_id'] == Session::getLoginUserID()) || ((($this->fields["is_faq"] && Session::haveRight(self::$rightname, self::PUBLISHFAQ)) || (!$this->fields["is_faq"] && Session::haveRight(self::$rightname, UPDATE))) && $this->haveVisibilityAccess())); } /** * Check if current user can comment on KB entries * * @return boolean */ public function canComment() { return $this->canViewItem() && Session::haveRight(self::$rightname, self::COMMENTS); } /** * Get the search page URL for the current classe * * @since 0.84 * * @param boolean $full path or relative one **/ public static function getSearchURL($full = true) { global $CFG_GLPI; $dir = ($full ? $CFG_GLPI['root_doc'] : ''); if (Session::getCurrentInterface() == "central") { return "$dir/front/knowbaseitem.php"; } return "$dir/front/helpdesk.faq.php"; } /** * Get the form page URL for the current classe * * @param boolean $full path or relative one **/ public static function getFormURL($full = true) { global $CFG_GLPI; $dir = ($full ? $CFG_GLPI['root_doc'] : ''); if (Session::getCurrentInterface() == "central") { return "$dir/front/knowbaseitem.form.php"; } return "$dir/front/helpdesk.faq.php"; } /** * Get the form page URL for the current classe * * @param boolean $full path or relative one **/ public static function getFormURLWithParam($params = [], $full = true) { $url = self::getFormURL($full) . '?'; if (isset($params['_sol_to_kb'])) { $url .= '&_sol_to_kb=' . $params['_sol_to_kb']; } if (isset($params['_fup_to_kb'])) { $url .= '&_fup_to_kb=' . $params['_fup_to_kb']; } if (isset($params['_task_to_kb'])) { $url .= '&_task_to_kb=' . $params['_task_to_kb']; } return $url; } public function defineTabs($options = []) { $ong = []; $this->addStandardTab(__CLASS__, $ong, $options); $this->addStandardTab('KnowbaseItem_Item', $ong, $options); $this->addStandardTab('Document_Item', $ong, $options); $this->addStandardTab('KnowbaseItemTranslation', $ong, $options); $this->addStandardTab('Log', $ong, $options); $this->addStandardTab('KnowbaseItem_Revision', $ong, $options); $this->addStandardTab('KnowbaseItem_Comment', $ong, $options); return $ong; } public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { if (!$withtemplate) { $nb = 0; switch ($item->getType()) { case __CLASS__: $ong[1] = $this->getTypeName(1); if ($item->canUpdateItem()) { if ($_SESSION['glpishow_count_on_tabs']) { $nb = $item->countVisibilities(); } $ong[2] = self::createTabEntry( _n('Target', 'Targets', Session::getPluralNumber()), $nb ); $ong[3] = __('Edit'); } return $ong; } } return ''; } public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { if ($item->getType() == __CLASS__) { switch ($tabnum) { case 1: $item->showFull(); break; case 2: $item->showVisibility(); break; case 3: $item->showForm($item->getID()); break; } } return true; } /** * Actions done at the end of the getEmpty function * *@return void **/ public function post_getEmpty() { if ( Session::haveRight(self::$rightname, self::PUBLISHFAQ) && !Session::haveRight("knowbase", UPDATE) ) { $this->fields["is_faq"] = 1; } } /** * @since 0.85 * @see CommonDBTM::post_addItem() **/ public function post_addItem() { // Handle rich-text images and uploaded documents $this->input = $this->addFiles( $this->input, [ 'force_update' => true, 'content_field' => 'answer', ] ); if ( isset($this->input["_visibility"]) && isset($this->input["_visibility"]['_type']) && !empty($this->input["_visibility"]["_type"]) ) { $this->input["_visibility"]['knowbaseitems_id'] = $this->getID(); $item = null; switch ($this->input["_visibility"]['_type']) { case 'User': if ( isset($this->input["_visibility"]['users_id']) && $this->input["_visibility"]['users_id'] ) { $item = new KnowbaseItem_User(); } break; case 'Group': if ( isset($this->input["_visibility"]['groups_id']) && $this->input["_visibility"]['groups_id'] ) { $item = new Group_KnowbaseItem(); } break; case 'Profile': if ( isset($this->input["_visibility"]['profiles_id']) && $this->input["_visibility"]['profiles_id'] ) { $item = new KnowbaseItem_Profile(); } break; case 'Entity': $item = new Entity_KnowbaseItem(); break; } if (!is_null($item)) { $item->add($this->input["_visibility"]); Event::log( $this->getID(), "knowbaseitem", 4, "tools", //TRANS: %s is the user login sprintf(__('%s adds a target'), $_SESSION["glpiname"]) ); } } if (isset($this->input['_do_item_link']) && $this->input['_do_item_link'] == 1) { $params = [ 'knowbaseitems_id' => $this->getID(), 'itemtype' => $this->input['_itemtype'], 'items_id' => $this->input['_items_id'] ]; $kb_item_item = new KnowbaseItem_Item(); $kb_item_item->add($params); } // Support old "knowbaseitemcategories_id" input // FIXME Deprecate it in GLPI 10.1 if (isset($this->input['knowbaseitemcategories_id'])) { $categories = $this->input['knowbaseitemcategories_id']; $this->input['_categories'] = is_array($categories) ? $categories : [$categories]; unset($this->input['knowbaseitemcategories_id']); } // Handle categories $this->update1NTableData(KnowbaseItem_KnowbaseItemCategory::class, "_categories"); } /** * @since 0.83 **/ public function post_getFromDB() { // Users $this->users = KnowbaseItem_User::getUsers($this->fields['id']); // Entities $this->entities = Entity_KnowbaseItem::getEntities($this->fields['id']); // Group / entities $this->groups = Group_KnowbaseItem::getGroups($this->fields['id']); // Profile / entities $this->profiles = KnowbaseItem_Profile::getProfiles($this->fields['id']); // Load categories $this->load1NTableData(KnowbaseItem_KnowbaseItemCategory::class, '_categories'); } /** * @see CommonDBTM::cleanDBonPurge() * * @since 0.83.1 **/ public function cleanDBonPurge() { $this->deleteChildrenAndRelationsFromDb( [ Entity_KnowbaseItem::class, Group_KnowbaseItem::class, KnowbaseItem_KnowbaseItemCategory::class, KnowbaseItem_Item::class, KnowbaseItem_Profile::class, KnowbaseItem_User::class, KnowbaseItemTranslation::class, ] ); /// KnowbaseItem_Comment does not extends CommonDBConnexity $kbic = new KnowbaseItem_Comment(); $kbic->deleteByCriteria(['knowbaseitems_id' => $this->fields['id']]); /// KnowbaseItem_Revision does not extends CommonDBConnexity $kbir = new KnowbaseItem_Revision(); $kbir->deleteByCriteria(['knowbaseitems_id' => $this->fields['id']]); } /** * Check is this item if visible to everybody (anonymous users) * * @since 0.83 * * @return Boolean **/ public function isPubliclyVisible() { global $CFG_GLPI; if (!$CFG_GLPI['use_public_faq']) { return false; } if (isset($this->entities[0])) { // Browse root entity rights foreach ($this->entities[0] as $entity) { if ($entity['is_recursive']) { return true; } } } return false; } public function haveVisibilityAccess() { // No public knowbaseitem right : no visibility check if (!Session::haveRightsOr(self::$rightname, [self::READFAQ, READ])) { return false; } // KB Admin if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) { return true; } return parent::haveVisibilityAccess(); } /** * Return visibility joins to add to SQL * * @since 0.83 * * @param boolean $forceall force all joins * * @return string joins to add **/ public static function addVisibilityJoins($forceall = false) { //not deprecated because used in self::getListRequest and self::showRecentPopular global $DB; //get and clean criteria $criteria = self::getVisibilityCriteria($forceall); unset($criteria['WHERE']); $criteria['FROM'] = self::getTable(); $it = new \DBmysqlIterator(null); $it->buildQuery($criteria); $sql = $it->getSql(); $sql = str_replace( 'SELECT * FROM ' . $DB->quoteName(self::getTable()) . '', '', $sql ); return $sql; } /** * Return visibility SQL restriction to add * * @since 0.83 * * @return string restrict to add **/ public static function addVisibilityRestrict() { //not deprecated because used in self::getListRequest and self::showRecentPopular global $DB; //get and clean criteria $criteria = self::getVisibilityCriteria(); unset($criteria['LEFT JOIN']); $criteria['FROM'] = self::getTable(); $it = new \DBmysqlIterator(null); $it->buildQuery($criteria); $sql = $it->getSql(); $sql = str_replace( 'SELECT * FROM ' . $DB->quoteName(self::getTable()) . '', '', $sql ); $sql = preg_replace('/.*WHERE /', '', $sql); //No where restrictions. Add a placeholder for compatibility with later restrictions if (strlen(trim($sql)) == 0) { $sql = "1"; } return $sql; } /** * Return visibility joins to add to DBIterator parameters * * @since 9.2 * * @param boolean $forceall force all joins (false by default) * * @return array */ public static function getVisibilityCriteria(bool $forceall = false): array { global $CFG_GLPI; // Build common JOIN clause $criteria = [ 'LEFT JOIN' => self::getVisibilityCriteriaCommonJoin($forceall), ]; // Handle anonymous users if (!Session::getLoginUserID()) { if ($CFG_GLPI["use_public_faq"]) { // Public FAQ is enabled; show FAQ $criteria['WHERE'] = self::getVisibilityCriteriaFAQ(); return $criteria; } else { // Public FAQ is disabled; show nothing $criteria['WHERE'] = [0]; return $criteria; } } // Handle logged in users if (Session::getCurrentInterface() == "helpdesk") { // Show FAQ for helpdesk user $criteria['WHERE'] = self::getVisibilityCriteriaFAQ(); return $criteria; } else { // Show knowledge base for central users $criteria['WHERE'] = self::getVisibilityCriteriaKB(); return $criteria; } } /** * Common JOIN clause used by getVisibilityCriteria* methods * * @param bool $forceall Force all join ? * * @return array LEFT JOIN clause */ private static function getVisibilityCriteriaCommonJoin(bool $forceall = false) { global $CFG_GLPI; $join = []; // Context checks - avoid doing unnecessary join if possible $is_public_faq_context = !Session::getLoginUserID() && $CFG_GLPI["use_public_faq"]; $has_session_groups = count(($_SESSION["glpigroups"] ?? [])); $has_active_profile = isset($_SESSION["glpiactiveprofile"]['id']); $has_active_entity = count(($_SESSION["glpiactiveentities"] ?? [])); // Add user restriction data if ($forceall || Session::getLoginUserID()) { $join['glpi_knowbaseitems_users'] = [ 'ON' => [ 'glpi_knowbaseitems_users' => 'knowbaseitems_id', 'glpi_knowbaseitems' => 'id' ] ]; } // Add group restriction data if ($forceall || $has_session_groups) { $join['glpi_groups_knowbaseitems'] = [ 'ON' => [ 'glpi_groups_knowbaseitems' => 'knowbaseitems_id', 'glpi_knowbaseitems' => 'id' ] ]; } // Add profile restriction data if ($forceall || $has_active_profile) { $join['glpi_knowbaseitems_profiles'] = [ 'ON' => [ 'glpi_knowbaseitems_profiles' => 'knowbaseitems_id', 'glpi_knowbaseitems' => 'id' ] ]; } // Add entity restriction data if ($forceall || $has_active_entity || $is_public_faq_context) { $join['glpi_entities_knowbaseitems'] = [ 'ON' => [ 'glpi_entities_knowbaseitems' => 'knowbaseitems_id', 'glpi_knowbaseitems' => 'id' ] ]; } return $join; } /** * Get visibility criteria for articles displayed in the FAQ (seen by * helpdesk and anonymous users) * This mean any KB article tagged as 'is_faq' should be displayed * * @return array WHERE clause */ private static function getVisibilityCriteriaFAQ(): array { // Specific case for anonymous users + multi entities if (!Session::getLoginUserID()) { $where = ['is_faq' => 1]; if (Session::isMultiEntitiesMode()) { $where[Entity_KnowbaseItem::getTableField('entities_id')] = 0; $where[Entity_KnowbaseItem::getTableField('is_recursive')] = 1; } } else { $where = self::getVisibilityCriteriaKB_Entity(); $where['is_faq'] = 1; } return $where; } /** * Get visibility criteria for articles displayed in the knowledge base * (seen by central users) * This mean any KB article with valid visibility criteria for the current * user should be displayed * * @return array WHERE clause */ private static function getVisibilityCriteriaKB(): array { // Special case for KB Admins if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) { // See all articles return [1]; } // Prepare criteria, which will use an OR statement (the user can read // the article if any of the user/group/profile/entity criteria are // validated) $where = ['OR' => []]; // Special case: the user may be the article's author $user = Session::getLoginUserID(); $author_check = [self::getTableField('users_id') => $user]; $where['OR'][] = $author_check; // Filter on users $where['OR'][] = self::getVisibilityCriteriaKB_User(); // Filter on groups (if the current user have any) $groups = $_SESSION["glpigroups"] ?? []; if (count($groups)) { $where['OR'][] = self::getVisibilityCriteriaKB_Group(); } // Filter on profiles $where['OR'][] = self::getVisibilityCriteriaKB_Profile(); // Filter on entities $where['OR'][] = self::getVisibilityCriteriaKB_Entity(); return $where; } /** * Get criteria used to filter knowledge base articles on users * * @return array */ private static function getVisibilityCriteriaKB_User(): array { $user = Session::getLoginUserID(); return [ KnowbaseItem_User::getTableField('users_id') => $user, ]; } /** * Get criteria used to filter knowledge base articles on groups * * @return array */ private static function getVisibilityCriteriaKB_Group(): array { $groups = $_SESSION["glpigroups"] ?? [-1]; $entity_restriction = getEntitiesRestrictCriteria( Group_KnowbaseItem::getTable(), '', '', true, true ); return [ Group_KnowbaseItem::getTableField('groups_id') => $groups, 'OR' => [ Group_KnowbaseItem::getTableField('no_entity_restriction') => 1, ] + $entity_restriction, ]; } /** * Get criteria used to filter knowledge base articles on profiles * * @return array */ private static function getVisibilityCriteriaKB_Profile(): array { $profile = $_SESSION["glpiactiveprofile"]['id'] ?? -1; $entity_restriction = getEntitiesRestrictCriteria( KnowbaseItem_Profile::getTable(), '', '', true, true ); return [ KnowbaseItem_Profile::getTableField('profiles_id') => $profile, 'OR' => [ KnowbaseItem_Profile::getTableField('no_entity_restriction') => 1, ] + $entity_restriction, ]; } /** * Get criteria used to filter knowledge base articles on entity * * @return array */ private static function getVisibilityCriteriaKB_Entity(): array { $entity_restriction = getEntitiesRestrictCriteria( Entity_KnowbaseItem::getTable(), '', '', true, true ); // All entities if (!count($entity_restriction)) { $entity_restriction = [ Entity_KnowbaseItem::getTableField('entities_id') => null ]; } return $entity_restriction; } public function prepareInputForAdd($input) { // set title for question if empty if (isset($input["name"]) && empty($input["name"])) { $input["name"] = __('New item'); } if ( Session::haveRight(self::$rightname, self::PUBLISHFAQ) && !Session::haveRight(self::$rightname, UPDATE) ) { $input["is_faq"] = 1; } if ( !Session::haveRight(self::$rightname, self::PUBLISHFAQ) && Session::haveRight(self::$rightname, UPDATE) ) { $input["is_faq"] = 0; } return $input; } public function prepareInputForUpdate($input) { // set title for question if empty if (isset($input["name"]) && empty($input["name"])) { $input["name"] = __('New item'); } return $input; } public function post_updateItem($history = 1) { // Handle rich-text images and uploaded documents $this->input = $this->addFiles( $this->input, [ 'force_update' => true, 'content_field' => 'answer', ] ); // Support old "knowbaseitemcategories_id" input // FIXME Deprecate it in GLPI 10.1 if (isset($this->input['knowbaseitemcategories_id'])) { $categories = $this->input['knowbaseitemcategories_id']; $this->input['_categories'] = is_array($categories) ? $categories : [$categories]; unset($this->input['knowbaseitemcategories_id']); } // Update categories $this->update1NTableData(KnowbaseItem_KnowbaseItemCategory::class, '_categories'); } /** * Print out an HTML "<form>" for knowbase item * * @param $ID * @param $options array * - target for the Form * * @return void **/ public function showForm($ID, array $options = []) { global $CFG_GLPI; // show kb item form if ( !Session::haveRightsOr( self::$rightname, [UPDATE, self::PUBLISHFAQ, self::KNOWBASEADMIN] ) ) { return false; } $canedit = $this->can($ID, UPDATE); $item = null; // Load ticket solution if ( empty($ID) && isset($options['item_itemtype']) && !empty($options['item_itemtype']) && isset($options['item_items_id']) && !empty($options['item_items_id']) ) { if ($item = getItemForItemtype($options['item_itemtype'])) { if ($item->getFromDB($options['item_items_id'])) { $this->fields['name'] = $item->getField('name'); if (isset($options['_fup_to_kb'])) { $fup = new ITILFollowup(); $fup->getFromDBByCrit([ 'id' => $options['_fup_to_kb'], 'itemtype' => $item->getType(), 'items_id' => $item->getID() ]); $this->fields['answer'] = $fup->getField('content'); } else if (isset($options['_task_to_kb'])) { $tasktype = $item->getType() . 'Task'; $task = new $tasktype(); $task->getFromDB($options['_task_to_kb']); $this->fields['answer'] = $task->getField('content'); } else if (isset($options['_sol_to_kb'])) { $solution = new ITILSolution(); $solution->getFromDBByCrit([ 'itemtype' => $item->getType(), 'items_id' => $item->getID(), [ 'NOT' => ['status' => CommonITILValidation::REFUSED] ] ]); $this->fields['answer'] = $solution->getField('content'); } if ($item->isField('itilcategories_id')) { $ic = new ITILCategory(); if ($ic->getFromDB($item->getField('itilcategories_id'))) { $this->fields['knowbaseitemcategories_id'] = $ic->getField('knowbaseitemcategories_id'); } } } } } $rand = mt_rand(); $this->initForm($ID, $options); $options['formoptions'] = "data-track-changes=true"; $this->showFormHeader($options); echo "<tr class='tab_bg_1'>"; echo "<td>" . KnowbaseItemCategory::getTypeName(Session::getPluralNumber()) . "</td>"; echo "<td>"; // See CommonDBTM::update1NTableData, hidden input needed to handle empty values echo Html::input("__categories_defined", ["type" => "hidden", "value" => 1]); KnowbaseItemCategory::dropdown([ 'name' => "_categories[]", 'value' => $this->fields['_categories'] ?? [], 'multiple' => true, 'width' => "100%", ]); echo "</td>"; echo "<td>"; echo "<input type='hidden' name='users_id' value=\"" . Session::getLoginUserID() . "\">"; if ($this->fields["date_creation"]) { //TRANS: %s is the datetime of insertion printf(__('Created on %s'), Html::convDateTime($this->fields["date_creation"])); } echo "</td><td>"; if ($this->fields["date_mod"]) { //TRANS: %s is the datetime of update printf(__('Last update on %s'), Html::convDateTime($this->fields["date_mod"])); } echo "</td>"; echo "</tr>\n"; echo "<tr class='tab_bg_1'>"; if (Session::haveRight(self::$rightname, self::PUBLISHFAQ)) { echo "<td>" . __('Put this item in the FAQ') . "</td>"; echo "<td>"; Dropdown::showYesNo('is_faq', $this->fields["is_faq"]); echo "</td>"; } else { echo "<td colspan='2'>"; if ($this->fields["is_faq"]) { echo __('This item is part of the FAQ'); } else { echo __('This item is not part of the FAQ'); } echo "</td>"; } echo "<td>"; $showuserlink = 0; if (Session::haveRight('user', READ)) { $showuserlink = 1; } if ($this->fields["users_id"]) { //TRANS: %s is the writer name printf(__('%1$s: %2$s'), __('Writer'), getUserName( $this->fields["users_id"], $showuserlink )); } echo "</td><td>"; //TRANS: %d is the number of view if ($ID) { printf(_n('%d view', '%d views', $this->fields["view"]), $this->fields["view"]); } echo "</td>"; echo "</tr>\n"; //Link with solution if ($item != null) { if ($item = getItemForItemtype($options['item_itemtype'])) { if ($item->getFromDB($options['item_items_id'])) { echo "<tr>"; echo "<td>" . __('Add link') . "</td>"; echo "<td colspan='3'>"; echo "<input type='checkbox' name='_do_item_link' value='1' checked='checked'/> "; echo Html::hidden('_itemtype', ['value' => $item->getType()]); echo Html::hidden('_items_id', ['value' => $item->getID()]); echo sprintf( __('link with %1$s'), $item->getLink() ); echo "</td>"; echo "</tr>\n"; } } } echo "<tr class='tab_bg_1'>"; echo "<td>" . __('Visible since') . "</td><td>"; Html::showDateTimeField("begin_date", ['value' => $this->fields["begin_date"], 'maybeempty' => true, 'canedit' => $canedit ]); echo "</td>"; echo "<td>" . __('Visible until') . "</td><td>"; Html::showDateTimeField("end_date", ['value' => $this->fields["end_date"], 'maybeempty' => true, 'canedit' => $canedit ]); echo "</td></tr>"; echo "<tr class='tab_bg_1'>"; echo "<td>" . __('Subject') . "</td>"; echo "<td colspan='3'>"; echo "<textarea class='form-control' name='name'>" . $this->fields["name"] . "</textarea>"; echo "</td>"; echo "</tr>\n"; echo "<tr class='tab_bg_1'>"; echo "<td>" . __('Content') . "</td>"; echo "<td colspan='3'>"; $cols = 100; $rows = 30; if (isset($options['_in_modal']) && $options['_in_modal']) { $rows = 15; echo Html::hidden('_in_modal', ['value' => 1]); } Html::textarea(['name' => 'answer', 'value' => RichText::getSafeHtml($this->fields['answer'], true), 'enable_fileupload' => true, 'enable_richtext' => true, 'cols' => $cols, 'rows' => $rows ]); echo "</td>"; echo "</tr>"; if ($this->isNewID($ID)) { echo "<tr class='tab_bg_1'>"; echo "<td>" . _n('Target', 'Targets', 1) . "</td>"; echo "<td>"; $types = ['Entity', 'Group', 'Profile', 'User']; $addrand = Dropdown::showItemTypes('_visibility[_type]', $types); echo "</td><td colspan='2'>"; $params = ['type' => '__VALUE__', 'right' => 'knowbase', 'prefix' => '_visibility', 'nobutton' => 1 ]; Ajax::updateItemOnSelectEvent( "dropdown__visibility__type_" . $addrand, "visibility$rand", $CFG_GLPI["root_doc"] . "/ajax/visibility.php", $params ); echo "<span id='visibility$rand'></span>"; echo "</td></tr>\n"; } $this->showFormButtons($options); return true; } /** * Add kb item to the public FAQ * * @return void **/ public function addToFaq() { global $DB; $DB->update( $this->getTable(), [ 'is_faq' => 1 ], [ 'id' => $this->fields['id'] ] ); } /** * Increase the view counter of the current knowbaseitem * * @since 0.83 */ public function updateCounter() { global $DB; //update counter view $DB->update( 'glpi_knowbaseitems', [ 'view' => new \QueryExpression($DB->quoteName('view') . ' + 1') ], [ 'id' => $this->getID() ] ); } /** * Print out (html) show item : question and answer * * @param $options array of options * * @return void|string * void if option display=true * string if option display=false (HTML code) **/ public function showFull($options = []) { global $CFG_GLPI, $DB; if (!$this->can($this->fields['id'], READ)) { return false; } $default_options = [ 'display' => true, ]; $options = array_merge($default_options, $options); $out = ""; $linkusers_id = true; // show item : question and answer if ( ((Session::getLoginUserID() === false) && $CFG_GLPI["use_public_faq"]) || (Session::getCurrentInterface() == "helpdesk") || !User::canView() ) { $linkusers_id = false; } $this->updateCounter(); $tmp = []; $categories = KnowbaseItem_KnowbaseItemCategory::getItems($this); foreach ($categories as $category) { $knowbaseitemcategories_id = $category['knowbaseitemcategories_id']; $fullcategoryname = getTreeValueCompleteName( "glpi_knowbaseitemcategories", $knowbaseitemcategories_id ); $tmp[] = "<a href='" . $this->getSearchURL() . "?knowbaseitemcategories_id=$knowbaseitemcategories_id&forcetab=Knowbase$2'>" . $fullcategoryname . "</a>"; } $tmp = implode(', ', $tmp); $out .= "<table class='tab_cadre_fixe'>"; $out .= "<tr><th colspan='4'>" . sprintf(__('%1$s: %2$s'), _n('Category', 'Categories', 1), $tmp); $out .= "</th></tr>"; $out .= "<tr><td class='left' colspan='4'><h2>" . __('Subject') . "</h2>"; if (KnowbaseItemTranslation::canBeTranslated($this)) { $out .= KnowbaseItemTranslation::getTranslatedValue($this, 'name'); } else { $out .= $this->fields["name"]; } $out .= "</td></tr>"; $out .= "<tr><td class='left' colspan='4'><h2>" . __('Content') . "</h2>\n"; $out .= "<div class='rich_text_container' id='kbanswer'>"; $out .= $this->getAnswer(); $out .= "</div>"; $out .= "</td></tr>"; // Show documents attached to the FAQ Item $sort = 'filename'; $order = 'ASC'; $criteria = Document_Item::getDocumentForItemRequest($this, ["$sort $order"]); $criteria['WHERE'][] = ['is_deleted' => '0']; $iterator = $DB->request($criteria); if (count($iterator) > 0) { $out .= "<tr><td class='left' colspan='4'><h2>" . Document::getTypeName(Session::getPluralNumber()) . "</h2></td></tr>\n"; $columns = [ 'filename' => __('File'), 'headings' => __('Heading'), 'assocdate' => _n('Date', 'Dates', 1), ]; $header_begin = "<tr>"; $header_top = ''; $header_end = ''; foreach ($columns as $key => $val) { $colspan = $key == 'filename' ? 'colspan="2"' : ''; $header_end .= "<th $colspan" . ($sort == "$key" ? " class='order_$order'" : '') . ">" . "<a href='javascript:reloadTab(\"sort=$key&order=" . (($order == "ASC") ? "DESC" : "ASC") . "&start=0\");'>$val</a></th>"; } $header_end .= "</tr>"; $out .= $header_begin . $header_top . $header_end; $document = new Document(); foreach ($iterator as $data) { $docID = $data["id"]; $downloadlink = NOT_AVAILABLE; if ($document->getFromDB($docID)) { $downloadlink = $document->getDownloadLink(); } $used[$docID] = $docID; $out .= "<tr class='tab_bg_1" . ($data["is_deleted"] ? "_2" : "") . "'>"; $out .= "<td colspan='2'>$downloadlink</td>"; $out .= "<td>" . Dropdown::getDropdownName( "glpi_documentcategories", $data["documentcategories_id"] ); $out .= "</td>"; $out .= "<td>" . Html::convDateTime($data["assocdate"]) . "</td>"; $out .= "</tr>"; } } $out .= "<tr><th class='tdkb' colspan='2'>"; if ($this->fields["users_id"]) { // Integer because true may be 2 and getUserName return array if ($linkusers_id) { $linkusers_id = 1; } else { $linkusers_id = 0; } $out .= sprintf(__('%1$s: %2$s'), __('Writer'), getUserName( $this->fields["users_id"], $linkusers_id )); $out .= "<br>"; } if ($this->fields["date_creation"]) { //TRANS: %s is the datetime of update $out .= sprintf(__('Created on %s'), Html::convDateTime($this->fields["date_creation"])); $out .= "<br>"; } if ($this->fields["date_mod"]) { //TRANS: %s is the datetime of update $out .= sprintf(__('Last update on %s'), Html::convDateTime($this->fields["date_mod"])); } $out .= "</th>"; $out .= "<th class='tdkb' colspan='2'>"; if ($this->countVisibilities() == 0) { $out .= "<span class='red'>" . __('Unpublished') . "</span><br>"; } $out .= sprintf(_n('%d view', '%d views', $this->fields["view"]), $this->fields["view"]); $out .= "<br>"; if ($this->fields["is_faq"]) { $out .= __('This item is part of the FAQ'); } else { $out .= __('This item is not part of the FAQ'); } $out .= "</th></tr>"; $out .= "</table>"; if ($options['display']) { echo $out; } else { return $out; } return true; } /** * Print out an HTML form for Search knowbase item * * @param $options $_GET * * @return void **/ public function searchForm($options) { global $CFG_GLPI; if ( !$CFG_GLPI["use_public_faq"] && !Session::haveRightsOr(self::$rightname, [READ, self::READFAQ]) ) { return false; } // Default values of parameters $params["contains"] = ""; $params["target"] = $_SERVER['PHP_SELF']; if (is_array($options) && count($options)) { foreach ($options as $key => $val) { $params[$key] = $val; } } echo "<form method='get' action='" . $this->getSearchURL() . "' class='d-flex justify-content-center'>"; echo "<input class='form-control me-1' type='text' size='50' name='contains' value=\"" . Html::cleanInputText(stripslashes($params["contains"])) . "\">"; echo "<input type='submit' value=\"" . _sx('button', 'Search') . "\" class='btn btn-primary'>"; echo "</table>"; if ( isset($options['item_itemtype']) && isset($options['item_items_id']) ) { echo "<input type='hidden' name='item_itemtype' value='" . $options['item_itemtype'] . "'>"; echo "<input type='hidden' name='item_items_id' value='" . $options['item_items_id'] . "'>"; } Html::closeForm(); } /** * Print out an HTML "<form>" for Search knowbase item * * @since 0.84 * * @param $options $_GET * * @return void **/ public function showBrowseForm($options) { global $CFG_GLPI; if ( !$CFG_GLPI["use_public_faq"] && !Session::haveRightsOr(self::$rightname, [READ, self::READFAQ]) ) { return false; } // Default values of parameters $params["knowbaseitemcategories_id"] = ""; if (is_array($options) && count($options)) { foreach ($options as $key => $val) { $params[$key] = $val; } } $faq = !Session::haveRight(self::$rightname, READ); // Category select not for anonymous FAQ if ( Session::getLoginUserID() && !$faq ) { echo "<div>"; echo "<form method='get' action='" . $this->getSearchURL() . "'>"; echo "<table class='tab_cadre_fixe'>"; echo "<tr class='tab_bg_2'><td class='right' width='50%'>" . _n('Category', 'Categories', 1) . " "; KnowbaseItemCategory::dropdown(['value' => $params["knowbaseitemcategories_id"]]); echo "</td><td class='left'>"; echo "<input type='submit' value=\"" . _sx('button', 'Post') . "\" class='btn btn-primary'></td>"; echo "</tr></table>"; if ( isset($options['item_itemtype']) && isset($options['item_items_id']) ) { echo "<input type='hidden' name='item_itemtype' value='" . $options['item_itemtype'] . "'>"; echo "<input type='hidden' name='item_items_id' value='" . $options['item_items_id'] . "'>"; } Html::closeForm(); echo "</div>"; } } /** * Print out an HTML form for Search knowbase item * * @since 0.84 * * @param $options $_GET * * @return void **/ public function showManageForm($options) { if ( !Session::haveRightsOr( self::$rightname, [UPDATE, self::PUBLISHFAQ, self::KNOWBASEADMIN] ) ) { return false; } $params['unpublished'] = 'my'; if (is_array($options) && count($options)) { foreach ($options as $key => $val) { $params[$key] = $val; } } echo "<div>"; echo "<form method='get' action='" . $this->getSearchURL() . "'>"; echo "<table class='tab_cadre_fixe'>"; echo "<tr class='tab_bg_2'><td class='right' width='50%'>"; $values = ['myunpublished' => __('My unpublished articles'), 'allmy' => __('All my articles') ]; if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) { $values['allunpublished'] = __('All unpublished articles'); $values['allpublished'] = __('All published articles'); } Dropdown::showFromArray('unpublished', $values, ['value' => $params['unpublished']]); echo "</td><td class='left'>"; echo "<input type='submit' value=\"" . _sx('button', 'Post') . "\" class='btn btn-primary'></td>"; echo "</tr></table>"; Html::closeForm(); echo "</div>"; } /** * Build request for showList * * @since 0.83 * * @param $params array (contains, knowbaseitemcategories_id, faq) * @param $type string search type : browse / search (default search) * * @return array : SQL request **/ public static function getListRequest(array $params, $type = 'search') { global $DB; $criteria = [ 'SELECT' => [ 'glpi_knowbaseitems.*', new QueryExpression( 'COUNT(' . $DB->quoteName('glpi_knowbaseitems_users.id') . ')' . ' + COUNT(' . $DB->quoteName('glpi_groups_knowbaseitems.id') . ')' . ' + COUNT(' . $DB->quoteName('glpi_knowbaseitems_profiles.id') . ')' . ' + COUNT(' . $DB->quoteName('glpi_entities_knowbaseitems.id') . ') AS ' . $DB->quoteName('visibility_count') ) ], 'FROM' => 'glpi_knowbaseitems', 'WHERE' => [], //to be filled 'LEFT JOIN' => [], //to be filled 'GROUPBY' => ['glpi_knowbaseitems.id'] ]; // Lists kb Items $restrict = self::getVisibilityCriteria(true); $restrict_where = $restrict['WHERE']; unset($restrict['WHERE']); unset($restrict['SELECT']); $criteria = array_merge_recursive($criteria, $restrict); switch ($type) { case 'myunpublished': case 'allmy': case 'allunpublished': break; default: // Build query if (Session::getLoginUserID()) { $criteria['WHERE'] = array_merge( $criteria['WHERE'], $restrict_where ); } else { // Anonymous access if (Session::isMultiEntitiesMode()) { $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = 0; $criteria['WHERE']['glpi_entities_knowbaseitems.is_recursive'] = 1; } } break; } if ($params['faq']) { // helpdesk $criteria['WHERE'][] = [ 'OR' => [ 'glpi_knowbaseitems.is_faq' => 1, 'glpi_knowbaseitems_users.users_id' => Session::getLoginUserID(), ] ]; } if ($params['knowbaseitemcategories_id'] !== KnowbaseItemCategory::SEEALL) { $criteria['LEFT JOIN'][KnowbaseItem_KnowbaseItemCategory::getTable()] = [ 'FKEY' => [ KnowbaseItem_KnowbaseItemCategory::getTable() => KnowbaseItem::getForeignKeyField(), KnowbaseItem::getTable() => 'id', ], ]; if ($params['knowbaseitemcategories_id'] > 0) { $criteria['WHERE'][KnowbaseItem_KnowbaseItemCategory::getTableField('knowbaseitemcategories_id')] = $params['knowbaseitemcategories_id']; } elseif ($params['knowbaseitemcategories_id'] === 0) { $criteria['WHERE'][KnowbaseItem_KnowbaseItemCategory::getTableField('knowbaseitemcategories_id')] = null; } } if ( KnowbaseItemTranslation::isKbTranslationActive() && (countElementsInTable('glpi_knowbaseitemtranslations') > 0) ) { $criteria['LEFT JOIN']['glpi_knowbaseitemtranslations'] = [ 'ON' => [ 'glpi_knowbaseitems' => 'id', 'glpi_knowbaseitemtranslations' => 'knowbaseitems_id', [ 'AND' => [ 'glpi_knowbaseitemtranslations.language' => $_SESSION['glpilanguage'] ] ] ] ]; $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.name AS transname'; $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.answer AS transanswer'; } // a search with $contains switch ($type) { case 'allmy': $criteria['WHERE']['glpi_knowbaseitems.users_id'] = Session::getLoginUserID(); break; case 'myunpublished': $criteria['WHERE']['glpi_knowbaseitems.users_id'] = Session::getLoginUserID(); $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = null; $criteria['WHERE']['glpi_knowbaseitems_profiles.profiles_id'] = null; $criteria['WHERE']['glpi_groups_knowbaseitems.groups_id'] = null; $criteria['WHERE']['glpi_knowbaseitems_users.users_id'] = null; break; case 'allunpublished': // Only published $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = null; $criteria['WHERE']['glpi_knowbaseitems_profiles.profiles_id'] = null; $criteria['WHERE']['glpi_groups_knowbaseitems.groups_id'] = null; $criteria['WHERE']['glpi_knowbaseitems_users.users_id'] = null; break; case 'allpublished': $criteria['HAVING']['visibility_count'] = ['>', 0]; break; case 'search': if (strlen($params["contains"]) > 0) { $search = Sanitizer::unsanitize($params["contains"]); $search_wilcard = self::computeBooleanFullTextSearch($search); $addscore = []; if ( KnowbaseItemTranslation::isKbTranslationActive() && (countElementsInTable('glpi_knowbaseitemtranslations') > 0) ) { $addscore = [ 'glpi_knowbaseitemtranslations.name', 'glpi_knowbaseitemtranslations.answer' ]; } $expr = "(MATCH(" . $DB->quoteName('glpi_knowbaseitems.name') . ", " . $DB->quoteName('glpi_knowbaseitems.answer') . ") AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)"; if (!empty($addscore)) { foreach ($addscore as $addscore_field) { $expr .= " + MATCH(" . $DB->quoteName($addscore_field) . ") AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)"; } } $expr .= " ) AS SCORE "; $criteria['SELECT'][] = new QueryExpression($expr); $ors = [ new QueryExpression( "MATCH(" . $DB->quoteName('glpi_knowbaseitems.name') . ", " . $DB->quoteName('glpi_knowbaseitems.answer') . ") AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)" ) ]; if (!empty($addscore)) { foreach ($addscore as $addscore_field) { $ors[] = [ 'NOT' => [$addscore_field => null], new QueryExpression( "MATCH(" . $DB->quoteName($addscore_field) . ") AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)" ) ]; } } $search_where = $criteria['WHERE']; // Visibility restrict criteria $search_where[] = ['OR' => $ors]; // Add visibility date $visibility_crit = [ [ 'OR' => [ ['glpi_knowbaseitems.begin_date' => null], ['glpi_knowbaseitems.begin_date' => ['<', new QueryExpression('NOW()')]] ] ], [ 'OR' => [ ['glpi_knowbaseitems.end_date' => null], ['glpi_knowbaseitems.end_date' => ['>', new QueryExpression('NOW()')]] ] ] ]; $search_where[] = $visibility_crit; $criteria['ORDERBY'] = ['SCORE DESC']; // preliminar query to allow alternate search if no result with fulltext $search_criteria = [ 'COUNT' => 'cpt', 'LEFT JOIN' => $criteria['LEFT JOIN'], 'FROM' => 'glpi_knowbaseitems', 'WHERE' => $search_where ]; $search_iterator = $DB->request($search_criteria); $numrows_search = $search_iterator->current()['cpt']; if ($numrows_search <= 0) {// not result this fulltext try with alternate search $search1 = [/* 1 */ '/\\\"/', /* 2 */ "/\+/", /* 3 */ "/\*/", /* 4 */ "/~/", /* 5 */ "/</", /* 6 */ "/>/", /* 7 */ "/\(/", /* 8 */ "/\)/", /* 9 */ "/\-/" ]; $contains = preg_replace($search1, "", $params["contains"]); $ors = [ ["glpi_knowbaseitems.name" => ['LIKE', Search::makeTextSearchValue($contains)]], ["glpi_knowbaseitems.answer" => ['LIKE', Search::makeTextSearchValue($contains)]] ]; if ( KnowbaseItemTranslation::isKbTranslationActive() && (countElementsInTable('glpi_knowbaseitemtranslations') > 0) ) { $ors[] = ["glpi_knowbaseitemtranslations.name" => ['LIKE', Search::makeTextSearchValue($contains)]]; $ors[] = ["glpi_knowbaseitemtranslations.answer" => ['LIKE', Search::makeTextSearchValue($contains)]]; } $criteria['WHERE'][] = ['OR' => $ors]; // Add visibility date $criteria['WHERE'][] = $visibility_crit; } else { $criteria['WHERE'] = $search_where; } } break; case 'browse': if (!Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) { // Add visibility date $criteria['WHERE'][] = [ 'OR' => [ ['glpi_knowbaseitems.begin_date' => null], ['glpi_knowbaseitems.begin_date' => ['<', new QueryExpression('NOW()')]] ] ]; $criteria['WHERE'][] = [ 'OR' => [ ['glpi_knowbaseitems.end_date' => null], ['glpi_knowbaseitems.end_date' => ['>', new QueryExpression('NOW()')]] ] ]; } $criteria['ORDERBY'] = ['glpi_knowbaseitems.name ASC']; break; } return $criteria; } /** * Clean search for Boolean FullText * * @since 10.0.7 * @param string $search * * @return string **/ private static function computeBooleanFullTextSearch(string $search): string { $word_chars = '\p{L}\p{N}_'; $ponderation_chars = '+\-<>~'; // Remove any whitespace from begin/end $search = preg_replace('/^[\p{Z}\h\v\r\n]+|[\p{Z}\h\v\r\n]+$/u', '', $search); // Remove all symbols except word chars, ponderation chars, parenthesis, quotes, wildcards and spaces. // @distance is not included, since it's unlikely a human will be using it through UI form $search = preg_replace("/[^{$word_chars}{$ponderation_chars}()\"* ]/u", '', $search); // Remove all ponderation chars, that can only precede a word and that are not preceded by either beginning of string, a space or an opening parenthesis $search = preg_replace("/(?<!^| |\()[{$ponderation_chars}]/u", '', $search); // Remove all ponderation chars that are not followed by a search term // (they are followed by a space, a closing parenthesis or end fo string) $search = preg_replace("/[{$ponderation_chars}]+( |\)|$)/u", '', $search); // Remove all opening parenthesis that are located inside a searched term // (they are preceded by a word char or a quote) $search = preg_replace("/(?<=[{$word_chars}\"])\(/u", '', $search); // Remove all closing parenthesis that are located inside a searched term // (they are followed by a word char or a quote) $search = preg_replace("/\)(?=[{$word_chars}\")])/u", '', $search); // Remove empty parenthesis $search = preg_replace("/\(\)/u", '', $search); // Remove all parenthesis if count of closing does not match count of opening ones if (mb_substr_count($search, '(') !== mb_substr_count($search, ')')) { $search = preg_replace("/[()]/u", '', $search); } // Remove all asterisks that are not located at the end of a word // (can be followed by a space, a closing parenthesis or end of string, and must be preceded by a word char) $search = preg_replace("/(?<=[{$word_chars}])\*(?! |\)|$)/u", '', $search); // Remove all double quotes // - that are not located before a searched term // (can be preceded by beginning of string, an operator, a space or an opening parenthesis, and must be followed by a word char) $search = preg_replace("/(?<=^|[{$ponderation_chars} (])\"(?![{$word_chars}])/u", '', $search); // - that are not located after a searched term // (can be followed by a space, a closing parenthesis or end of string, and must be preceded by a word char) $search = preg_replace("/(?<=[{$word_chars}])\"(?! |\)|$)/u", '', $search); // - if the count is not even if (mb_substr_count($search, '"') % 2 !== 0) { $search = preg_replace("/\"/u", '', $search); } // Check if the new value is just the set of operators and spaces and if it is - set the value to an empty string if (preg_match("/^[{$ponderation_chars}()\"* ]+$/u", $search)) { $search = ''; } // Remove extra spaces $search = preg_replace('/\s+/u', ' ', trim($search)); // Add * foreach word when no boolean operator is used if (!preg_match('/[^\p{L}\p{N}_ ]/u', $search)) { $search = implode('* ', explode(' ', $search)) . '*'; } return $search; } /** * Print out list kb item * * @param $options $_GET * @param $type string search type : browse / search (default search) **/ public static function showList($options, $type = 'search') { global $CFG_GLPI; $DBread = DBConnection::getReadConnection(); // Default values of parameters $params['faq'] = !Session::haveRight(self::$rightname, READ); $params["start"] = "0"; $params["knowbaseitemcategories_id"] = null; $params["contains"] = ""; $params["target"] = $_SERVER['PHP_SELF']; if (is_array($options) && count($options)) { foreach ($options as $key => $val) { $params[$key] = $val; } } $ki = new self(); switch ($type) { case 'myunpublished': if (!Session::haveRightsOr(self::$rightname, [UPDATE, self::PUBLISHFAQ])) { return false; } break; case 'allunpublished': if (!Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) { return false; } break; default: break; } if (!$params["start"]) { $params["start"] = 0; } $criteria = self::getListRequest($params, $type); $main_iterator = $DBread->request($criteria); $rows = count($main_iterator); $numrows = $rows; // Get it from database $KbCategory = new KnowbaseItemCategory(); $title = ""; if ($KbCategory->getFromDB($params["knowbaseitemcategories_id"])) { $title = (empty($KbCategory->fields['name']) ? "(" . $params['knowbaseitemcategories_id'] . ")" : $KbCategory->fields['name']); $title = sprintf(__('%1$s: %2$s'), _n('Category', 'Categories', 1), $title); } Session::initNavigateListItems('KnowbaseItem', $title); // force using getSearchUrl on list icon (when viewing a single article) $_SESSION['glpilisturl']['KnowbaseItem'] = ''; $list_limit = $_SESSION['glpilist_limit']; $showwriter = in_array($type, ['myunpublished', 'allunpublished', 'allmy']); // Limit the result, if no limit applies, use prior result if ( ($rows > $list_limit) && !isset($_GET['export_all']) ) { $criteria['START'] = (int)$params['start']; $criteria['LIMIT'] = (int)$list_limit; $main_iterator = $DBread->request($criteria); $numrows = count($main_iterator); } if ($numrows > 0) { // Set display type for export if define $output_type = Search::HTML_OUTPUT; if (isset($_GET["display_type"])) { $output_type = $_GET["display_type"]; } // Pager $parameters = "start=" . $params["start"] . "&knowbaseitemcategories_id=" . $params['knowbaseitemcategories_id'] . "&contains=" . $params["contains"] . "&is_faq=" . $params['faq']; if ( isset($options['item_itemtype']) && isset($options['item_items_id']) ) { $parameters .= "&item_items_id=" . $options['item_items_id'] . "&item_itemtype=" . $options['item_itemtype']; } $pager_url = ""; if ($output_type == Search::HTML_OUTPUT) { $pager_url = Toolbox::getItemTypeSearchURL('KnowbaseItem'); if (!Session::getLoginUserID()) { $pager_url = $CFG_GLPI['root_doc'] . "/front/helpdesk.faq.php"; } Html::printPager($params['start'], $rows, $pager_url, $parameters, 'KnowbaseItem'); } $nbcols = 1; // Display List Header echo Search::showHeader($output_type, $numrows + 1, $nbcols); echo Search::showNewLine($output_type); $header_num = 1; echo Search::showHeaderItem($output_type, __('Subject'), $header_num); if ($output_type != Search::HTML_OUTPUT) { echo Search::showHeaderItem($output_type, __('Content'), $header_num); } if ($showwriter) { echo Search::showHeaderItem($output_type, __('Writer'), $header_num); } echo Search::showHeaderItem($output_type, _n('Category', 'Categories', 1), $header_num); if ($output_type == Search::HTML_OUTPUT) { echo Search::showHeaderItem($output_type, _n('Associated element', 'Associated elements', Session::getPluralNumber()), $header_num); } if ( isset($options['item_itemtype']) && isset($options['item_items_id']) && ($output_type == Search::HTML_OUTPUT) ) { echo Search::showHeaderItem($output_type, ' ', $header_num); } // Num of the row (1=header_line) $row_num = 1; foreach ($main_iterator as $data) { Session::addToNavigateListItems('KnowbaseItem', $data["id"]); // Column num $item_num = 1; echo Search::showNewLine($output_type, ($row_num - 1) % 2); $row_num++; $item = new self(); $item->getFromDB($data["id"]); $name = $data["name"]; $answer = $data["answer"]; // Manage translations if (isset($data['transname']) && !empty($data['transname'])) { $name = $data["transname"]; } if (isset($data['transanswer']) && !empty($data['transanswer'])) { $answer = $data["transanswer"]; } if ($output_type == Search::HTML_OUTPUT) { $toadd = ''; if ( isset($options['item_itemtype']) && isset($options['item_items_id']) ) { $href = " href='#' data-bs-toggle='modal' data-bs-target='#kbshow{$data["id"]}'"; $toadd = Ajax::createIframeModalWindow( 'kbshow' . $data["id"], KnowbaseItem::getFormURLWithID($data["id"]), ['display' => false] ); } else { $href = " href=\"" . KnowbaseItem::getFormURLWithID($data["id"]) . "\" "; } $fa_class = ""; $fa_title = ""; if ( $data['is_faq'] && (!Session::isMultiEntitiesMode() || isset($data['visibility_count']) && $data['visibility_count'] > 0) ) { $fa_class = "fa-question-circle faq"; $fa_title = __s("This item is part of the FAQ"); } else if ( isset($data['visibility_count']) && $data['visibility_count'] <= 0 ) { $fa_class = "fa-eye-slash not-published"; $fa_title = __s("This item is not published yet"); } echo Search::showItem( $output_type, "<div class='kb'>$toadd <i class='fa fa-fw $fa_class' title='$fa_title'></i> <a $href>" . Html::resume_text($name, 80) . "</a></div> <div class='kb_resume'>" . Html::resume_text(RichText::getTextFromHtml($answer, false, false, true), 600) . "</div>", $item_num, $row_num ); } else { echo Search::showItem($output_type, $name, $item_num, $row_num); echo Search::showItem($output_type, RichText::getTextFromHtml($answer, true, false, true), $item_num, $row_num); } $showuserlink = 0; if (Session::haveRight('user', READ)) { $showuserlink = 1; } if ($showwriter) { echo Search::showItem( $output_type, getUserName($data["users_id"], $showuserlink), $item_num, $row_num ); } $categories_names = []; $ki->getFromDB($data["id"]); $categories = KnowbaseItem_KnowbaseItemCategory::getItems($ki); foreach ($categories as $category) { $knowbaseitemcategories_id = $category['knowbaseitemcategories_id']; $fullcategoryname = getTreeValueCompleteName( "glpi_knowbaseitemcategories", $knowbaseitemcategories_id ); if ($output_type == Search::HTML_OUTPUT) { $cathref = $ki->getSearchURL() . "?knowbaseitemcategories_id=" . $knowbaseitemcategories_id . '&forcetab=Knowbase$2'; $categories_names[] = "<a class='kb-category'" . " href='$cathref'" . " data-category-id='" . $knowbaseitemcategories_id . "'" . ">" . $fullcategoryname . '</a>'; } else { $categories_names[] = $fullcategoryname; } } echo Search::showItem($output_type, implode(', ', $categories_names), $item_num, $row_num); if ($output_type == Search::HTML_OUTPUT) { echo "<td class='center'>"; $j = 0; $iterator = $DBread->request([ 'FIELDS' => 'documents_id', 'FROM' => 'glpi_documents_items', 'WHERE' => [ 'items_id' => $data["id"], 'itemtype' => 'KnowbaseItem' ] + getEntitiesRestrictCriteria('', '', '', true) ]); foreach ($iterator as $docs) { $doc = new Document(); $doc->getFromDB($docs["documents_id"]); echo $doc->getDownloadLink(); $j++; if ($j > 1) { echo "<br>"; } } echo "</td>"; } if ( isset($options['item_itemtype']) && isset($options['item_items_id']) && ($output_type == Search::HTML_OUTPUT) ) { $forcetab = $options['item_itemtype'] . '$1'; $item_itemtype = $options['item_itemtype']; $content = "<a href='" . $item_itemtype::getFormURLWithID($options['item_items_id']) . "&load_kb_sol=" . $data['id'] . "&forcetab=" . $forcetab . "'>" . __('Use as a solution') . "</a>"; echo Search::showItem($output_type, $content, $item_num, $row_num); } // End Line echo Search::showEndLine($output_type); } // Display footer if ( ($output_type == Search::PDF_OUTPUT_LANDSCAPE) || ($output_type == Search::PDF_OUTPUT_PORTRAIT) ) { echo Search::showFooter( $output_type, Dropdown::getDropdownName( "glpi_knowbaseitemcategories", $params['knowbaseitemcategories_id'] ), $numrows ); } else { echo Search::showFooter($output_type, '', $numrows); } echo "<br>"; if ($output_type == Search::HTML_OUTPUT) { Html::printPager($params['start'], $rows, $pager_url, $parameters, 'KnowbaseItem'); } } else { echo "<div class='center b'>" . __('No item found') . "</div>"; } } /** * Print out list recent or popular kb/faq * * @param string $type type : recent / popular / not published * @param bool $display if false, return html * * @return void **/ public static function showRecentPopular(string $type = "", bool $display = true) { global $DB; $faq = !Session::haveRight(self::$rightname, READ); $criteria = [ 'SELECT' => ['glpi_knowbaseitems.*'], 'DISTINCT' => true, 'FROM' => self::getTable(), 'WHERE' => [], 'LIMIT' => 10 ]; if ($type == "recent") { $criteria['ORDERBY'] = 'date_creation DESC'; $title = __('Recent entries'); } else if ($type == 'lastupdate') { $criteria['ORDERBY'] = 'date_mod DESC'; $title = __('Last updated entries'); } else { $criteria['ORDERBY'] = 'view DESC'; $title = __('Most popular questions'); } // Force all joins for not published to verify no visibility set $restrict = self::getVisibilityCriteria(true); unset($restrict['WHERE']); unset($restrict['SELECT']); $criteria = array_merge($criteria, $restrict); if (Session::getLoginUserID()) { $restrict = self::getVisibilityCriteria(); $criteria['WHERE'] = array_merge($criteria['WHERE'], $restrict['WHERE']); } else { // Anonymous access if (Session::isMultiEntitiesMode()) { $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = 0; $criteria['WHERE']['glpi_entities_knowbaseitems.is_recursive'] = 1; } } // Only published $criteria['WHERE'][] = [ 'NOT' => [ 'glpi_entities_knowbaseitems.entities_id' => null, 'glpi_knowbaseitems_profiles.profiles_id' => null, 'glpi_groups_knowbaseitems.groups_id' => null, 'glpi_knowbaseitems_users.users_id' => null ] ]; // Add visibility date $criteria['WHERE'][] = [ 'OR' => [ ['glpi_knowbaseitems.begin_date' => null], ['glpi_knowbaseitems.begin_date' => ['<', new QueryExpression('NOW()')]] ] ]; $criteria['WHERE'][] = [ 'OR' => [ ['glpi_knowbaseitems.end_date' => null], ['glpi_knowbaseitems.end_date' => ['>', new QueryExpression('NOW()')]] ] ]; if ($faq) { // FAQ $criteria['WHERE']['glpi_knowbaseitems.is_faq'] = 1; } if ( KnowbaseItemTranslation::isKbTranslationActive() && (countElementsInTable('glpi_knowbaseitemtranslations') > 0) ) { $criteria['LEFT JOIN']['glpi_knowbaseitemtranslations'] = [ 'ON' => [ 'glpi_knowbaseitems' => 'id', 'glpi_knowbaseitemtranslations' => 'knowbaseitems_id', [ 'AND' => [ 'glpi_knowbaseitemtranslations.language' => $_SESSION['glpilanguage'] ] ] ] ]; $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.name AS transname'; $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.answer AS transanswer'; } $iterator = $DB->request($criteria); $output = ""; if (count($iterator)) { $output .= "<table class='tab_cadrehov'>"; $output .= "<tr class='noHover'><th>" . $title . "</th></tr>"; foreach ($iterator as $data) { $name = $data['name']; if (isset($data['transname']) && !empty($data['transname'])) { $name = $data['transname']; } $output .= "<tr class='tab_bg_2'><td class='left'><div class='kb'>"; if ($data['is_faq']) { $output .= "<i class='fa fa-fw fa-question-circle faq' title='" . __("This item is part of the FAQ") . "'></i>"; } $output .= Html::link(Html::resume_text($name, 80), KnowbaseItem::getFormURLWithID($data["id"]), [ 'class' => $data['is_faq'] ? 'faq' : 'knowbase', 'title' => $data['is_faq'] ? __s("This item is part of the FAQ") : '' ]); $output .= "</div></td></tr>"; } $output .= "</table>"; } if ($display) { echo $output; } else { return $output; } } public function rawSearchOptions() { $tab = []; $tab[] = [ 'id' => 'common', 'name' => __('Characteristics') ]; $tab[] = [ 'id' => '2', 'table' => $this->getTable(), 'field' => 'id', 'name' => __('ID'), 'massiveaction' => false, 'datatype' => 'number' ]; $tab[] = [ 'id' => '6', 'table' => $this->getTable(), 'field' => 'name', 'name' => __('Subject'), 'datatype' => 'text' ]; $tab[] = [ 'id' => '7', 'table' => $this->getTable(), 'field' => 'answer', 'name' => __('Content'), 'datatype' => 'text', 'htmltext' => true ]; $tab[] = [ 'id' => '8', 'table' => $this->getTable(), 'field' => 'is_faq', 'name' => __('FAQ item'), 'datatype' => 'bool' ]; $tab[] = [ 'id' => '9', 'table' => $this->getTable(), 'field' => 'view', 'name' => _n('View', 'Views', Session::getPluralNumber()), 'datatype' => 'integer', 'massiveaction' => false ]; $tab[] = [ 'id' => '10', 'table' => $this->getTable(), 'field' => 'begin_date', 'name' => __('Visibility start date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '11', 'table' => $this->getTable(), 'field' => 'end_date', 'name' => __('Visibility end date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '19', 'table' => $this->getTable(), 'field' => 'date_mod', 'name' => __('Last update'), 'datatype' => 'datetime', 'massiveaction' => false ]; $tab[] = [ 'id' => '121', 'table' => $this->getTable(), 'field' => 'date_creation', 'name' => __('Creation date'), 'datatype' => 'datetime', 'massiveaction' => false ]; $tab[] = [ 'id' => '70', 'table' => 'glpi_users', 'field' => 'name', 'name' => User::getTypeName(1), 'massiveaction' => false, 'datatype' => 'dropdown', 'right' => 'all' ]; // add objectlock search options $tab = array_merge($tab, ObjectLock::rawSearchOptionsToAdd(get_class($this))); return $tab; } public function getRights($interface = 'central') { if ($interface == 'central') { $values = parent::getRights(); $values[self::KNOWBASEADMIN] = __('Knowledge base administration'); $values[self::PUBLISHFAQ] = __('Publish in the FAQ'); $values[self::COMMENTS] = __('Comment KB entries'); } $values[self::READFAQ] = __('Read the FAQ'); return $values; } public function pre_updateInDB() { $revision = new KnowbaseItem_Revision(); $kb = new KnowbaseItem(); $kb->getFromDB($this->getID()); $revision->createNew($kb); } /** * Get KB answer, with id on titles to set anchors * * @return string */ public function getAnswer() { if (KnowbaseItemTranslation::canBeTranslated($this)) { $answer = KnowbaseItemTranslation::getTranslatedValue($this, 'answer'); } else { $answer = $this->fields["answer"]; } $answer = RichText::getEnhancedHtml($answer, [ 'text_maxsize' => 0 // Show all text without read more button ]); $callback = function ($matches) { //1 => tag name, 2 => existing attributes, 3 => title contents $tpl = '<%tag%attrs id="%slug"><a href="#%slug">%icon</a>%title</%tag>'; $title = str_replace( ['%tag', '%attrs', '%slug', '%title', '%icon'], [ $matches[1], $matches[2], Toolbox::slugify($matches[3]), $matches[3], '<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"/></svg>' ], $tpl ); return $title; }; $pattern = '|<(h[1-6]{1})(.?[^>])?>(.+?)</h[1-6]{1}>|'; $answer = preg_replace_callback($pattern, $callback, $answer); return $answer; } /** * Get dropdown parameters from showVisibility method * * @return array */ protected function getShowVisibilityDropdownParams() { $params = parent::getShowVisibilityDropdownParams(); $params['right'] = ($this->getField('is_faq') ? 'faq' : 'knowbase'); $params['allusers'] = 1; return $params; } /** * Reverts item contents to specified revision * * @param integer $revid Revision ID * * @return boolean */ public function revertTo($revid) { $revision = new KnowbaseItem_Revision(); $revision->getFromDB($revid); $values = [ 'id' => $this->getID(), 'name' => addslashes($revision->fields['name']), 'answer' => addslashes($revision->fields['answer']) ]; if ($this->update($values)) { Event::log( $this->getID(), "knowbaseitem", 5, "tools", //TRANS: %1$s is the user login, %2$s the revision number sprintf(__('%1$s reverts item to revision %2$s'), $_SESSION["glpiname"], $revid) ); return true; } else { return false; } } /** * Get ids of KBI in given category * * @param int $category_id id of the parent category * @param KnowbaseItem $kbi used only for unit tests * * @return array Array of ids */ public static function getForCategory($category_id, $kbi = null) { global $DB; if ($kbi === null) { $kbi = new self(); } $ids = $DB->request([ 'SELECT' => self::getTable() . '.id', 'FROM' => self::getTable(), 'LEFT JOIN' => [ 'glpi_knowbaseitems_knowbaseitemcategories' => [ 'ON' => [ 'glpi_knowbaseitems_knowbaseitemcategories' => 'knowbaseitems_id', 'glpi_knowbaseitems' => 'id' ] ] ], 'WHERE' => ['glpi_knowbaseitems_knowbaseitemcategories.knowbaseitemcategories_id' => $category_id], ]); // Get array of ids $ids = array_map(function ($row) { return $row['id']; }, iterator_to_array($ids, false)); // Filter on canViewItem $ids = array_filter($ids, function ($id) use ($kbi) { $kbi->getFromDB($id); return $kbi->canViewItem(); }); // Avoid empty IN if (count($ids) === 0) { $ids[] = -1; } return $ids; } public static function getIcon() { return "ti ti-lifebuoy"; } }