Ganteng Doang Upload Shell Gak Bisa


Linux server.jmdstrack.com 3.10.0-1160.119.1.el7.tuxcare.els10.x86_64 #1 SMP Fri Oct 11 21:40:41 UTC 2024 x86_64
/ home/ jmdstrac/ public_html/ devices/ src/

/home/jmdstrac/public_html/devices/src/KnowbaseItem.php

<?php

/**
 * ---------------------------------------------------------------------
 *
 * GLPI - Gestionnaire Libre de Parc Informatique
 *
 * http://glpi-project.org
 *
 * @copyright 2015-2023 Teclib' and contributors.
 * @copyright 2003-2014 by the INDEPNET Development Team.
 * @licence   https://www.gnu.org/licenses/gpl-3.0.html
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * ---------------------------------------------------------------------
 */

use Glpi\Event;
use Glpi\RichText\RichText;
use Glpi\Toolbox\Sanitizer;

/**
 * KnowbaseItem Class
 **/
class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria
{
   // From CommonDBTM
    public $dohistory    = true;

    protected $items     = [];

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

    public static $rightname   = 'knowbase';


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


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


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

        return parent::getName();
    }


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


    public static function getMenuContent()
    {
        $menu = parent::getMenuContent();
        if (isset($menu['links']['lists'])) {
            unset($menu['links']['lists']);
        }

        return $menu;
    }


    public static function canCreate()
    {

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


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


    public static function canView()
    {
        global $CFG_GLPI;

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


    public function canViewItem()
    {
        if ($this->fields['users_id'] == Session::getLoginUserID()) {
            return true;
        }
        if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
            return true;
        }

        if ($this->fields["is_faq"]) {
            return ((Session::haveRightsOr(self::$rightname, [READ, self::READFAQ])
                  && $this->haveVisibilityAccess())
                 || ((Session::getLoginUserID() === false) && $this->isPubliclyVisible()));
        }
        return (Session::haveRight(self::$rightname, READ) && $this->haveVisibilityAccess());
    }


    public function canUpdateItem()
    {
       // Personal knowbase or visibility and write access
        return (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)
              || (Session::getCurrentInterface() == "central"
                  && $this->fields['users_id'] == Session::getLoginUserID())
              || ((($this->fields["is_faq"] && Session::haveRight(self::$rightname, self::PUBLISHFAQ))
                   || (!$this->fields["is_faq"]
                       && Session::haveRight(self::$rightname, UPDATE)))
                  && $this->haveVisibilityAccess()));
    }

    /**
     * Check if current user can comment on KB entries
     *
     * @return boolean
     */
    public function canComment()
    {
        return $this->canViewItem() && Session::haveRight(self::$rightname, self::COMMENTS);
    }

    /**
     * Get the search page URL for the current classe
     *
     * @since 0.84
     *
     * @param boolean $full  path or relative one
     **/
    public static function getSearchURL($full = true)
    {
        global $CFG_GLPI;

        $dir = ($full ? $CFG_GLPI['root_doc'] : '');

        if (Session::getCurrentInterface() == "central") {
            return "$dir/front/knowbaseitem.php";
        }
        return "$dir/front/helpdesk.faq.php";
    }

    /**
     * Get the form page URL for the current classe
     *
     * @param boolean $full  path or relative one
     **/
    public static function getFormURL($full = true)
    {
        global $CFG_GLPI;

        $dir = ($full ? $CFG_GLPI['root_doc'] : '');

        if (Session::getCurrentInterface() == "central") {
            return "$dir/front/knowbaseitem.form.php";
        }
        return "$dir/front/helpdesk.faq.php";
    }

    /**
     * Get the form page URL for the current classe
     *
     * @param boolean $full  path or relative one
     **/
    public static function getFormURLWithParam($params = [], $full = true)
    {
        $url = self::getFormURL($full) . '?';

        if (isset($params['_sol_to_kb'])) {
            $url .= '&_sol_to_kb=' . $params['_sol_to_kb'];
        }
        if (isset($params['_fup_to_kb'])) {
            $url .= '&_fup_to_kb=' . $params['_fup_to_kb'];
        }
        if (isset($params['_task_to_kb'])) {
            $url .= '&_task_to_kb=' . $params['_task_to_kb'];
        }
        return $url;
    }

    public function defineTabs($options = [])
    {

        $ong = [];
        $this->addStandardTab(__CLASS__, $ong, $options);
        $this->addStandardTab('KnowbaseItem_Item', $ong, $options);
        $this->addStandardTab('Document_Item', $ong, $options);
        $this->addStandardTab('KnowbaseItemTranslation', $ong, $options);
        $this->addStandardTab('Log', $ong, $options);
        $this->addStandardTab('KnowbaseItem_Revision', $ong, $options);
        $this->addStandardTab('KnowbaseItem_Comment', $ong, $options);

        return $ong;
    }


    public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
    {

        if (!$withtemplate) {
            $nb = 0;
            switch ($item->getType()) {
                case __CLASS__:
                    $ong[1] = $this->getTypeName(1);
                    if ($item->canUpdateItem()) {
                        if ($_SESSION['glpishow_count_on_tabs']) {
                            $nb = $item->countVisibilities();
                        }
                        $ong[2] = self::createTabEntry(
                            _n('Target', 'Targets', Session::getPluralNumber()),
                            $nb
                        );
                        $ong[3] = __('Edit');
                    }
                    return $ong;
            }
        }
        return '';
    }


    public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
    {

        if ($item->getType() == __CLASS__) {
            switch ($tabnum) {
                case 1:
                    $item->showFull();
                    break;

                case 2:
                    $item->showVisibility();
                    break;

                case 3:
                    $item->showForm($item->getID());
                    break;
            }
        }
        return true;
    }


    /**
     * Actions done at the end of the getEmpty function
     *
     *@return void
     **/
    public function post_getEmpty()
    {

        if (
            Session::haveRight(self::$rightname, self::PUBLISHFAQ)
            && !Session::haveRight("knowbase", UPDATE)
        ) {
            $this->fields["is_faq"] = 1;
        }
    }


    /**
     * @since 0.85
     * @see CommonDBTM::post_addItem()
     **/
    public function post_addItem()
    {
        // Handle rich-text images and uploaded documents
        $this->input = $this->addFiles(
            $this->input,
            [
                'force_update'  => true,
                'content_field' => 'answer',
            ]
        );

        if (
            isset($this->input["_visibility"])
            && isset($this->input["_visibility"]['_type'])
            && !empty($this->input["_visibility"]["_type"])
        ) {
            $this->input["_visibility"]['knowbaseitems_id'] = $this->getID();
            $item                                           = null;

            switch ($this->input["_visibility"]['_type']) {
                case 'User':
                    if (
                        isset($this->input["_visibility"]['users_id'])
                        && $this->input["_visibility"]['users_id']
                    ) {
                        $item = new KnowbaseItem_User();
                    }
                    break;

                case 'Group':
                    if (
                        isset($this->input["_visibility"]['groups_id'])
                        && $this->input["_visibility"]['groups_id']
                    ) {
                        $item = new Group_KnowbaseItem();
                    }
                    break;

                case 'Profile':
                    if (
                        isset($this->input["_visibility"]['profiles_id'])
                        && $this->input["_visibility"]['profiles_id']
                    ) {
                        $item = new KnowbaseItem_Profile();
                    }
                    break;

                case 'Entity':
                    $item = new Entity_KnowbaseItem();
                    break;
            }
            if (!is_null($item)) {
                $item->add($this->input["_visibility"]);
                Event::log(
                    $this->getID(),
                    "knowbaseitem",
                    4,
                    "tools",
                    //TRANS: %s is the user login
                    sprintf(__('%s adds a target'), $_SESSION["glpiname"])
                );
            }
        }

        if (isset($this->input['_do_item_link']) && $this->input['_do_item_link'] == 1) {
            $params = [
                'knowbaseitems_id' => $this->getID(),
                'itemtype'         => $this->input['_itemtype'],
                'items_id'         => $this->input['_items_id']
            ];
            $kb_item_item = new KnowbaseItem_Item();
            $kb_item_item->add($params);
        }

        // Support old "knowbaseitemcategories_id" input
        // FIXME Deprecate it in GLPI 10.1
        if (isset($this->input['knowbaseitemcategories_id'])) {
            $categories = $this->input['knowbaseitemcategories_id'];
            $this->input['_categories'] = is_array($categories) ? $categories : [$categories];
            unset($this->input['knowbaseitemcategories_id']);
        }

        // Handle categories
        $this->update1NTableData(KnowbaseItem_KnowbaseItemCategory::class, "_categories");
    }


    /**
     * @since 0.83
     **/
    public function post_getFromDB()
    {

       // Users
        $this->users    = KnowbaseItem_User::getUsers($this->fields['id']);

       // Entities
        $this->entities = Entity_KnowbaseItem::getEntities($this->fields['id']);

       // Group / entities
        $this->groups   = Group_KnowbaseItem::getGroups($this->fields['id']);

       // Profile / entities
        $this->profiles = KnowbaseItem_Profile::getProfiles($this->fields['id']);

        // Load categories
        $this->load1NTableData(KnowbaseItem_KnowbaseItemCategory::class, '_categories');
    }


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

        $this->deleteChildrenAndRelationsFromDb(
            [
                Entity_KnowbaseItem::class,
                Group_KnowbaseItem::class,
                KnowbaseItem_KnowbaseItemCategory::class,
                KnowbaseItem_Item::class,
                KnowbaseItem_Profile::class,
                KnowbaseItem_User::class,
                KnowbaseItemTranslation::class,
            ]
        );

       /// KnowbaseItem_Comment does not extends CommonDBConnexity
        $kbic = new KnowbaseItem_Comment();
        $kbic->deleteByCriteria(['knowbaseitems_id' => $this->fields['id']]);

       /// KnowbaseItem_Revision does not extends CommonDBConnexity
        $kbir = new KnowbaseItem_Revision();
        $kbir->deleteByCriteria(['knowbaseitems_id' => $this->fields['id']]);
    }

    /**
     * Check is this item if visible to everybody (anonymous users)
     *
     * @since 0.83
     *
     * @return Boolean
     **/
    public function isPubliclyVisible()
    {
        global $CFG_GLPI;

        if (!$CFG_GLPI['use_public_faq']) {
            return false;
        }

        if (isset($this->entities[0])) { // Browse root entity rights
            foreach ($this->entities[0] as $entity) {
                if ($entity['is_recursive']) {
                    return true;
                }
            }
        }
        return false;
    }

    public function haveVisibilityAccess()
    {
       // No public knowbaseitem right : no visibility check
        if (!Session::haveRightsOr(self::$rightname, [self::READFAQ, READ])) {
            return false;
        }

       // KB Admin
        if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
            return true;
        }

        return parent::haveVisibilityAccess();
    }

    /**
     * Return visibility joins to add to SQL
     *
     * @since 0.83
     *
     * @param boolean $forceall  force all joins
     *
     * @return string joins to add
     **/
    public static function addVisibilityJoins($forceall = false)
    {
       //not deprecated because used in self::getListRequest and self::showRecentPopular

        global $DB;

       //get and clean criteria
        $criteria = self::getVisibilityCriteria($forceall);
        unset($criteria['WHERE']);
        $criteria['FROM'] = self::getTable();

        $it = new \DBmysqlIterator(null);
        $it->buildQuery($criteria);
        $sql = $it->getSql();
        $sql = str_replace(
            'SELECT * FROM ' . $DB->quoteName(self::getTable()) . '',
            '',
            $sql
        );
        return $sql;
    }

    /**
     * Return visibility SQL restriction to add
     *
     * @since 0.83
     *
     * @return string restrict to add
     **/
    public static function addVisibilityRestrict()
    {
       //not deprecated because used in self::getListRequest and self::showRecentPopular

        global $DB;

       //get and clean criteria
        $criteria = self::getVisibilityCriteria();
        unset($criteria['LEFT JOIN']);
        $criteria['FROM'] = self::getTable();

        $it = new \DBmysqlIterator(null);
        $it->buildQuery($criteria);
        $sql = $it->getSql();
        $sql = str_replace(
            'SELECT * FROM ' . $DB->quoteName(self::getTable()) . '',
            '',
            $sql
        );
        $sql = preg_replace('/.*WHERE /', '', $sql);

       //No where restrictions. Add a placeholder for compatibility with later restrictions
        if (strlen(trim($sql)) == 0) {
            $sql = "1";
        }
        return $sql;
    }

    /**
     * Return visibility joins to add to DBIterator parameters
     *
     * @since 9.2
     *
     * @param boolean $forceall force all joins (false by default)
     *
     * @return array
     */
    public static function getVisibilityCriteria(bool $forceall = false): array
    {
        global $CFG_GLPI;

        // Build common JOIN clause
        $criteria = [
            'LEFT JOIN' => self::getVisibilityCriteriaCommonJoin($forceall),
        ];

        // Handle anonymous users
        if (!Session::getLoginUserID()) {
            if ($CFG_GLPI["use_public_faq"]) {
                // Public FAQ is enabled; show FAQ
                $criteria['WHERE'] = self::getVisibilityCriteriaFAQ();
                return $criteria;
            } else {
                // Public FAQ is disabled; show nothing
                $criteria['WHERE'] = [0];
                return $criteria;
            }
        }

        // Handle logged in users
        if (Session::getCurrentInterface() == "helpdesk") {
            // Show FAQ for helpdesk user
            $criteria['WHERE'] = self::getVisibilityCriteriaFAQ();
            return $criteria;
        } else {
            // Show knowledge base for central users
            $criteria['WHERE'] = self::getVisibilityCriteriaKB();
            return $criteria;
        }
    }

    /**
     * Common JOIN clause used by getVisibilityCriteria* methods
     *
     * @param bool $forceall Force all join ?
     *
     * @return array LEFT JOIN clause
     */
    private static function getVisibilityCriteriaCommonJoin(bool $forceall = false)
    {
        global $CFG_GLPI;

        $join = [];

        // Context checks - avoid doing unnecessary join if possible
        $is_public_faq_context = !Session::getLoginUserID() && $CFG_GLPI["use_public_faq"];
        $has_session_groups = count(($_SESSION["glpigroups"] ?? []));
        $has_active_profile = isset($_SESSION["glpiactiveprofile"]['id']);
        $has_active_entity = count(($_SESSION["glpiactiveentities"] ?? []));

        // Add user restriction data
        if ($forceall || Session::getLoginUserID()) {
            $join['glpi_knowbaseitems_users'] = [
                'ON' => [
                    'glpi_knowbaseitems_users' => 'knowbaseitems_id',
                    'glpi_knowbaseitems'       => 'id'
                ]
            ];
        }

        // Add group restriction data
        if ($forceall || $has_session_groups) {
            $join['glpi_groups_knowbaseitems'] = [
                'ON' => [
                    'glpi_groups_knowbaseitems' => 'knowbaseitems_id',
                    'glpi_knowbaseitems'       => 'id'
                ]
            ];
        }

        // Add profile restriction data
        if ($forceall || $has_active_profile) {
            $join['glpi_knowbaseitems_profiles'] = [
                'ON' => [
                    'glpi_knowbaseitems_profiles' => 'knowbaseitems_id',
                    'glpi_knowbaseitems'       => 'id'
                ]
            ];
        }

        // Add entity restriction data
        if ($forceall || $has_active_entity || $is_public_faq_context) {
            $join['glpi_entities_knowbaseitems'] = [
                'ON' => [
                    'glpi_entities_knowbaseitems' => 'knowbaseitems_id',
                    'glpi_knowbaseitems'       => 'id'
                ]
            ];
        }

        return $join;
    }

    /**
     * Get visibility criteria for articles displayed in the FAQ (seen by
     * helpdesk and anonymous users)
     * This mean any KB article tagged as 'is_faq' should be displayed
     *
     * @return array WHERE clause
     */
    private static function getVisibilityCriteriaFAQ(): array
    {
        // Specific case for anonymous users + multi entities
        if (!Session::getLoginUserID()) {
            $where = ['is_faq' => 1];
            if (Session::isMultiEntitiesMode()) {
                $where[Entity_KnowbaseItem::getTableField('entities_id')] = 0;
                $where[Entity_KnowbaseItem::getTableField('is_recursive')] = 1;
            }
        } else {
            $where = self::getVisibilityCriteriaKB_Entity();
            $where['is_faq'] = 1;
        }

        return $where;
    }

    /**
     * Get visibility criteria for articles displayed in the knowledge base
     * (seen by central users)
     * This mean any KB article with valid visibility criteria for the current
     * user should be displayed
     *
     * @return array WHERE clause
     */
    private static function getVisibilityCriteriaKB(): array
    {
        // Special case for KB Admins
        if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
            // See all articles
            return [1];
        }

        // Prepare criteria, which will use an OR statement (the user can read
        // the article if any of the user/group/profile/entity criteria are
        // validated)
        $where = ['OR' => []];

        // Special case: the user may be the article's author
        $user = Session::getLoginUserID();
        $author_check = [self::getTableField('users_id') => $user];
        $where['OR'][] = $author_check;

        // Filter on users
        $where['OR'][] = self::getVisibilityCriteriaKB_User();

        // Filter on groups (if the current user have any)
        $groups = $_SESSION["glpigroups"] ?? [];
        if (count($groups)) {
            $where['OR'][] = self::getVisibilityCriteriaKB_Group();
        }

        // Filter on profiles
        $where['OR'][] = self::getVisibilityCriteriaKB_Profile();

        // Filter on entities
        $where['OR'][] = self::getVisibilityCriteriaKB_Entity();

        return $where;
    }

    /**
     * Get criteria used to filter knowledge base articles on users
     *
     * @return array
     */
    private static function getVisibilityCriteriaKB_User(): array
    {
        $user = Session::getLoginUserID();
        return [
            KnowbaseItem_User::getTableField('users_id') => $user,
        ];
    }

    /**
     * Get criteria used to filter knowledge base articles on groups
     *
     * @return array
     */
    private static function getVisibilityCriteriaKB_Group(): array
    {
        $groups = $_SESSION["glpigroups"] ?? [-1];
        $entity_restriction = getEntitiesRestrictCriteria(
            Group_KnowbaseItem::getTable(),
            '',
            '',
            true,
            true
        );

        return [
            Group_KnowbaseItem::getTableField('groups_id') => $groups,
            'OR' => [
                Group_KnowbaseItem::getTableField('no_entity_restriction') => 1,
            ] + $entity_restriction,
        ];
    }

    /**
     * Get criteria used to filter knowledge base articles on profiles
     *
     * @return array
     */
    private static function getVisibilityCriteriaKB_Profile(): array
    {
        $profile = $_SESSION["glpiactiveprofile"]['id'] ?? -1;
        $entity_restriction = getEntitiesRestrictCriteria(
            KnowbaseItem_Profile::getTable(),
            '',
            '',
            true,
            true
        );

        return [
            KnowbaseItem_Profile::getTableField('profiles_id') => $profile,
            'OR' => [
                KnowbaseItem_Profile::getTableField('no_entity_restriction') => 1,
            ] + $entity_restriction,
        ];
    }

    /**
     * Get criteria used to filter knowledge base articles on entity
     *
     * @return array
     */
    private static function getVisibilityCriteriaKB_Entity(): array
    {
        $entity_restriction = getEntitiesRestrictCriteria(
            Entity_KnowbaseItem::getTable(),
            '',
            '',
            true,
            true
        );

        // All entities
        if (!count($entity_restriction)) {
            $entity_restriction = [
                Entity_KnowbaseItem::getTableField('entities_id') => null
            ];
        }

        return $entity_restriction;
    }

    public function prepareInputForAdd($input)
    {

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

        if (
            Session::haveRight(self::$rightname, self::PUBLISHFAQ)
            && !Session::haveRight(self::$rightname, UPDATE)
        ) {
            $input["is_faq"] = 1;
        }
        if (
            !Session::haveRight(self::$rightname, self::PUBLISHFAQ)
            && Session::haveRight(self::$rightname, UPDATE)
        ) {
            $input["is_faq"] = 0;
        }
        return $input;
    }


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

    public function post_updateItem($history = 1)
    {
        // Handle rich-text images and uploaded documents
        $this->input = $this->addFiles(
            $this->input,
            [
                'force_update'  => true,
                'content_field' => 'answer',
            ]
        );

        // Support old "knowbaseitemcategories_id" input
        // FIXME Deprecate it in GLPI 10.1
        if (isset($this->input['knowbaseitemcategories_id'])) {
            $categories = $this->input['knowbaseitemcategories_id'];
            $this->input['_categories'] = is_array($categories) ? $categories : [$categories];
            unset($this->input['knowbaseitemcategories_id']);
        }

        // Update categories
        $this->update1NTableData(KnowbaseItem_KnowbaseItemCategory::class, '_categories');
    }


    /**
     * Print out an HTML "<form>" for knowbase item
     *
     * @param $ID
     * @param $options array
     *     - target for the Form
     *
     * @return void
     **/
    public function showForm($ID, array $options = [])
    {
        global $CFG_GLPI;

       // show kb item form
        if (
            !Session::haveRightsOr(
                self::$rightname,
                [UPDATE, self::PUBLISHFAQ, self::KNOWBASEADMIN]
            )
        ) {
            return false;
        }

        $canedit = $this->can($ID, UPDATE);

        $item = null;
       // Load ticket solution
        if (
            empty($ID)
            && isset($options['item_itemtype']) && !empty($options['item_itemtype'])
            && isset($options['item_items_id']) && !empty($options['item_items_id'])
        ) {
            if ($item = getItemForItemtype($options['item_itemtype'])) {
                if ($item->getFromDB($options['item_items_id'])) {
                    $this->fields['name']   = $item->getField('name');
                    if (isset($options['_fup_to_kb'])) {
                        $fup = new ITILFollowup();
                        $fup->getFromDBByCrit([
                            'id'           => $options['_fup_to_kb'],
                            'itemtype'     => $item->getType(),
                            'items_id'     => $item->getID()
                        ]);
                        $this->fields['answer'] = $fup->getField('content');
                    } else if (isset($options['_task_to_kb'])) {
                        $tasktype = $item->getType() . 'Task';
                        $task = new $tasktype();
                        $task->getFromDB($options['_task_to_kb']);
                        $this->fields['answer'] = $task->getField('content');
                    } else if (isset($options['_sol_to_kb'])) {
                        $solution = new ITILSolution();
                        $solution->getFromDBByCrit([
                            'itemtype'     => $item->getType(),
                            'items_id'     => $item->getID(),
                            [
                                'NOT' => ['status'       => CommonITILValidation::REFUSED]
                            ]
                        ]);
                        $this->fields['answer'] = $solution->getField('content');
                    }
                    if ($item->isField('itilcategories_id')) {
                          $ic = new ITILCategory();
                        if ($ic->getFromDB($item->getField('itilcategories_id'))) {
                            $this->fields['knowbaseitemcategories_id']
                            = $ic->getField('knowbaseitemcategories_id');
                        }
                    }
                }
            }
        }
        $rand = mt_rand();

        $this->initForm($ID, $options);
        $options['formoptions'] = "data-track-changes=true";
        $this->showFormHeader($options);
        echo "<tr class='tab_bg_1'>";
        echo "<td>" . KnowbaseItemCategory::getTypeName(Session::getPluralNumber()) . "</td>";
        echo "<td>";
        // See CommonDBTM::update1NTableData, hidden input needed to handle empty values
        echo  Html::input("__categories_defined", ["type" => "hidden", "value" => 1]);
        KnowbaseItemCategory::dropdown([
            'name'     => "_categories[]",
            'value'    => $this->fields['_categories'] ?? [],
            'multiple' => true,
            'width'    => "100%",
        ]);
        echo "</td>";

        echo "<td>";
        echo "<input type='hidden' name='users_id' value=\"" . Session::getLoginUserID() . "\">";
        if ($this->fields["date_creation"]) {
           //TRANS: %s is the datetime of insertion
            printf(__('Created on %s'), Html::convDateTime($this->fields["date_creation"]));
        }
        echo "</td><td>";
        if ($this->fields["date_mod"]) {
           //TRANS: %s is the datetime of update
            printf(__('Last update on %s'), Html::convDateTime($this->fields["date_mod"]));
        }
        echo "</td>";
        echo "</tr>\n";

        echo "<tr class='tab_bg_1'>";
        if (Session::haveRight(self::$rightname, self::PUBLISHFAQ)) {
            echo "<td>" . __('Put this item in the FAQ') . "</td>";
            echo "<td>";
            Dropdown::showYesNo('is_faq', $this->fields["is_faq"]);
            echo "</td>";
        } else {
            echo "<td colspan='2'>";
            if ($this->fields["is_faq"]) {
                echo __('This item is part of the FAQ');
            } else {
                echo __('This item is not part of the FAQ');
            }
            echo "</td>";
        }
        echo "<td>";
        $showuserlink = 0;
        if (Session::haveRight('user', READ)) {
            $showuserlink = 1;
        }
        if ($this->fields["users_id"]) {
           //TRANS: %s is the writer name
            printf(__('%1$s: %2$s'), __('Writer'), getUserName(
                $this->fields["users_id"],
                $showuserlink
            ));
        }
        echo "</td><td>";
       //TRANS: %d is the number of view
        if ($ID) {
            printf(_n('%d view', '%d views', $this->fields["view"]), $this->fields["view"]);
        }
        echo "</td>";
        echo "</tr>\n";

       //Link with solution
        if ($item != null) {
            if ($item = getItemForItemtype($options['item_itemtype'])) {
                if ($item->getFromDB($options['item_items_id'])) {
                    echo "<tr>";
                    echo "<td>" . __('Add link') . "</td>";
                    echo "<td colspan='3'>";
                    echo "<input type='checkbox' name='_do_item_link' value='1' checked='checked'/> ";
                    echo Html::hidden('_itemtype', ['value' => $item->getType()]);
                    echo Html::hidden('_items_id', ['value' => $item->getID()]);
                    echo sprintf(
                        __('link with %1$s'),
                        $item->getLink()
                    );
                     echo "</td>";
                     echo "</tr>\n";
                }
            }
        }

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Visible since') . "</td><td>";
        Html::showDateTimeField("begin_date", ['value'       => $this->fields["begin_date"],
            'maybeempty' => true,
            'canedit'    => $canedit
        ]);
        echo "</td>";
        echo "<td>" . __('Visible until') . "</td><td>";
        Html::showDateTimeField("end_date", ['value'       => $this->fields["end_date"],
            'maybeempty' => true,
            'canedit'    => $canedit
        ]);
        echo "</td></tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Subject') . "</td>";
        echo "<td colspan='3'>";
        echo "<textarea class='form-control' name='name'>" . $this->fields["name"] . "</textarea>";
        echo "</td>";
        echo "</tr>\n";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Content') . "</td>";
        echo "<td colspan='3'>";

        $cols = 100;
        $rows = 30;
        if (isset($options['_in_modal']) && $options['_in_modal']) {
            $rows = 15;
            echo Html::hidden('_in_modal', ['value' => 1]);
        }
        Html::textarea(['name'              => 'answer',
            'value'             => RichText::getSafeHtml($this->fields['answer'], true),
            'enable_fileupload' => true,
            'enable_richtext'   => true,
            'cols'              => $cols,
            'rows'              => $rows
        ]);
        echo "</td>";
        echo "</tr>";

        if ($this->isNewID($ID)) {
            echo "<tr class='tab_bg_1'>";
            echo "<td>" . _n('Target', 'Targets', 1) . "</td>";
            echo "<td>";
            $types   = ['Entity', 'Group', 'Profile', 'User'];
            $addrand = Dropdown::showItemTypes('_visibility[_type]', $types);
            echo "</td><td colspan='2'>";
            $params  = ['type'     => '__VALUE__',
                'right'    => 'knowbase',
                'prefix'   => '_visibility',
                'nobutton' => 1
            ];

            Ajax::updateItemOnSelectEvent(
                "dropdown__visibility__type_" . $addrand,
                "visibility$rand",
                $CFG_GLPI["root_doc"] . "/ajax/visibility.php",
                $params
            );
            echo "<span id='visibility$rand'></span>";
            echo "</td></tr>\n";
        }

        $this->showFormButtons($options);
        return true;
    }


    /**
     * Add kb item to the public FAQ
     *
     * @return void
     **/
    public function addToFaq()
    {
        global $DB;

        $DB->update(
            $this->getTable(),
            [
                'is_faq' => 1
            ],
            [
                'id' => $this->fields['id']
            ]
        );
    }

    /**
     * Increase the view counter of the current knowbaseitem
     *
     * @since 0.83
     */
    public function updateCounter()
    {
        global $DB;

       //update counter view
        $DB->update(
            'glpi_knowbaseitems',
            [
                'view'   => new \QueryExpression($DB->quoteName('view') . ' + 1')
            ],
            [
                'id' => $this->getID()
            ]
        );
    }


    /**
     * Print out (html) show item : question and answer
     *
     * @param $options      array of options
     *
     * @return void|string
     *    void if option display=true
     *    string if option display=false (HTML code)
     **/
    public function showFull($options = [])
    {
        global $CFG_GLPI, $DB;

        if (!$this->can($this->fields['id'], READ)) {
            return false;
        }

        $default_options = [
            'display' => true,
        ];
        $options = array_merge($default_options, $options);

        $out = "";

        $linkusers_id = true;
       // show item : question and answer
        if (
            ((Session::getLoginUserID() === false) && $CFG_GLPI["use_public_faq"])
            || (Session::getCurrentInterface() == "helpdesk")
            || !User::canView()
        ) {
            $linkusers_id = false;
        }

        $this->updateCounter();

        $tmp = [];
        $categories = KnowbaseItem_KnowbaseItemCategory::getItems($this);
        foreach ($categories as $category) {
            $knowbaseitemcategories_id = $category['knowbaseitemcategories_id'];
            $fullcategoryname          = getTreeValueCompleteName(
                "glpi_knowbaseitemcategories",
                $knowbaseitemcategories_id
            );

            $tmp[] = "<a href='" . $this->getSearchURL() .
             "?knowbaseitemcategories_id=$knowbaseitemcategories_id&forcetab=Knowbase$2'>" .
             $fullcategoryname . "</a>";
        }
        $tmp = implode(', ', $tmp);
        $out .= "<table class='tab_cadre_fixe'>";
        $out .= "<tr><th colspan='4'>" . sprintf(__('%1$s: %2$s'), _n('Category', 'Categories', 1), $tmp);
        $out .= "</th></tr>";

        $out .= "<tr><td class='left' colspan='4'><h2>" . __('Subject') . "</h2>";
        if (KnowbaseItemTranslation::canBeTranslated($this)) {
            $out .= KnowbaseItemTranslation::getTranslatedValue($this, 'name');
        } else {
            $out .= $this->fields["name"];
        }

        $out .= "</td></tr>";
        $out .= "<tr><td class='left' colspan='4'><h2>" . __('Content') . "</h2>\n";

        $out .= "<div class='rich_text_container' id='kbanswer'>";
        $out .= $this->getAnswer();
        $out .= "</div>";
        $out .= "</td></tr>";

       // Show documents attached to the FAQ Item
        $sort = 'filename';
        $order = 'ASC';
        $criteria = Document_Item::getDocumentForItemRequest($this, ["$sort $order"]);
        $criteria['WHERE'][] = ['is_deleted' => '0'];
        $iterator = $DB->request($criteria);
        if (count($iterator) > 0) {
            $out .= "<tr><td class='left' colspan='4'><h2>" . Document::getTypeName(Session::getPluralNumber()) . "</h2></td></tr>\n";

            $columns = [
                'filename'  => __('File'),
                'headings'  => __('Heading'),
                'assocdate' => _n('Date', 'Dates', 1),
            ];

            $header_begin  = "<tr>";
            $header_top    = '';
            $header_end    = '';

            foreach ($columns as $key => $val) {
                $colspan = $key == 'filename' ? 'colspan="2"' : '';
                $header_end .= "<th $colspan" . ($sort == "$key" ? " class='order_$order'" : '') . ">" .
                           "<a href='javascript:reloadTab(\"sort=$key&amp;order=" .
                              (($order == "ASC") ? "DESC" : "ASC") . "&amp;start=0\");'>$val</a></th>";
            }
            $header_end .= "</tr>";
            $out .= $header_begin . $header_top . $header_end;

            $document = new Document();
            foreach ($iterator as $data) {
                $docID        = $data["id"];
                $downloadlink = NOT_AVAILABLE;

                if ($document->getFromDB($docID)) {
                    $downloadlink = $document->getDownloadLink();
                }

                $used[$docID] = $docID;

                $out .= "<tr class='tab_bg_1" . ($data["is_deleted"] ? "_2" : "") . "'>";
                $out .= "<td colspan='2'>$downloadlink</td>";
                $out .= "<td>" . Dropdown::getDropdownName(
                    "glpi_documentcategories",
                    $data["documentcategories_id"]
                );
                $out .= "</td>";
                $out .= "<td>" . Html::convDateTime($data["assocdate"]) . "</td>";
                $out .= "</tr>";
            }
        }

        $out .= "<tr><th class='tdkb'  colspan='2'>";
        if ($this->fields["users_id"]) {
           // Integer because true may be 2 and getUserName return array
            if ($linkusers_id) {
                $linkusers_id = 1;
            } else {
                $linkusers_id = 0;
            }

            $out .= sprintf(__('%1$s: %2$s'), __('Writer'), getUserName(
                $this->fields["users_id"],
                $linkusers_id
            ));
            $out .= "<br>";
        }

        if ($this->fields["date_creation"]) {
           //TRANS: %s is the datetime of update
            $out .= sprintf(__('Created on %s'), Html::convDateTime($this->fields["date_creation"]));
            $out .= "<br>";
        }
        if ($this->fields["date_mod"]) {
           //TRANS: %s is the datetime of update
            $out .= sprintf(__('Last update on %s'), Html::convDateTime($this->fields["date_mod"]));
        }

        $out .= "</th>";
        $out .= "<th class='tdkb' colspan='2'>";
        if ($this->countVisibilities() == 0) {
            $out .= "<span class='red'>" . __('Unpublished') . "</span><br>";
        }

        $out .= sprintf(_n('%d view', '%d views', $this->fields["view"]), $this->fields["view"]);
        $out .= "<br>";
        if ($this->fields["is_faq"]) {
            $out .= __('This item is part of the FAQ');
        } else {
            $out .= __('This item is not part of the FAQ');
        }
        $out .= "</th></tr>";
        $out .= "</table>";

        if ($options['display']) {
            echo $out;
        } else {
            return $out;
        }

        return true;
    }


    /**
     * Print out an HTML form for Search knowbase item
     *
     * @param $options   $_GET
     *
     * @return void
     **/
    public function searchForm($options)
    {
        global $CFG_GLPI;

        if (
            !$CFG_GLPI["use_public_faq"]
            && !Session::haveRightsOr(self::$rightname, [READ, self::READFAQ])
        ) {
            return false;
        }

       // Default values of parameters
        $params["contains"]                  = "";
        $params["target"]                    = $_SERVER['PHP_SELF'];

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $params[$key] = $val;
            }
        }

        echo "<form method='get' action='" . $this->getSearchURL() . "' class='d-flex justify-content-center'>";
        echo "<input class='form-control me-1' type='text' size='50' name='contains' value=\"" .
             Html::cleanInputText(stripslashes($params["contains"])) . "\">";
        echo "<input type='submit' value=\"" . _sx('button', 'Search') . "\" class='btn btn-primary'>";
        echo "</table>";
        if (
            isset($options['item_itemtype'])
            && isset($options['item_items_id'])
        ) {
            echo "<input type='hidden' name='item_itemtype' value='" . $options['item_itemtype'] . "'>";
            echo "<input type='hidden' name='item_items_id' value='" . $options['item_items_id'] . "'>";
        }
        Html::closeForm();
    }


    /**
     * Print out an HTML "<form>" for Search knowbase item
     *
     * @since 0.84
     *
     * @param $options   $_GET
     *
     * @return void
     **/
    public function showBrowseForm($options)
    {
        global $CFG_GLPI;

        if (
            !$CFG_GLPI["use_public_faq"]
            && !Session::haveRightsOr(self::$rightname, [READ, self::READFAQ])
        ) {
            return false;
        }

       // Default values of parameters
        $params["knowbaseitemcategories_id"] = "";

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $params[$key] = $val;
            }
        }
        $faq = !Session::haveRight(self::$rightname, READ);

       // Category select not for anonymous FAQ
        if (
            Session::getLoginUserID()
            && !$faq
        ) {
            echo "<div>";
            echo "<form method='get' action='" . $this->getSearchURL() . "'>";
            echo "<table class='tab_cadre_fixe'>";
            echo "<tr class='tab_bg_2'><td class='right' width='50%'>" . _n('Category', 'Categories', 1) . "&nbsp;";
            KnowbaseItemCategory::dropdown(['value' => $params["knowbaseitemcategories_id"]]);
            echo "</td><td class='left'>";
            echo "<input type='submit' value=\"" . _sx('button', 'Post') . "\" class='btn btn-primary'></td>";
            echo "</tr></table>";
            if (
                isset($options['item_itemtype'])
                && isset($options['item_items_id'])
            ) {
                echo "<input type='hidden' name='item_itemtype' value='" . $options['item_itemtype'] . "'>";
                echo "<input type='hidden' name='item_items_id' value='" . $options['item_items_id'] . "'>";
            }
            Html::closeForm();
            echo "</div>";
        }
    }


    /**
     * Print out an HTML form for Search knowbase item
     *
     * @since 0.84
     *
     * @param $options   $_GET
     *
     * @return void
     **/
    public function showManageForm($options)
    {
        if (
            !Session::haveRightsOr(
                self::$rightname,
                [UPDATE, self::PUBLISHFAQ, self::KNOWBASEADMIN]
            )
        ) {
            return false;
        }
        $params['unpublished'] = 'my';
        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $params[$key] = $val;
            }
        }

        echo "<div>";
        echo "<form method='get' action='" . $this->getSearchURL() . "'>";
        echo "<table class='tab_cadre_fixe'>";
        echo "<tr class='tab_bg_2'><td class='right' width='50%'>";
        $values = ['myunpublished' => __('My unpublished articles'),
            'allmy'         => __('All my articles')
        ];
        if (Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
            $values['allunpublished'] = __('All unpublished articles');
            $values['allpublished'] = __('All published articles');
        }
        Dropdown::showFromArray('unpublished', $values, ['value' => $params['unpublished']]);
        echo "</td><td class='left'>";
        echo "<input type='submit' value=\"" . _sx('button', 'Post') . "\" class='btn btn-primary'></td>";
        echo "</tr></table>";
        Html::closeForm();
        echo "</div>";
    }


    /**
     * Build request for showList
     *
     * @since 0.83
     *
     * @param $params array  (contains, knowbaseitemcategories_id, faq)
     * @param $type   string search type : browse / search (default search)
     *
     * @return array : SQL request
     **/
    public static function getListRequest(array $params, $type = 'search')
    {
        global $DB;

        $criteria = [
            'SELECT' => [
                'glpi_knowbaseitems.*',
                new QueryExpression(
                    'COUNT(' . $DB->quoteName('glpi_knowbaseitems_users.id') . ')' .
                    ' + COUNT(' . $DB->quoteName('glpi_groups_knowbaseitems.id') . ')' .
                    ' + COUNT(' . $DB->quoteName('glpi_knowbaseitems_profiles.id') . ')' .
                    ' + COUNT(' . $DB->quoteName('glpi_entities_knowbaseitems.id') . ') AS ' .
                    $DB->quoteName('visibility_count')
                )
            ],
            'FROM'   => 'glpi_knowbaseitems',
            'WHERE'     => [], //to be filled
            'LEFT JOIN' => [], //to be filled
            'GROUPBY'   => ['glpi_knowbaseitems.id']
        ];

       // Lists kb Items
        $restrict = self::getVisibilityCriteria(true);
        $restrict_where = $restrict['WHERE'];
        unset($restrict['WHERE']);
        unset($restrict['SELECT']);
        $criteria = array_merge_recursive($criteria, $restrict);

        switch ($type) {
            case 'myunpublished':
            case 'allmy':
            case 'allunpublished':
                break;

            default:
               // Build query
                if (Session::getLoginUserID()) {
                    $criteria['WHERE'] = array_merge(
                        $criteria['WHERE'],
                        $restrict_where
                    );
                } else {
                   // Anonymous access
                    if (Session::isMultiEntitiesMode()) {
                        $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = 0;
                        $criteria['WHERE']['glpi_entities_knowbaseitems.is_recursive'] = 1;
                    }
                }
                break;
        }

        if ($params['faq']) { // helpdesk
            $criteria['WHERE'][] = [
                'OR' => [
                    'glpi_knowbaseitems.is_faq' => 1,
                    'glpi_knowbaseitems_users.users_id' => Session::getLoginUserID(),
                ]
            ];
        }

        if ($params['knowbaseitemcategories_id'] !== KnowbaseItemCategory::SEEALL) {
            $criteria['LEFT JOIN'][KnowbaseItem_KnowbaseItemCategory::getTable()] = [
                'FKEY' => [
                    KnowbaseItem_KnowbaseItemCategory::getTable() => KnowbaseItem::getForeignKeyField(),
                    KnowbaseItem::getTable() => 'id',
                ],
            ];
            if ($params['knowbaseitemcategories_id'] > 0) {
                $criteria['WHERE'][KnowbaseItem_KnowbaseItemCategory::getTableField('knowbaseitemcategories_id')] = $params['knowbaseitemcategories_id'];
            } elseif ($params['knowbaseitemcategories_id'] === 0) {
                $criteria['WHERE'][KnowbaseItem_KnowbaseItemCategory::getTableField('knowbaseitemcategories_id')] = null;
            }
        }

        if (
            KnowbaseItemTranslation::isKbTranslationActive()
            && (countElementsInTable('glpi_knowbaseitemtranslations') > 0)
        ) {
            $criteria['LEFT JOIN']['glpi_knowbaseitemtranslations'] = [
                'ON'  => [
                    'glpi_knowbaseitems'             => 'id',
                    'glpi_knowbaseitemtranslations'  => 'knowbaseitems_id', [
                        'AND'                            => [
                            'glpi_knowbaseitemtranslations.language' => $_SESSION['glpilanguage']
                        ]
                    ]
                ]
            ];
            $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.name AS transname';
            $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.answer AS transanswer';
        }

       // a search with $contains
        switch ($type) {
            case 'allmy':
                $criteria['WHERE']['glpi_knowbaseitems.users_id'] = Session::getLoginUserID();
                break;

            case 'myunpublished':
                $criteria['WHERE']['glpi_knowbaseitems.users_id'] = Session::getLoginUserID();
                $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = null;
                $criteria['WHERE']['glpi_knowbaseitems_profiles.profiles_id'] = null;
                $criteria['WHERE']['glpi_groups_knowbaseitems.groups_id'] = null;
                $criteria['WHERE']['glpi_knowbaseitems_users.users_id'] = null;
                break;

            case 'allunpublished':
               // Only published
                $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = null;
                $criteria['WHERE']['glpi_knowbaseitems_profiles.profiles_id'] = null;
                $criteria['WHERE']['glpi_groups_knowbaseitems.groups_id'] = null;
                $criteria['WHERE']['glpi_knowbaseitems_users.users_id'] = null;
                break;

            case 'allpublished':
                $criteria['HAVING']['visibility_count'] = ['>', 0];
                break;

            case 'search':
                if (strlen($params["contains"]) > 0) {
                    $search  = Sanitizer::unsanitize($params["contains"]);
                    $search_wilcard = self::computeBooleanFullTextSearch($search);

                    $addscore = [];
                    if (
                        KnowbaseItemTranslation::isKbTranslationActive()
                        && (countElementsInTable('glpi_knowbaseitemtranslations') > 0)
                    ) {
                        $addscore = [
                            'glpi_knowbaseitemtranslations.name',
                            'glpi_knowbaseitemtranslations.answer'
                        ];
                    }

                    $expr = "(MATCH(" . $DB->quoteName('glpi_knowbaseitems.name') . ", " . $DB->quoteName('glpi_knowbaseitems.answer') . ")
                           AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)";

                    if (!empty($addscore)) {
                        foreach ($addscore as $addscore_field) {
                            $expr .= " + MATCH(" . $DB->quoteName($addscore_field) . ")
                                        AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)";
                        }
                    }
                    $expr .= " ) AS SCORE ";
                    $criteria['SELECT'][] = new QueryExpression($expr);

                    $ors = [
                        new QueryExpression(
                            "MATCH(" . $DB->quoteName('glpi_knowbaseitems.name') . ",
                        " . $DB->quoteName('glpi_knowbaseitems.answer') . ")
                        AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)"
                        )
                    ];

                    if (!empty($addscore)) {
                        foreach ($addscore as $addscore_field) {
                            $ors[] = [
                                'NOT' => [$addscore_field => null],
                                new QueryExpression(
                                    "MATCH(" . $DB->quoteName($addscore_field) . ")
                              AGAINST(" . $DB->quote($search_wilcard) . " IN BOOLEAN MODE)"
                                )
                            ];
                        }
                    }

                    $search_where =  $criteria['WHERE']; // Visibility restrict criteria

                    $search_where[] = ['OR' => $ors];

                   // Add visibility date
                    $visibility_crit = [
                        [
                            'OR'  => [
                                ['glpi_knowbaseitems.begin_date'  => null],
                                ['glpi_knowbaseitems.begin_date'  => ['<', new QueryExpression('NOW()')]]
                            ]
                        ], [
                            'OR'  => [
                                ['glpi_knowbaseitems.end_date'    => null],
                                ['glpi_knowbaseitems.end_date'    => ['>', new QueryExpression('NOW()')]]
                            ]
                        ]
                    ];
                    $search_where[] = $visibility_crit;

                    $criteria['ORDERBY'] = ['SCORE DESC'];

                   // preliminar query to allow alternate search if no result with fulltext
                    $search_criteria = [
                        'COUNT'     => 'cpt',
                        'LEFT JOIN' => $criteria['LEFT JOIN'],
                        'FROM'      => 'glpi_knowbaseitems',
                        'WHERE'     => $search_where
                    ];
                    $search_iterator = $DB->request($search_criteria);
                    $numrows_search = $search_iterator->current()['cpt'];

                    if ($numrows_search <= 0) {// not result this fulltext try with alternate search
                        $search1 = [/* 1 */   '/\\\"/',
                            /* 2 */   "/\+/",
                            /* 3 */   "/\*/",
                            /* 4 */   "/~/",
                            /* 5 */   "/</",
                            /* 6 */   "/>/",
                            /* 7 */   "/\(/",
                            /* 8 */   "/\)/",
                            /* 9 */   "/\-/"
                        ];
                        $contains = preg_replace($search1, "", $params["contains"]);
                        $ors = [
                            ["glpi_knowbaseitems.name"     => ['LIKE', Search::makeTextSearchValue($contains)]],
                            ["glpi_knowbaseitems.answer"   => ['LIKE', Search::makeTextSearchValue($contains)]]
                        ];
                        if (
                            KnowbaseItemTranslation::isKbTranslationActive()
                            && (countElementsInTable('glpi_knowbaseitemtranslations') > 0)
                        ) {
                            $ors[] = ["glpi_knowbaseitemtranslations.name"   => ['LIKE', Search::makeTextSearchValue($contains)]];
                            $ors[] = ["glpi_knowbaseitemtranslations.answer" => ['LIKE', Search::makeTextSearchValue($contains)]];
                        }
                        $criteria['WHERE'][] = ['OR' => $ors];
                       // Add visibility date
                        $criteria['WHERE'][] = $visibility_crit;
                    } else {
                        $criteria['WHERE'] = $search_where;
                    }
                }
                break;

            case 'browse':
                if (!Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
                   // Add visibility date
                    $criteria['WHERE'][] = [
                        'OR'  => [
                            ['glpi_knowbaseitems.begin_date' => null],
                            ['glpi_knowbaseitems.begin_date' => ['<', new QueryExpression('NOW()')]]
                        ]
                    ];
                    $criteria['WHERE'][] = [
                        'OR'  => [
                            ['glpi_knowbaseitems.end_date' => null],
                            ['glpi_knowbaseitems.end_date' => ['>', new QueryExpression('NOW()')]]
                        ]
                    ];
                }

                $criteria['ORDERBY'] = ['glpi_knowbaseitems.name ASC'];
                break;
        }

        return $criteria;
    }

    /**
     * Clean search for Boolean FullText
     *
     * @since 10.0.7
     * @param string $search
     *
     * @return string
     **/
    private static function computeBooleanFullTextSearch(string $search): string
    {
        $word_chars        = '\p{L}\p{N}_';
        $ponderation_chars = '+\-<>~';

        // Remove any whitespace from begin/end
        $search = preg_replace('/^[\p{Z}\h\v\r\n]+|[\p{Z}\h\v\r\n]+$/u', '', $search);

        // Remove all symbols except word chars, ponderation chars, parenthesis, quotes, wildcards and spaces.
        // @distance is not included, since it's unlikely a human will be using it through UI form
        $search = preg_replace("/[^{$word_chars}{$ponderation_chars}()\"* ]/u", '', $search);

        // Remove all ponderation chars, that can only precede a word and that are not preceded by either beginning of string, a space or an opening parenthesis
        $search = preg_replace("/(?<!^| |\()[{$ponderation_chars}]/u", '', $search);
        // Remove all ponderation chars that are not followed by a search term
        // (they are followed by a space, a closing parenthesis or end fo string)
        $search = preg_replace("/[{$ponderation_chars}]+( |\)|$)/u", '', $search);

        // Remove all opening parenthesis that are located inside a searched term
        // (they are preceded by a word char or a quote)
        $search = preg_replace("/(?<=[{$word_chars}\"])\(/u", '', $search);
        // Remove all closing parenthesis that are located inside a searched term
        // (they are followed by a word char or a quote)
        $search = preg_replace("/\)(?=[{$word_chars}\")])/u", '', $search);
        // Remove empty parenthesis
        $search = preg_replace("/\(\)/u", '', $search);
        // Remove all parenthesis if count of closing does not match count of opening ones
        if (mb_substr_count($search, '(') !== mb_substr_count($search, ')')) {
            $search = preg_replace("/[()]/u", '', $search);
        }

        // Remove all asterisks that are not located at the end of a word
        // (can be followed by a space, a closing parenthesis or end of string, and must be preceded by a word char)
        $search = preg_replace("/(?<=[{$word_chars}])\*(?! |\)|$)/u", '', $search);

        // Remove all double quotes
        // - that are not located before a searched term
        //   (can be preceded by beginning of string, an operator, a space or an opening parenthesis, and must be followed by a word char)
        $search = preg_replace("/(?<=^|[{$ponderation_chars} (])\"(?![{$word_chars}])/u", '', $search);
        // - that are not located after a searched term
        //   (can be followed by a space, a closing parenthesis or end of string, and must be preceded by a word char)
        $search = preg_replace("/(?<=[{$word_chars}])\"(?! |\)|$)/u", '', $search);
        // - if the count is not even
        if (mb_substr_count($search, '"') % 2 !== 0) {
            $search = preg_replace("/\"/u", '', $search);
        }

        // Check if the new value is just the set of operators and spaces and if it is - set the value to an empty string
        if (preg_match("/^[{$ponderation_chars}()\"* ]+$/u", $search)) {
            $search = '';
        }

        // Remove extra spaces
        $search = preg_replace('/\s+/u', ' ', trim($search));

        // Add * foreach word when no boolean operator is used
        if (!preg_match('/[^\p{L}\p{N}_ ]/u', $search)) {
            $search = implode('* ', explode(' ', $search)) . '*';
        }

        return $search;
    }


    /**
     * Print out list kb item
     *
     * @param $options            $_GET
     * @param $type      string   search type : browse / search (default search)
     **/
    public static function showList($options, $type = 'search')
    {
        global $CFG_GLPI;

        $DBread = DBConnection::getReadConnection();

       // Default values of parameters
        $params['faq']                       = !Session::haveRight(self::$rightname, READ);
        $params["start"]                     = "0";
        $params["knowbaseitemcategories_id"] = null;
        $params["contains"]                  = "";
        $params["target"]                    = $_SERVER['PHP_SELF'];

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $params[$key] = $val;
            }
        }
        $ki = new self();
        switch ($type) {
            case 'myunpublished':
                if (!Session::haveRightsOr(self::$rightname, [UPDATE, self::PUBLISHFAQ])) {
                    return false;
                }
                break;

            case 'allunpublished':
                if (!Session::haveRight(self::$rightname, self::KNOWBASEADMIN)) {
                    return false;
                }
                break;

            default:
                break;
        }

        if (!$params["start"]) {
            $params["start"] = 0;
        }

        $criteria = self::getListRequest($params, $type);

        $main_iterator = $DBread->request($criteria);
        $rows = count($main_iterator);
        $numrows = $rows;

       // Get it from database
        $KbCategory = new KnowbaseItemCategory();
        $title      = "";
        if ($KbCategory->getFromDB($params["knowbaseitemcategories_id"])) {
            $title = (empty($KbCategory->fields['name']) ? "(" . $params['knowbaseitemcategories_id'] . ")"
                                                      : $KbCategory->fields['name']);
            $title = sprintf(__('%1$s: %2$s'), _n('Category', 'Categories', 1), $title);
        }

        Session::initNavigateListItems('KnowbaseItem', $title);
       // force using getSearchUrl on list icon (when viewing a single article)
        $_SESSION['glpilisturl']['KnowbaseItem'] = '';

        $list_limit = $_SESSION['glpilist_limit'];

        $showwriter = in_array($type, ['myunpublished', 'allunpublished', 'allmy']);

       // Limit the result, if no limit applies, use prior result
        if (
            ($rows > $list_limit)
            && !isset($_GET['export_all'])
        ) {
            $criteria['START'] = (int)$params['start'];
            $criteria['LIMIT'] = (int)$list_limit;
            $main_iterator = $DBread->request($criteria);
            $numrows = count($main_iterator);
        }

        if ($numrows > 0) {
           // Set display type for export if define
            $output_type = Search::HTML_OUTPUT;

            if (isset($_GET["display_type"])) {
                $output_type = $_GET["display_type"];
            }

           // Pager
            $parameters = "start=" . $params["start"] . "&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, _n('Category', 'Categories', 1), $header_num);

            if ($output_type == Search::HTML_OUTPUT) {
                echo Search::showHeaderItem($output_type, _n('Associated element', 'Associated elements', Session::getPluralNumber()), $header_num);
            }

            if (
                isset($options['item_itemtype'])
                && isset($options['item_items_id'])
                && ($output_type == Search::HTML_OUTPUT)
            ) {
                echo Search::showHeaderItem($output_type, '&nbsp;', $header_num);
            }

           // Num of the row (1=header_line)
            $row_num = 1;
            foreach ($main_iterator as $data) {
                Session::addToNavigateListItems('KnowbaseItem', $data["id"]);
               // Column num
                $item_num = 1;
                echo Search::showNewLine($output_type, ($row_num - 1) % 2);
                $row_num++;

                $item = new self();
                $item->getFromDB($data["id"]);
                $name   = $data["name"];
                $answer = $data["answer"];
               // Manage translations
                if (isset($data['transname']) && !empty($data['transname'])) {
                    $name   = $data["transname"];
                }
                if (isset($data['transanswer']) && !empty($data['transanswer'])) {
                    $answer = $data["transanswer"];
                }

                if ($output_type == Search::HTML_OUTPUT) {
                    $toadd = '';
                    if (
                        isset($options['item_itemtype'])
                        && isset($options['item_items_id'])
                    ) {
                        $href  = " href='#' data-bs-toggle='modal' data-bs-target='#kbshow{$data["id"]}'";
                        $toadd = Ajax::createIframeModalWindow(
                            'kbshow' . $data["id"],
                            KnowbaseItem::getFormURLWithID($data["id"]),
                            ['display' => false]
                        );
                    } else {
                        $href = " href=\"" . KnowbaseItem::getFormURLWithID($data["id"]) . "\" ";
                    }

                    $fa_class = "";
                    $fa_title = "";
                    if (
                        $data['is_faq']
                        && (!Session::isMultiEntitiesMode()
                        || isset($data['visibility_count'])
                           && $data['visibility_count'] > 0)
                    ) {
                        $fa_class = "fa-question-circle faq";
                        $fa_title = __s("This item is part of the FAQ");
                    } else if (
                        isset($data['visibility_count'])
                        && $data['visibility_count'] <= 0
                    ) {
                        $fa_class = "fa-eye-slash not-published";
                        $fa_title = __s("This item is not published yet");
                    }
                    echo Search::showItem(
                        $output_type,
                        "<div class='kb'>$toadd <i class='fa fa-fw $fa_class' title='$fa_title'></i> <a $href>" . Html::resume_text($name, 80) . "</a></div>
                                       <div class='kb_resume'>" . Html::resume_text(RichText::getTextFromHtml($answer, false, false, true), 600) . "</div>",
                        $item_num,
                        $row_num
                    );
                } else {
                    echo Search::showItem($output_type, $name, $item_num, $row_num);
                    echo Search::showItem($output_type, RichText::getTextFromHtml($answer, true, false, true), $item_num, $row_num);
                }

                $showuserlink = 0;
                if (Session::haveRight('user', READ)) {
                    $showuserlink = 1;
                }
                if ($showwriter) {
                    echo Search::showItem(
                        $output_type,
                        getUserName($data["users_id"], $showuserlink),
                        $item_num,
                        $row_num
                    );
                }

                $categories_names = [];
                $ki->getFromDB($data["id"]);
                $categories = KnowbaseItem_KnowbaseItemCategory::getItems($ki);
                foreach ($categories as $category) {
                    $knowbaseitemcategories_id = $category['knowbaseitemcategories_id'];
                    $fullcategoryname          = getTreeValueCompleteName(
                        "glpi_knowbaseitemcategories",
                        $knowbaseitemcategories_id
                    );
                    if ($output_type == Search::HTML_OUTPUT) {
                        $cathref = $ki->getSearchURL() . "?knowbaseitemcategories_id=" .
                              $knowbaseitemcategories_id . '&amp;forcetab=Knowbase$2';
                        $categories_names[] = "<a class='kb-category'"
                            . " href='$cathref'"
                            . " data-category-id='" . $knowbaseitemcategories_id . "'"
                            . ">" . $fullcategoryname . '</a>';
                    } else {
                        $categories_names[] = $fullcategoryname;
                    }
                }
                echo Search::showItem($output_type, implode(', ', $categories_names), $item_num, $row_num);

                if ($output_type == Search::HTML_OUTPUT) {
                    echo "<td class='center'>";
                    $j = 0;
                    $iterator = $DBread->request([
                        'FIELDS' => 'documents_id',
                        'FROM'   => 'glpi_documents_items',
                        'WHERE'  => [
                            'items_id'  => $data["id"],
                            'itemtype'  => 'KnowbaseItem'
                        ] + getEntitiesRestrictCriteria('', '', '', true)
                    ]);
                    foreach ($iterator as $docs) {
                          $doc = new Document();
                          $doc->getFromDB($docs["documents_id"]);
                          echo $doc->getDownloadLink();
                          $j++;
                        if ($j > 1) {
                            echo "<br>";
                        }
                    }
                    echo "</td>";
                }

                if (
                    isset($options['item_itemtype'])
                    && isset($options['item_items_id'])
                    && ($output_type == Search::HTML_OUTPUT)
                ) {
                    $forcetab = $options['item_itemtype'] . '$1';
                    $item_itemtype = $options['item_itemtype'];
                    $content = "<a href='" . $item_itemtype::getFormURLWithID($options['item_items_id']) .
                              "&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
     * @param bool   $display if false, return html
     *
     * @return void
     **/
    public static function showRecentPopular(string $type = "", bool $display = true)
    {
        global $DB;

        $faq = !Session::haveRight(self::$rightname, READ);

        $criteria = [
            'SELECT'    => ['glpi_knowbaseitems.*'],
            'DISTINCT'  => true,
            'FROM'      => self::getTable(),
            'WHERE'     => [],
            'LIMIT'     => 10
        ];

        if ($type == "recent") {
            $criteria['ORDERBY'] = 'date_creation DESC';
            $title   = __('Recent entries');
        } else if ($type == 'lastupdate') {
            $criteria['ORDERBY'] = 'date_mod DESC';
            $title   = __('Last updated entries');
        } else {
            $criteria['ORDERBY'] = 'view DESC';
            $title   = __('Most popular questions');
        }

       // Force all joins for not published to verify no visibility set
        $restrict = self::getVisibilityCriteria(true);
        unset($restrict['WHERE']);
        unset($restrict['SELECT']);
        $criteria = array_merge($criteria, $restrict);

        if (Session::getLoginUserID()) {
            $restrict = self::getVisibilityCriteria();
            $criteria['WHERE'] = array_merge($criteria['WHERE'], $restrict['WHERE']);
        } else {
           // Anonymous access
            if (Session::isMultiEntitiesMode()) {
                $criteria['WHERE']['glpi_entities_knowbaseitems.entities_id'] = 0;
                $criteria['WHERE']['glpi_entities_knowbaseitems.is_recursive'] = 1;
            }
        }

       // Only published
        $criteria['WHERE'][] = [
            'NOT'  => [
                'glpi_entities_knowbaseitems.entities_id' => null,
                'glpi_knowbaseitems_profiles.profiles_id' => null,
                'glpi_groups_knowbaseitems.groups_id'     => null,
                'glpi_knowbaseitems_users.users_id'       => null
            ]
        ];

       // Add visibility date
        $criteria['WHERE'][] = [
            'OR'  => [
                ['glpi_knowbaseitems.begin_date' => null],
                ['glpi_knowbaseitems.begin_date' => ['<', new QueryExpression('NOW()')]]
            ]
        ];
        $criteria['WHERE'][] = [
            'OR'  => [
                ['glpi_knowbaseitems.end_date'   => null],
                ['glpi_knowbaseitems.end_date'   => ['>', new QueryExpression('NOW()')]]
            ]
        ];

        if ($faq) { // FAQ
            $criteria['WHERE']['glpi_knowbaseitems.is_faq'] = 1;
        }

        if (
            KnowbaseItemTranslation::isKbTranslationActive()
            && (countElementsInTable('glpi_knowbaseitemtranslations') > 0)
        ) {
            $criteria['LEFT JOIN']['glpi_knowbaseitemtranslations'] = [
                'ON'  => [
                    'glpi_knowbaseitems'             => 'id',
                    'glpi_knowbaseitemtranslations'  => 'knowbaseitems_id', [
                        'AND'                            => [
                            'glpi_knowbaseitemtranslations.language' => $_SESSION['glpilanguage']
                        ]
                    ]
                ]
            ];
            $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.name AS transname';
            $criteria['SELECT'][] = 'glpi_knowbaseitemtranslations.answer AS transanswer';
        }

        $iterator = $DB->request($criteria);

        $output = "";
        if (count($iterator)) {
            $output .= "<table class='tab_cadrehov'>";
            $output .= "<tr class='noHover'><th>" . $title . "</th></tr>";
            foreach ($iterator as $data) {
                $name = $data['name'];

                if (isset($data['transname']) && !empty($data['transname'])) {
                    $name = $data['transname'];
                }
                $output .= "<tr class='tab_bg_2'><td class='left'><div class='kb'>";
                if ($data['is_faq']) {
                    $output .= "<i class='fa fa-fw fa-question-circle faq' title='" . __("This item is part of the FAQ") . "'></i>";
                }
                $output .= Html::link(Html::resume_text($name, 80), KnowbaseItem::getFormURLWithID($data["id"]), [
                    'class' => $data['is_faq'] ? 'faq' : 'knowbase',
                    'title' => $data['is_faq'] ? __s("This item is part of the FAQ") : ''
                ]);
                $output .= "</div></td></tr>";
            }
            $output .= "</table>";
        }

        if ($display) {
            echo $output;
        } else {
            return $output;
        }
    }


    public function rawSearchOptions()
    {
        $tab = [];

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

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

        $tab[] = [
            'id'                 => '6',
            'table'              => $this->getTable(),
            'field'              => 'name',
            'name'               => __('Subject'),
            'datatype'           => 'text'
        ];

        $tab[] = [
            'id'                 => '7',
            'table'              => $this->getTable(),
            'field'              => 'answer',
            'name'               => __('Content'),
            'datatype'           => 'text',
            'htmltext'           => true
        ];

        $tab[] = [
            'id'                 => '8',
            'table'              => $this->getTable(),
            'field'              => 'is_faq',
            'name'               => __('FAQ item'),
            'datatype'           => 'bool'
        ];

        $tab[] = [
            'id'                 => '9',
            'table'              => $this->getTable(),
            'field'              => 'view',
            'name'               => _n('View', 'Views', Session::getPluralNumber()),
            'datatype'           => 'integer',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '10',
            'table'              => $this->getTable(),
            'field'              => 'begin_date',
            'name'               => __('Visibility start date'),
            'datatype'           => 'datetime'
        ];

        $tab[] = [
            'id'                 => '11',
            'table'              => $this->getTable(),
            'field'              => 'end_date',
            'name'               => __('Visibility end date'),
            'datatype'           => 'datetime'
        ];

        $tab[] = [
            'id'                 => '19',
            'table'              => $this->getTable(),
            'field'              => 'date_mod',
            'name'               => __('Last update'),
            'datatype'           => 'datetime',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '121',
            'table'              => $this->getTable(),
            'field'              => 'date_creation',
            'name'               => __('Creation date'),
            'datatype'           => 'datetime',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '70',
            'table'              => 'glpi_users',
            'field'              => 'name',
            'name'               => User::getTypeName(1),
            'massiveaction'      => false,
            'datatype'           => 'dropdown',
            'right'              => 'all'
        ];

       // add objectlock search options
        $tab = array_merge($tab, ObjectLock::rawSearchOptionsToAdd(get_class($this)));

        return $tab;
    }

    public function getRights($interface = 'central')
    {

        if ($interface == 'central') {
            $values = parent::getRights();
            $values[self::KNOWBASEADMIN] = __('Knowledge base administration');
            $values[self::PUBLISHFAQ]    = __('Publish in the FAQ');
            $values[self::COMMENTS]      = __('Comment KB entries');
        }
        $values[self::READFAQ]       = __('Read the FAQ');
        return $values;
    }

    public function pre_updateInDB()
    {
        $revision = new KnowbaseItem_Revision();
        $kb = new KnowbaseItem();
        $kb->getFromDB($this->getID());
        $revision->createNew($kb);
    }

    /**
     * Get KB answer, with id on titles to set anchors
     *
     * @return string
     */
    public function getAnswer()
    {
        if (KnowbaseItemTranslation::canBeTranslated($this)) {
            $answer = KnowbaseItemTranslation::getTranslatedValue($this, 'answer');
        } else {
            $answer = $this->fields["answer"];
        }
        $answer = RichText::getEnhancedHtml($answer, [
            'text_maxsize' => 0 // Show all text without read more button
        ]);

        $callback = function ($matches) {
          //1 => tag name, 2 => existing attributes, 3 => title contents
            $tpl = '<%tag%attrs id="%slug"><a href="#%slug">%icon</a>%title</%tag>';

            $title = str_replace(
                ['%tag', '%attrs', '%slug', '%title', '%icon'],
                [
                    $matches[1],
                    $matches[2],
                    Toolbox::slugify($matches[3]),
                    $matches[3],
                    '<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"/></svg>'
                ],
                $tpl
            );

            return $title;
        };
        $pattern = '|<(h[1-6]{1})(.?[^>])?>(.+?)</h[1-6]{1}>|';
        $answer = preg_replace_callback($pattern, $callback, $answer);

        return $answer;
    }

    /**
     * Get dropdown parameters from showVisibility method
     *
     * @return array
     */
    protected function getShowVisibilityDropdownParams()
    {
        $params = parent::getShowVisibilityDropdownParams();
        $params['right'] = ($this->getField('is_faq') ? 'faq' : 'knowbase');
        $params['allusers'] = 1;
        return $params;
    }

    /**
     * Reverts item contents to specified revision
     *
     * @param integer $revid Revision ID
     *
     * @return boolean
     */
    public function revertTo($revid)
    {
        $revision = new KnowbaseItem_Revision();
        $revision->getFromDB($revid);

        $values = [
            'id'     => $this->getID(),
            'name'   => addslashes($revision->fields['name']),
            'answer' => addslashes($revision->fields['answer'])
        ];

        if ($this->update($values)) {
            Event::log(
                $this->getID(),
                "knowbaseitem",
                5,
                "tools",
                //TRANS: %1$s is the user login, %2$s the revision number
                sprintf(__('%1$s reverts item to revision %2$s'), $_SESSION["glpiname"], $revid)
            );
            return true;
        } else {
            return false;
        }
    }

    /**
     * Get ids of KBI in given category
     *
     * @param int           $category_id   id of the parent category
     * @param KnowbaseItem  $kbi           used only for unit tests
     *
     * @return array        Array of ids
     */
    public static function getForCategory($category_id, $kbi = null)
    {
        global $DB;

        if ($kbi === null) {
            $kbi = new self();
        }

        $ids = $DB->request([
            'SELECT' => self::getTable() . '.id',

            'FROM'   => self::getTable(),
            'LEFT JOIN' => [
                'glpi_knowbaseitems_knowbaseitemcategories' => [
                    'ON'  => [
                        'glpi_knowbaseitems_knowbaseitemcategories'  => 'knowbaseitems_id',
                        'glpi_knowbaseitems'             => 'id'
                    ]
                ]
            ],
            'WHERE'  => ['glpi_knowbaseitems_knowbaseitemcategories.knowbaseitemcategories_id' => $category_id],
        ]);

       // Get array of ids
        $ids = array_map(function ($row) {
            return $row['id'];
        }, iterator_to_array($ids, false));

       // Filter on canViewItem
        $ids = array_filter($ids, function ($id) use ($kbi) {
            $kbi->getFromDB($id);
            return $kbi->canViewItem();
        });

       // Avoid empty IN
        if (count($ids) === 0) {
            $ids[] = -1;
        }

        return $ids;
    }


    public static function getIcon()
    {
        return "ti ti-lifebuoy";
    }
}
			
			


Thanks For 0xGh05T - DSRF14 - Mr.Dan07 - Leri01 - FxshX7 - AlkaExploiter - xLoveSyndrome'z - Acep Gans'z

JMDS TRACK – Just Another Diagnostics Lab Site

Home

JMDS TRACK Cameroon

Boost the productivity of your mobile ressources


Make An Appointment


Fleet management

  1. Reduce the operting cost and the unavailability of your vehicles
  2. reduce the fuel consumption of your fleet
  3. Improve the driving dehavior and safety of your drivers
  4. optimize the utilization rate of your equipment 
  5. protect your vehicle against theft
  6. Improve the quality of your customer service


Find out more

Assets management

  1. Track the roaming of your equipment
  2. Optimise the management of your assets on site and during transport
  3. Secure the transport of your goods
  4. Make your team responsible for preventing the loss of tools, equipment
  5. Take a real-time inventory of your equipment on site
  6. Easily find your mobile objects or equipment



Find out more



Find out more

Antitheft solutions

  1. Secure your vehicles and machinery and increase your chances of recovering them in the event of theft
  2. Protect your assets and reduce the costs associated with their loss
  3. Combine immobiliser and driver identification and limit the risk of theft
  4. Identify fuel theft and reduce costs
  5. Protect your goods and take no more risks
  6. Be alerted to abnormal events

Our Location

 Douala BP cité 

     and

Yaoundé Total Essos


Make An Appointment


Get Directions

682230363/ 677481892

What makes us different from others

  • young and dynamic team
  • call center 24/24 7/7
  • roaming throughout Africa
  • team of developers who can develop customer-specific solutions
  • diversity of services
  • reactive and prompt after-sales service when soliciting a customer or a malfunction
  • Free Maintenance and installation in the cities of Douala and Yaounde

https://youtu.be/xI1cz_Jh2x8

15+
years of experience in GPS system development, production and deployment.

15 Collaborators

More than 15 employees dedicated to the research and development of new applications and to customer care

5 000 Vehicles and mobile assets

5 000 vehicles and mobile assets under management, in Africa

Our Partners










Latest Case Studies

Our current projects 

5/5
Bon SAV , SATISFAIT DU TRAITEMENT DES REQUETES

M DIPITA CHRISTIAN
Logistic Safety Manager Road Safety Manager
5/5
La réactivité de JMDS est excellente
Nous restons satisfait dans l’ensemble des prestations relatives a la couverture de notre parc automobile

Hervé Frédéric NDENGUE
Chef Service Adjoint de la Sécurité Générale (CNPS)
5/5
L’APPLICATION EMIXIS est convivial A L’utilisation
BEIG-3 SARL
DIRECTOR GENERAL
5/5
Nevertheless I am delighted with the service
MR. BISSE BENJAMIN
CUSTOMER

Subsribe To Our Newsletter

Stay in touch with us to get latest news and special offers.



Address JMDS TRACK

Douala bp cité



and

YAOUNDE Total Essos

Call Us

+237682230363



Email Us


info@jmdstrack.cm