Program: CWE-79
Project: CVE-2020-15217
Buggy source:
<?php
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2015-2020 Teclib' and contributors.
 *
 * http://glpi-project.org
 *
 * based on GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2003-2014 by the INDEPNET Development Team.
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * GLPI 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 2 of the License, or
 * (at your option) any later version.
 *
 * GLPI 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 GLPI. If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
 */

use Glpi\Event;

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}

/**
 * KnowbaseItem Class
**/
class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {


   // From CommonDBTM
   public $dohistory    = true;

   // For visibility checks
   protected $users     = [];
   protected $groups    = [];
   protected $profiles  = [];
   protected $entities  = [];
   protected $items     = [];

   const KNOWBASEADMIN = 1024;
   const READFAQ       = 2048;
   const PUBLISHFAQ    = 4096;
   const COMMENTS      = 8192;

   static $rightname   = 'knowbase';


   static function getTypeName($nb = 0) {
      return __('Knowledge base');
   }


   /**
    * @see CommonGLPI::getMenuShorcut()
    *
    * @since 0.85
   **/
   static function getMenuShorcut() {
      return 'b';
   }


   function getName($options = []) {
      if (KnowbaseItemTranslation::canBeTranslated($this)) {
         return KnowbaseItemTranslation::getTranslatedValue($this);
      }

      return parent::getName();
   }


   /**
    * @see CommonGLPI::getMenuName()
    *
    * @since 0.85
   **/
   static function getMenuName() {
      if (!Session::haveRight('knowbase', READ)) {
         return __('FAQ');
      } else {
         return static::getTypeName(Session::getPluralNumber());
      }
   }


   static function canCreate() {

      return Session::haveRightsOr(self::$rightname, [CREATE, self::PUBLISHFAQ]);
   }


   /**
    * @since 0.85
   **/
   static function canUpdate() {
      return Session::haveRightsOr(self::$rightname, [UPDATE, self::KNOWBASEADMIN]);
   }


   static function canView() {
      global $CFG_GLPI;

      return (Session::haveRightsOr(self::$rightname, [READ, self::READFAQ])
              || ((Session::getLoginUserID() === false) && $CFG_GLPI["use_public_faq"]));
   }


   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());
   }


   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
   **/
   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
   **/
   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";
   }

   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;
   }


   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 '';
   }


   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
   **/
   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()
   **/
   function post_addItem() {

      // add screenshots
      $this->input = $this->addFiles(
         $this->input, [
            'force_update'  => true,
            'content_field' => 'answer',
            'name'          => 'answer',
         ]
      );

      // Add documents
      $this->input = $this->addFiles($this->input, ['force_update' => true]);

      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);
      }
   }


   /**
    * @since 0.83
   **/
   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']);

      //Linked kb items
      $this->knowbase_items = KnowbaseItem_Item::getItems($this);
   }


   /**
    * @see CommonDBTM::cleanDBonPurge()
    *
    * @since 0.83.1
   **/
   function cleanDBonPurge() {

      $this->deleteChildrenAndRelationsFromDb(
         [
            Entity_KnowbaseItem::class,
            Group_KnowbaseItem::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
   **/
   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
   **/
   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
   **/
   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
    */
   static public function getVisibilityCriteria(bool $forceall = false): array {
      global $CFG_GLPI;

      $is_public_faq_context = !Session::getLoginUserID() && $CFG_GLPI["use_public_faq"];
      $has_session_groups = isset($_SESSION["glpigroups"]) && count($_SESSION["glpigroups"]);
      $has_active_profile = isset($_SESSION["glpiactiveprofile"])
         && isset($_SESSION["glpiactiveprofile"]['id']);
      $has_active_entity = isset($_SESSION["glpiactiveentities"])
         && count($_SESSION["glpiactiveentities"]);

      $where = [];
      $join = [
         'glpi_knowbaseitems_users' => [
            'ON' => [
               'glpi_knowbaseitems_users' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ]
      ];
      if ($forceall || $has_session_groups) {
         $join['glpi_groups_knowbaseitems'] = [
            'ON' => [
               'glpi_groups_knowbaseitems' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ];
      }
      if ($forceall || $has_active_profile) {
         $join['glpi_knowbaseitems_profiles'] = [
            'ON' => [
               'glpi_knowbaseitems_profiles' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ];
      }
      if ($forceall || $has_active_entity || $is_public_faq_context) {
         $join['glpi_entities_knowbaseitems'] = [
            'ON' => [
               'glpi_entities_knowbaseitems' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ];
      }

      if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
         return [
            'LEFT JOIN' => $join,
            'WHERE' => [],
         ];
      }

      // Users
      if (Session::getLoginUserID()) {
         $where['OR'] = [
               'glpi_knowbaseitems.users_id'       => Session::getLoginUserID(),
               'glpi_knowbaseitems_users.users_id' => Session::getLoginUserID(),
         ];

         // public faq
         if (!Session::haveRight(self::$rightname, READ)) {
            $where['AND']['glpi_knowbaseitems.is_faq'] = 1;
         }
      } else if ($is_public_faq_context) {
         $where = [
            "glpi_knowbaseitems.is_faq" => 1,
         ];
         if (Session::isMultiEntitiesMode()) {
            $where += [
               "glpi_entities_knowbaseitems.entities_id" => 0,
               "glpi_entities_knowbaseitems.is_recursive" => 1,
            ];
         }
      } else {
         $where = [
            0
         ];
      }
      // Groups
      if ($forceall || $has_session_groups) {
         if (Session::getLoginUserID()) {
            $restrict = getEntitiesRestrictCriteria('glpi_groups_knowbaseitems', '', '', true, true);
            $where['OR'][] = [
               'glpi_groups_knowbaseitems.groups_id' => count($_SESSION["glpigroups"])
                                                         ? $_SESSION["glpigroups"]
                                                         : [-1],
               'OR' => [
                  'glpi_groups_knowbaseitems.entities_id' => ['<', '0'],
               ] + $restrict
            ];
         }
      }

      // Profiles
      if ($forceall || $has_active_profile) {
         if (Session::getLoginUserID()) {
            $where['OR'][] = [
               'glpi_knowbaseitems_profiles.profiles_id' => $_SESSION["glpiactiveprofile"]['id'],
               'OR' => [
                  'glpi_knowbaseitems_profiles.entities_id' => ['<', '0'],
                  getEntitiesRestrictCriteria('glpi_knowbaseitems_profiles', '', '', true, true)
               ]
            ];
         }
      }

      // Entities
      if ($forceall || $has_active_entity) {
         if (Session::getLoginUserID()) {
            $restrict = getEntitiesRestrictCriteria('glpi_entities_knowbaseitems', '', '', true, true);
            if (count($restrict)) {
               $where['OR'] = $where['OR'] + $restrict;
            } else {
               $where['glpi_entities_knowbaseitems.entities_id'] = null;
            }
         }
      }

      $criteria = ['LEFT JOIN' => $join];
      if (count($where)) {
         $criteria['WHERE'] = $where;
      }

      return $criteria;
   }

   function prepareInputForAdd($input) {

      // set new date if not exists
      if (!isset($input["date"]) || empty($input["date"])) {
         $input["date"] = $_SESSION["glpi_currenttime"];
      }
      // set users_id

      // 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;
   }


   function prepareInputForUpdate($input) {
      // set title for question if empty
      if (isset($input["name"]) && empty($input["name"])) {
         $input["name"] = __('New item');
      }
      return $input;
   }

   function post_updateItem($history = 1) {
      // Update screenshots
      $this->input = $this->addFiles(
         $this->input,
         [
            'force_update'  => true,
            'content_field' => 'answer',
            'name'          => 'answer',
         ]
      );

      // add uploaded documents
      $this->input = $this->addFiles(
         $this->input,
         [
            'force_update'  => true,
         ]
      );
   }


   /**
    * Print out an HTML "<form>" for knowbase item
    *
    * @param $ID
    * @param $options array
    *     - target for the Form
    *
    * @return void
   **/
   function showForm($ID, $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');
               $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>".__('Category name')."</td>";
      echo "<td>";
      echo "<input type='hidden' name='users_id' value=\"".Session::getLoginUserID()."\">";
      KnowbaseItemCategory::dropdown(['value' => $this->fields["knowbaseitemcategories_id"]]);
      echo "</td>";
      echo "<td>";
      if ($this->fields["date"]) {
         //TRANS: %s is the datetime of insertion
         printf(__('Created on %s'), Html::convDateTime($this->fields["date"]));
      }
      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 cols='100' rows='1' 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'             => $this->fields["answer"],
                      '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;
   } // function showForm


   /**
    * Add kb item to the public FAQ
    *
    * @return void
   **/
   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
    */
   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)
   **/
   function showFull($options = []) {
      global $CFG_GLPI;

      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();

      $knowbaseitemcategories_id = $this->fields["knowbaseitemcategories_id"];
      $fullcategoryname          = getTreeValueCompleteName("glpi_knowbaseitemcategories",
                                                            $knowbaseitemcategories_id);

      $tmp = "<a href='".$this->getSearchURL().
             "?knowbaseitemcategories_id=$knowbaseitemcategories_id&forcetab=Knowbase$2'>".
             $fullcategoryname."</a>";
      $out.= "<table class='tab_cadre_fixe'>";
      $out.= "<tr><th colspan='4'>".sprintf(__('%1$s: %2$s'), __('Category'), $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 id='kbanswer'>";
      $out.= $this->getAnswer();
      $out.= "</div>";
      $out.= "</td></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"]) {
         //TRANS: %s is the datetime of update
         $out.= sprintf(__('Created on %s'), Html::convDateTime($this->fields["date"]));
         $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
   **/
   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 "<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%'>";
      echo "<input type='text' size='50' name='contains' value=\"".
             Html::cleanInputText(stripslashes($params["contains"]))."\"></td>";
      echo "<td class='left'>";
      echo "<input type='submit' value=\""._sx('button', 'Search')."\" class='submit'></td></tr>";
      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();

      echo "</div>";
   }


   /**
    * Print out an HTML "<form>" for Search knowbase item
    *
    * @since 0.84
    *
    * @param $options   $_GET
    *
    * @return void
   **/
   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%'>".__('Category')."&nbsp;";
         KnowbaseItemCategory::dropdown(['value' => $params["knowbaseitemcategories_id"]]);
         echo "</td><td class='left'>";
         echo "<input type='submit' value=\""._sx('button', 'Post')."\" class='submit'></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
   **/
   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='submit'></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 String : SQL request
   **/
   static function getListRequest(array $params, $type = 'search') {
      global $DB;

      $criteria = [
         'SELECT' => [
            'glpi_knowbaseitems.*',
            'glpi_knowbaseitemcategories.completename AS category',
            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', 'glpi_knowbaseitemcategories.completename']
      ];

      // 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']['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';
      }

      // 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 'search' :
            if (strlen($params["contains"]) > 0) {
               $search  = Toolbox::unclean_cross_side_scripting_deep($params["contains"]);
               $search_wilcard = explode(' ', $search);
               $search_wilcard = implode('* ', $search_wilcard).'*';

               $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 = ['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->next()['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' :
            $criteria['WHERE']['glpi_knowbaseitems.knowbaseitemcategories_id'] = $params['knowbaseitemcategories_id'];

            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;
      }

      $criteria['LEFT JOIN']['glpi_knowbaseitemcategories'] = [
         'ON'  => [
            'glpi_knowbaseitemcategories' => 'id',
            'glpi_knowbaseitems'          => 'knowbaseitemcategories_id'
         ]
      ];

      return $criteria;
   }


   /**
    * Print out list kb item
    *
    * @param $options            $_GET
    * @param $type      string   search type : browse / search (default search)
   **/
   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"] = "0";
      $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'), __('Category'), $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"]."&amp;knowbaseitemcategories_id=".
                        $params['knowbaseitemcategories_id']."&amp;contains=".
                        $params["contains"]."&amp;is_faq=".$params['faq'];

         if (isset($options['item_itemtype'])
               && isset($options['item_items_id'])) {
            $parameters .= "&amp;item_items_id=".$options['item_items_id']."&amp;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, __('Category'), $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, '&nbsp;', $header_num);
         }

         // Num of the row (1=header_line)
         $row_num = 1;
         while ($data = $main_iterator->next()) {
            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='#' onClick=\"".Html::jsGetElementbyID('kbshow'.$data["id"]).".dialog('open'); return false;\"";
                  $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(Html::clean(Toolbox::unclean_cross_side_scripting_deep($answer)),
                                                         600)."</div>",
                                       $item_num, $row_num);
            } else {
               echo Search::showItem($output_type, $name, $item_num, $row_num);
               echo Search::showItem($output_type,
                  Html::clean(Toolbox::unclean_cross_side_scripting_deep(html_entity_decode($answer,
                                                                                             ENT_QUOTES,
                                                                                             "UTF-8"))),
                              $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);
            }

            $categ = $data["category"];
            $inst = new KnowbaseItemCategory;
            if (DropdownTranslation::canBeTranslated($inst)) {
               $tcateg = DropdownTranslation::getTranslatedValue(
                  $data["knowbaseitemcategories_id"],
                  $inst->getType()
               );
               if (!empty($tcateg)) {
                  $categ = $tcateg;
               }
            }

            if ($output_type == Search::HTML_OUTPUT) {
               $cathref = $ki->getSearchURL()."?knowbaseitemcategories_id=".
                           $data["knowbaseitemcategories_id"].'&amp;forcetab=Knowbase$2';
               $categ   = "<a class='kb-category'"
                  . " href='$cathref'"
                  . " data-category-id='" . $data["knowbaseitemcategories_id"] . "'"
                  . ">".$categ.'</a>';
            }
            echo Search::showItem($output_type, $categ, $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'
                  ]
               ]);
               while ($docs = $iterator->next()) {
                  $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']).
                              "&amp;load_kb_sol=".$data['id'].
                              "&amp;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
    *
    * @return void
   **/
   static function showRecentPopular($type) {
      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 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);

      if (count($iterator)) {
         echo "<table class='tab_cadrehov'>";
         echo "<tr class='noHover'><th>".$title."</th></tr>";
         while ($data = $iterator->next()) {
            $name = $data['name'];

            if (isset($data['transname']) && !empty($data['transname'])) {
               $name = $data['transname'];
            }
            echo "<tr class='tab_bg_2'><td class='left'><div class='kb'>";
            if ($data['is_faq']) {
               echo "<i class='fa fa-fw fa-question-circle faq' title='".__("This item is part of the FAQ")."'></i>";
            }
            echo 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") : ''
            ]);
            echo "</div></td></tr>";
         }
         echo "</table>";
      }
   }


   function rawSearchOptions() {
      $tab = [];

      $tab[] = [
         'id'                 => 'common',
         'name'               => __('Characteristics')
      ];

      $tab[] = [
         'id'                 => '2',
         'table'              => $this->getTable(),
         'field'              => 'id',
         'name'               => __('ID'),
         'massiveaction'      => false,
         'datatype'           => 'number'
      ];

      $tab[] = [
         'id'                 => '4',
         'table'              => 'glpi_knowbaseitemcategories',
         'field'              => 'name',
         'name'               => __('Category'),
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '5',
         'table'              => $this->getTable(),
         'field'              => 'date',
         'name'               => _n('Date', 'Dates', 1),
         'datatype'           => 'datetime',
         'massiveaction'      => false
      ];

      $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'                 => '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;
   }

   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;
   }

   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 = html_entity_decode($answer);
      $answer = Toolbox::unclean_html_cross_side_scripting_deep($answer);

      $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');
      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' => 'id',
         'FROM'   => self::getTable(),
         'WHERE'  => ['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;
   }


   static function getIcon() {
      return "fas fa-question";
   }
}


Fixed version (ground truth):
<?php
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2015-2020 Teclib' and contributors.
 *
 * http://glpi-project.org
 *
 * based on GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2003-2014 by the INDEPNET Development Team.
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * GLPI 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 2 of the License, or
 * (at your option) any later version.
 *
 * GLPI 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 GLPI. If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
 */

use Glpi\Event;

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}

/**
 * KnowbaseItem Class
**/
class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {


   // From CommonDBTM
   public $dohistory    = true;

   // For visibility checks
   protected $users     = [];
   protected $groups    = [];
   protected $profiles  = [];
   protected $entities  = [];
   protected $items     = [];

   const KNOWBASEADMIN = 1024;
   const READFAQ       = 2048;
   const PUBLISHFAQ    = 4096;
   const COMMENTS      = 8192;

   static $rightname   = 'knowbase';


   static function getTypeName($nb = 0) {
      return __('Knowledge base');
   }


   /**
    * @see CommonGLPI::getMenuShorcut()
    *
    * @since 0.85
   **/
   static function getMenuShorcut() {
      return 'b';
   }


   function getName($options = []) {
      if (KnowbaseItemTranslation::canBeTranslated($this)) {
         return KnowbaseItemTranslation::getTranslatedValue($this);
      }

      return parent::getName();
   }


   /**
    * @see CommonGLPI::getMenuName()
    *
    * @since 0.85
   **/
   static function getMenuName() {
      if (!Session::haveRight('knowbase', READ)) {
         return __('FAQ');
      } else {
         return static::getTypeName(Session::getPluralNumber());
      }
   }


   static function canCreate() {

      return Session::haveRightsOr(self::$rightname, [CREATE, self::PUBLISHFAQ]);
   }


   /**
    * @since 0.85
   **/
   static function canUpdate() {
      return Session::haveRightsOr(self::$rightname, [UPDATE, self::KNOWBASEADMIN]);
   }


   static function canView() {
      global $CFG_GLPI;

      return (Session::haveRightsOr(self::$rightname, [READ, self::READFAQ])
              || ((Session::getLoginUserID() === false) && $CFG_GLPI["use_public_faq"]));
   }


   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());
   }


   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
   **/
   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
   **/
   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";
   }

   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;
   }


   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 '';
   }


   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
   **/
   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()
   **/
   function post_addItem() {

      // add screenshots
      $this->input = $this->addFiles(
         $this->input, [
            'force_update'  => true,
            'content_field' => 'answer',
            'name'          => 'answer',
         ]
      );

      // Add documents
      $this->input = $this->addFiles($this->input, ['force_update' => true]);

      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);
      }
   }


   /**
    * @since 0.83
   **/
   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']);

      //Linked kb items
      $this->knowbase_items = KnowbaseItem_Item::getItems($this);
   }


   /**
    * @see CommonDBTM::cleanDBonPurge()
    *
    * @since 0.83.1
   **/
   function cleanDBonPurge() {

      $this->deleteChildrenAndRelationsFromDb(
         [
            Entity_KnowbaseItem::class,
            Group_KnowbaseItem::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
   **/
   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
   **/
   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
   **/
   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
    */
   static public function getVisibilityCriteria(bool $forceall = false): array {
      global $CFG_GLPI;

      $is_public_faq_context = !Session::getLoginUserID() && $CFG_GLPI["use_public_faq"];
      $has_session_groups = isset($_SESSION["glpigroups"]) && count($_SESSION["glpigroups"]);
      $has_active_profile = isset($_SESSION["glpiactiveprofile"])
         && isset($_SESSION["glpiactiveprofile"]['id']);
      $has_active_entity = isset($_SESSION["glpiactiveentities"])
         && count($_SESSION["glpiactiveentities"]);

      $where = [];
      $join = [
         'glpi_knowbaseitems_users' => [
            'ON' => [
               'glpi_knowbaseitems_users' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ]
      ];
      if ($forceall || $has_session_groups) {
         $join['glpi_groups_knowbaseitems'] = [
            'ON' => [
               'glpi_groups_knowbaseitems' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ];
      }
      if ($forceall || $has_active_profile) {
         $join['glpi_knowbaseitems_profiles'] = [
            'ON' => [
               'glpi_knowbaseitems_profiles' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ];
      }
      if ($forceall || $has_active_entity || $is_public_faq_context) {
         $join['glpi_entities_knowbaseitems'] = [
            'ON' => [
               'glpi_entities_knowbaseitems' => 'knowbaseitems_id',
               'glpi_knowbaseitems'       => 'id'
            ]
         ];
      }

      if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
         return [
            'LEFT JOIN' => $join,
            'WHERE' => [],
         ];
      }

      // Users
      if (Session::getLoginUserID()) {
         $where['OR'] = [
               'glpi_knowbaseitems.users_id'       => Session::getLoginUserID(),
               'glpi_knowbaseitems_users.users_id' => Session::getLoginUserID(),
         ];

         // public faq
         if (!Session::haveRight(self::$rightname, READ)) {
            $where['AND']['glpi_knowbaseitems.is_faq'] = 1;
         }
      } else if ($is_public_faq_context) {
         $where = [
            "glpi_knowbaseitems.is_faq" => 1,
         ];
         if (Session::isMultiEntitiesMode()) {
            $where += [
               "glpi_entities_knowbaseitems.entities_id" => 0,
               "glpi_entities_knowbaseitems.is_recursive" => 1,
            ];
         }
      } else {
         $where = [
            0
         ];
      }
      // Groups
      if ($forceall || $has_session_groups) {
         if (Session::getLoginUserID()) {
            $restrict = getEntitiesRestrictCriteria('glpi_groups_knowbaseitems', '', '', true, true);
            $where['OR'][] = [
               'glpi_groups_knowbaseitems.groups_id' => count($_SESSION["glpigroups"])
                                                         ? $_SESSION["glpigroups"]
                                                         : [-1],
               'OR' => [
                  'glpi_groups_knowbaseitems.entities_id' => ['<', '0'],
               ] + $restrict
            ];
         }
      }

      // Profiles
      if ($forceall || $has_active_profile) {
         if (Session::getLoginUserID()) {
            $where['OR'][] = [
               'glpi_knowbaseitems_profiles.profiles_id' => $_SESSION["glpiactiveprofile"]['id'],
               'OR' => [
                  'glpi_knowbaseitems_profiles.entities_id' => ['<', '0'],
                  getEntitiesRestrictCriteria('glpi_knowbaseitems_profiles', '', '', true, true)
               ]
            ];
         }
      }

      // Entities
      if ($forceall || $has_active_entity) {
         if (Session::getLoginUserID()) {
            $restrict = getEntitiesRestrictCriteria('glpi_entities_knowbaseitems', '', '', true, true);
            if (count($restrict)) {
               $where['OR'] = $where['OR'] + $restrict;
            } else {
               $where['glpi_entities_knowbaseitems.entities_id'] = null;
            }
         }
      }

      $criteria = ['LEFT JOIN' => $join];
      if (count($where)) {
         $criteria['WHERE'] = $where;
      }

      return $criteria;
   }

   function prepareInputForAdd($input) {

      // set new date if not exists
      if (!isset($input["date"]) || empty($input["date"])) {
         $input["date"] = $_SESSION["glpi_currenttime"];
      }
      // set users_id

      // 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;
   }


   function prepareInputForUpdate($input) {
      // set title for question if empty
      if (isset($input["name"]) && empty($input["name"])) {
         $input["name"] = __('New item');
      }
      return $input;
   }

   function post_updateItem($history = 1) {
      // Update screenshots
      $this->input = $this->addFiles(
         $this->input,
         [
            'force_update'  => true,
            'content_field' => 'answer',
            'name'          => 'answer',
         ]
      );

      // add uploaded documents
      $this->input = $this->addFiles(
         $this->input,
         [
            'force_update'  => true,
         ]
      );
   }


   /**
    * Print out an HTML "<form>" for knowbase item
    *
    * @param $ID
    * @param $options array
    *     - target for the Form
    *
    * @return void
   **/
   function showForm($ID, $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');
               $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>".__('Category name')."</td>";
      echo "<td>";
      echo "<input type='hidden' name='users_id' value=\"".Session::getLoginUserID()."\">";
      KnowbaseItemCategory::dropdown(['value' => $this->fields["knowbaseitemcategories_id"]]);
      echo "</td>";
      echo "<td>";
      if ($this->fields["date"]) {
         //TRANS: %s is the datetime of insertion
         printf(__('Created on %s'), Html::convDateTime($this->fields["date"]));
      }
      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 cols='100' rows='1' 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'             => $this->fields["answer"],
                      '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;
   } // function showForm


   /**
    * Add kb item to the public FAQ
    *
    * @return void
   **/
   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
    */
   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)
   **/
   function showFull($options = []) {
      global $CFG_GLPI;

      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();

      $knowbaseitemcategories_id = $this->fields["knowbaseitemcategories_id"];
      $fullcategoryname          = getTreeValueCompleteName("glpi_knowbaseitemcategories",
                                                            $knowbaseitemcategories_id);

      $tmp = "<a href='".$this->getSearchURL().
             "?knowbaseitemcategories_id=$knowbaseitemcategories_id&forcetab=Knowbase$2'>".
             $fullcategoryname."</a>";
      $out.= "<table class='tab_cadre_fixe'>";
      $out.= "<tr><th colspan='4'>".sprintf(__('%1$s: %2$s'), __('Category'), $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 id='kbanswer'>";
      $out.= $this->getAnswer();
      $out.= "</div>";
      $out.= "</td></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"]) {
         //TRANS: %s is the datetime of update
         $out.= sprintf(__('Created on %s'), Html::convDateTime($this->fields["date"]));
         $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
   **/
   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 "<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%'>";
      echo "<input type='text' size='50' name='contains' value=\"".
             Html::cleanInputText(stripslashes($params["contains"]))."\"></td>";
      echo "<td class='left'>";
      echo "<input type='submit' value=\""._sx('button', 'Search')."\" class='submit'></td></tr>";
      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();

      echo "</div>";
   }


   /**
    * Print out an HTML "<form>" for Search knowbase item
    *
    * @since 0.84
    *
    * @param $options   $_GET
    *
    * @return void
   **/
   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%'>".__('Category')."&nbsp;";
         KnowbaseItemCategory::dropdown(['value' => $params["knowbaseitemcategories_id"]]);
         echo "</td><td class='left'>";
         echo "<input type='submit' value=\""._sx('button', 'Post')."\" class='submit'></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
   **/
   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='submit'></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 String : SQL request
   **/
   static function getListRequest(array $params, $type = 'search') {
      global $DB;

      $criteria = [
         'SELECT' => [
            'glpi_knowbaseitems.*',
            'glpi_knowbaseitemcategories.completename AS category',
            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', 'glpi_knowbaseitemcategories.completename']
      ];

      // 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']['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';
      }

      // 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 'search' :
            if (strlen($params["contains"]) > 0) {
               $search  = Toolbox::unclean_cross_side_scripting_deep($params["contains"]);
               $search_wilcard = explode(' ', $search);
               $search_wilcard = implode('* ', $search_wilcard).'*';

               $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->next()['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' :
            $criteria['WHERE']['glpi_knowbaseitems.knowbaseitemcategories_id'] = $params['knowbaseitemcategories_id'];

            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;
      }

      $criteria['LEFT JOIN']['glpi_knowbaseitemcategories'] = [
         'ON'  => [
            'glpi_knowbaseitemcategories' => 'id',
            'glpi_knowbaseitems'          => 'knowbaseitemcategories_id'
         ]
      ];

      return $criteria;
   }


   /**
    * Print out list kb item
    *
    * @param $options            $_GET
    * @param $type      string   search type : browse / search (default search)
   **/
   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"] = "0";
      $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'), __('Category'), $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"]."&amp;knowbaseitemcategories_id=".
                        $params['knowbaseitemcategories_id']."&amp;contains=".
                        $params["contains"]."&amp;is_faq=".$params['faq'];

         if (isset($options['item_itemtype'])
               && isset($options['item_items_id'])) {
            $parameters .= "&amp;item_items_id=".$options['item_items_id']."&amp;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, __('Category'), $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, '&nbsp;', $header_num);
         }

         // Num of the row (1=header_line)
         $row_num = 1;
         while ($data = $main_iterator->next()) {
            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='#' onClick=\"".Html::jsGetElementbyID('kbshow'.$data["id"]).".dialog('open'); return false;\"";
                  $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(Html::clean(Toolbox::unclean_cross_side_scripting_deep($answer)),
                                                         600)."</div>",
                                       $item_num, $row_num);
            } else {
               echo Search::showItem($output_type, $name, $item_num, $row_num);
               echo Search::showItem($output_type,
                  Html::clean(Toolbox::unclean_cross_side_scripting_deep(html_entity_decode($answer,
                                                                                             ENT_QUOTES,
                                                                                             "UTF-8"))),
                              $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);
            }

            $categ = $data["category"];
            $inst = new KnowbaseItemCategory;
            if (DropdownTranslation::canBeTranslated($inst)) {
               $tcateg = DropdownTranslation::getTranslatedValue(
                  $data["knowbaseitemcategories_id"],
                  $inst->getType()
               );
               if (!empty($tcateg)) {
                  $categ = $tcateg;
               }
            }

            if ($output_type == Search::HTML_OUTPUT) {
               $cathref = $ki->getSearchURL()."?knowbaseitemcategories_id=".
                           $data["knowbaseitemcategories_id"].'&amp;forcetab=Knowbase$2';
               $categ   = "<a class='kb-category'"
                  . " href='$cathref'"
                  . " data-category-id='" . $data["knowbaseitemcategories_id"] . "'"
                  . ">".$categ.'</a>';
            }
            echo Search::showItem($output_type, $categ, $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'
                  ]
               ]);
               while ($docs = $iterator->next()) {
                  $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']).
                              "&amp;load_kb_sol=".$data['id'].
                              "&amp;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
    *
    * @return void
   **/
   static function showRecentPopular($type) {
      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 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);

      if (count($iterator)) {
         echo "<table class='tab_cadrehov'>";
         echo "<tr class='noHover'><th>".$title."</th></tr>";
         while ($data = $iterator->next()) {
            $name = $data['name'];

            if (isset($data['transname']) && !empty($data['transname'])) {
               $name = $data['transname'];
            }
            echo "<tr class='tab_bg_2'><td class='left'><div class='kb'>";
            if ($data['is_faq']) {
               echo "<i class='fa fa-fw fa-question-circle faq' title='".__("This item is part of the FAQ")."'></i>";
            }
            echo 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") : ''
            ]);
            echo "</div></td></tr>";
         }
         echo "</table>";
      }
   }


   function rawSearchOptions() {
      $tab = [];

      $tab[] = [
         'id'                 => 'common',
         'name'               => __('Characteristics')
      ];

      $tab[] = [
         'id'                 => '2',
         'table'              => $this->getTable(),
         'field'              => 'id',
         'name'               => __('ID'),
         'massiveaction'      => false,
         'datatype'           => 'number'
      ];

      $tab[] = [
         'id'                 => '4',
         'table'              => 'glpi_knowbaseitemcategories',
         'field'              => 'name',
         'name'               => __('Category'),
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '5',
         'table'              => $this->getTable(),
         'field'              => 'date',
         'name'               => _n('Date', 'Dates', 1),
         'datatype'           => 'datetime',
         'massiveaction'      => false
      ];

      $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'                 => '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;
   }

   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;
   }

   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 = html_entity_decode($answer);
      $answer = Toolbox::unclean_html_cross_side_scripting_deep($answer);

      $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');
      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' => 'id',
         'FROM'   => self::getTable(),
         'WHERE'  => ['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;
   }


   static function getIcon() {
      return "fas fa-question";
   }
}