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/ Dashboard/

/home/jmdstrac/public_html/devices/src/Dashboard/Grid.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/>.
 *
 * ---------------------------------------------------------------------
 */

namespace Glpi\Dashboard;

use Config;
use DateInterval;
use Dropdown;
use Glpi\Application\View\TemplateRenderer;
use Glpi\Plugin\Hooks;
use Html;
use Plugin;
use Ramsey\Uuid\Uuid;
use Reminder;
use Session;
use ShareDashboardDropdown;
use Telemetry;
use Ticket;
use Toolbox;

class Grid
{
    protected $cell_margin     = 6;
    protected $grid_cols       = 26;
    protected $grid_rows       = 24;
    protected $current         = "";
    protected $dashboard       = null;
    protected $items           = [];
    protected $context            = '';

    public static $embed              = false;
    public static $all_dashboards     = [];

    public function __construct(string $dashboard_key = "central", int $grid_cols = 26, int $grid_rows = 24, string $context = 'core')
    {

        $this->current   = $dashboard_key;
        $this->grid_cols = $grid_cols;
        $this->grid_rows = $grid_rows;

        $this->dashboard = new Dashboard($dashboard_key);
        $this->context   = $context;
    }


    /**
     * Return the instance of current dasbhoard
     *
     * @return Dashboard
     */
    public function getDashboard()
    {
        return $this->dashboard;
    }

    /**
     * Return the context used for this Grid instance
     * @return string
     */
    public function getContext()
    {
        return $this->context;
    }


    /**
     * load all existing dashboards from DB into a static property for caching data
     *
     * @param bool $force, if false, don't use cache
     *
     * @return bool
     */
    public static function loadAllDashboards(bool $force = true): bool
    {
        if (
            !is_array(self::$all_dashboards)
            || count(self::$all_dashboards) === 0
            || $force
        ) {
            self::$all_dashboards = Dashboard::getAll($force, !self::$embed, '');
        }

        return is_array(self::$all_dashboards);
    }


    /**
     * Init dashboards cards
     * A define.php constant (GLPI_AJAX_DASHBOARD) exists to control how the cards should be loaded
     *  - if true: load all cards in seperate ajax request
     *    pros: slow cards wont impact the others
     * - else: load all cards in a single ajax request
     *    pros: less strain for the server
     *
     * @return void
     */
    public function getCards()
    {
        self::loadAllDashboards();

        if (
            !isset(self::$all_dashboards[$this->current])
            || !isset(self::$all_dashboards[$this->current]['items'])
        ) {
            self::$all_dashboards[$this->current] = [
                'items' => []
            ];
        }

        foreach (self::$all_dashboards[$this->current]['items'] as $specs) {
            $card_id      = $specs['card_id'] ?? $specs['gridstack_id'] ?? $specs['id'];
            $gridstack_id = $specs['gridstack_id']   ?? $specs['id'];
            $card_options = ($specs['card_options'] ?? []) + [
                'card_id' => $card_id
            ];

            $card_html    = <<<HTML
            <div class="loading-card">
               <i class="fas fa-spinner fa-spin fa-3x"></i>
            </div>
HTML;
            $this->addGridItem(
                $card_html,
                $gridstack_id,
                $specs['x'] ?? -1,
                $specs['y'] ?? -1,
                $specs['width'] ?? 2,
                $specs['height'] ?? 2,
                $card_options
            );
        }
    }


    /**
     * Do we have the right to view at least one dashboard int the current collection
     *
     * @return bool
     */
    public function canViewCurrent(): bool
    {
       // check global (admin) right
        if (Dashboard::canView()) {
            return true;
        }

        return $this->dashboard->canViewCurrent();
    }


    /**
     * Do we have the right to view at least one dashboard?
     *
     * This can be optionally restricted to a specific context.
     * @return bool
     */
    public static function canViewOneDashboard($context = null): bool
    {
        if (Dashboard::canView()) {
            return true;
        }

        self::loadAllDashboards();

        $viewable = self::$all_dashboards;
        if ($context) {
            $viewable = array_filter(self::$all_dashboards, function ($dashboard) use ($context) {
                return $dashboard['context'] === $context;
            });
        }
        return (count($viewable) > 0);
    }


    /**
     * Do we have the right to view the specified dashboard int the current collection
     *
     * @param string $key the dashboard to check
     * @param bool   $canViewAll Right to view all dashboards
     *
     * @return bool
     */
    public static function canViewSpecificicDashboard($key, $canViewAll = false): bool
    {
        self::loadAllDashboards();

        $dashboard = new Dashboard($key);
        $dashboard->load();
       // check global (admin) right
        if (Dashboard::canView() && !$dashboard->isPrivate()) {
            return true;
        }

        return isset(self::$all_dashboards[$key]);
    }


    /**
     * Display grid for the current dashboard
     *
     * @return void display html of the grid
     */
    public function show(bool $mini = false, ?string $token = null)
    {
        global $GLPI_CACHE;

        $rand = mt_rand();

        if (!self::$embed && !$this->dashboard->canViewCurrent()) {
            return;
        }

        self::loadAllDashboards();

        $this->restoreLastDashboard();

        if ($mini) {
            $this->cell_margin = 3;
        }

        $embed_class   = self::$embed ? "embed" : "";
        $mini_class    = $mini ? "mini" : "";

        $nb_dashboards = count(self::$all_dashboards);

        $can_view_all  = Session::haveRight('dashboard', READ) || self::$embed;
        $can_create    = Session::haveRight('dashboard', CREATE);
        $can_edit      = Session::haveRight('dashboard', UPDATE) && $nb_dashboards;
        $can_purge     = Session::haveRight('dashboard', PURGE) && $nb_dashboards;
        $can_clone     = $can_create && $nb_dashboards;

       // prepare html for add controls
        $add_controls = "";
        for ($y = 0; $y < $this->grid_rows; $y++) {
            for ($x = 0; $x < $this->grid_cols; $x++) {
                $add_controls .= "<div class='cell-add' data-x='$x' data-y='$y'>&nbsp;</div>";
            }
        }

        // Force clear the cards cache in debug mode
        if ($_SESSION['glpi_use_mode'] === Session::DEBUG_MODE) {
            $GLPI_CACHE->delete(self::getAllDashboardCardsCacheKey());
        }

        // prepare all available cards
        $cards = $this->getAllDasboardCards();

       // prepare all available widgets
        $all_widgets = Widget::getAllTypes();

       // prepare labels
        $embed_label      = __("Share or embed this dashboard");
        $delete_label     = __("Delete this dashboard");
        $history_label    = __("Toggle auto-refresh");
        $night_label      = __("Toggle night mode");
        $fs_label         = __("Toggle fullscreen");
        $clone_label      = __("Clone this dashboard");
        $edit_label       = __("Toggle edit mode");
        $add_filter_lbl   = __("Add filter");
        $add_dash_label   = __("Add a new dashboard");
        $save_label       = _x('button', "Save");

        $gridstack_items = $this->getGridItemsHtml(!$mini);

        $dropdown_dashboards = "";
        if ($nb_dashboards) {
            $dropdown_dashboards = self::dropdownDashboard("", [
                'value'        => $this->current,
                'display'      => false,
                'class'        => 'dashboard_select form-select',
                'can_view_all' => $can_view_all,
                'noselect2'    => true,
                'context'      => $this->context
            ]);
        }

        $dashboard_title = $this->dashboard->getTitle();

        $l_tb_icons   = "";
        $r_tb_icons   = "";
        $rename       = "";
        $left_toolbar = "";
        $grid_guide   = "";

        if (!self::$embed) {
            if (!$mini && $can_create) {
                $l_tb_icons .= "<i class='btn btn-outline-secondary fas fa-plus fs-toggle add-dashboard' title='$add_dash_label'></i>";
            }
            if (!$mini && $can_clone) {
                $r_tb_icons .= "<i class='btn btn-outline-secondary ti ti-copy fs-toggle clone-dashboard' title='$clone_label'></i>";
            }
            if (!$mini && $can_edit) {
                $r_tb_icons .= "<i class='btn btn-outline-secondary ti ti-share fs-toggle open-embed' title='$embed_label'></i>";
                $rename = "<div class='edit-dashboard-properties'>
               <input type='text' class='dashboard-name form-control' value='{$dashboard_title}' size='1'>
               <i class='btn btn-outline-secondary far fa-save save-dashboard-name' title='{$save_label}'></i>
               <span class='display-message'></span>
            </div>";
            }
            if (!$mini && $can_purge) {
                $r_tb_icons .= "<i class='btn btn-outline-secondary ti ti-trash fs-toggle delete-dashboard' title='$delete_label'></i>";
            }
            if ($can_edit) {
                $r_tb_icons .= "<i class='btn btn-outline-secondary ti ti-edit fs-toggle edit-dashboard' title='$edit_label'></i>";
            }

            if (!$mini) {
                $r_tb_icons .= "<i class='btn btn-outline-secondary ti ti-maximize toggle-fullscreen' title='$fs_label'></i>";
            }

            if (!$mini) {
                $left_toolbar = <<<HTML
               <span class="toolbar left-toolbar">
                  <div class="change-dashboard d-flex">
                     $dropdown_dashboards
                     $l_tb_icons
                  </div>
                  $rename
               </span>
HTML;
            }

            $grid_guide = <<<HTML
            <div class="grid-guide">
               $add_controls
            </div>
HTML;
        }

        $toolbars = <<<HTML
         $left_toolbar
         <span class="toolbar">
            <i class="btn btn-outline-secondary fas fa-history auto-refresh" title="$history_label"></i>
            <i class="btn btn-outline-secondary fas fa-moon night-mode" title="$night_label"></i>
            $r_tb_icons
         </span>
HTML;

        $filters = "";
        if (!$mini) {
            $filters = <<<HTML
         <div class='filters_toolbar'>
            <span class='filters'></span>
            <span class='filters-control'>
               <i class="btn btn-sm btn-icon btn-ghost-secondary fas fa-plus plus-sign add-filter">
                  <span class='add-filter-lbl'>{$add_filter_lbl}</span>
               </i>
            </span>
         </div>
HTML;
        }

       // display the grid
        $html = <<<HTML
      <div class="dashboard {$embed_class} {$mini_class}" id="dashboard-{$rand}">
         <span class='glpi_logo'></span>
         $toolbars
         $filters
         $grid_guide
         <div class="grid-stack grid-stack-{$this->grid_cols}"
            id="grid-stack-$rand"
            gs-column="{$this->grid_cols}"
            gs-min-row="{$this->grid_rows}"
            style="width: 100%">
            $gridstack_items
         </div>
      </div>
HTML;

        if ($mini) {
            $html = "<div class='card mb-4 d-none d-md-block dashboard-card'>
            <div class='card-body p-2'>
               $html
            </div>
         </div>";
        }

        $ajax_cards = GLPI_AJAX_DASHBOARD;
        $cache_key  = sha1($_SESSION['glpiactiveentities_string'] ?? "");

        $js_params = json_encode([
            'current'       => $this->current,
            'cols'          => $this->grid_cols,
            'rows'          => $this->grid_rows,
            'cell_margin'   => $this->cell_margin,
            'rand'          => $rand,
            'ajax_cards'    => $ajax_cards,
            'all_cards'     => $cards,
            'all_widgets'   => $all_widgets,
            'context'       => $this->context,
            'cache_key'     => $cache_key,
            'embed'         => self::$embed,
            'token'         => $token,
            'entities_id'   => $_SESSION['glpiactive_entity'],
            'is_recursive'  => $_SESSION['glpiactive_entity_recursive'] ? 1 : 0
        ]);
        $js = <<<JAVASCRIPT
      $(function () {
         new GLPIDashboard({$js_params})
      });
JAVASCRIPT;
        $js = Html::scriptBlock($js);

        echo $html . $js;
    }


    public function showDefault()
    {
        echo "<div class='card p-3'>";
        $this->show();
        echo "</div>";
    }


    /**
     * Show an embeded dashboard.
     * We must check token validity to avoid displaying dashboard to invalid users
     *
     * @param array $params contains theses keys:
     * - dashboard: the dashboard system name
     * - entities_id: entity to init in session
     * - is_recursive: do we need to display sub entities
     * - token: the token to check
     *
     * @return void (display)
     */
    public function embed(array $params = [])
    {
        $defaults = [
            'dashboard'    => '',
            'entities_id'  => 0,
            'is_recursive' => 0,
            'token'        => ''
        ];
        $params = array_merge($defaults, $params);

        if (!self::checkToken($params)) {
            Html::displayRightError();
            exit;
        }

        self::$embed = true;

       // load minimal session
        $_SESSION["glpiactive_entity"]           = $params['entities_id'];
        $_SESSION["glpiactive_entity_recursive"] = $params['is_recursive'];
        $_SESSION["glpiname"]                    = 'embed_dashboard';
        $_SESSION["glpigroups"]                  = [];
        if ($params['is_recursive']) {
            $entities = getSonsOf("glpi_entities", $params['entities_id']);
        } else {
            $entities = [$params['entities_id']];
        }
        $_SESSION['glpiactiveentities']        = $entities;
        $_SESSION['glpiactiveentities_string'] = "'" . implode("', '", $entities) . "'";

       // show embeded dashboard
        $this->show(true, $params['token']);
    }

    public static function getToken(string $dasboard = "", int $entities_id = 0, int $is_recursive = 0): string
    {
        $seed         = $dasboard . $entities_id . $is_recursive . Telemetry::getInstanceUuid();
        $uuid         = Uuid::uuid5(Uuid::NAMESPACE_OID, $seed);
        $token        = $uuid->toString();

        return $token;
    }

    /**
     * Check token variables (compare it to `dashboard`, `entities_id` and `is_recursive` paramater)
     *
     * @param array $params contains theses keys:
     * - dashboard: the dashboard system name
     * - entities_id: entity to init in session
     * - is_recursive: do we need to display sub entities
     * - token: the token to check
     *
     * @return bool
     */
    public static function checkToken(array $params = []): bool
    {
        $defaults = [
            'dashboard'    => '',
            'entities_id'  => 0,
            'is_recursive' => 0,
            'token'        => ''
        ];
        $params = array_merge($defaults, $params);

        $token = self::getToken(
            $params['dashboard'],
            $params['entities_id'],
            $params['is_recursive']
        );

        if ($token !== $params['token']) {
            return false;
        }

        return true;
    }


    /**
     * Return the html for all items for the current dashboard
     *
     * @param bool $with_lock if true, return also a locked bottom item (to fix grid height)
     *
     * @return string html of the grid items
     */
    public function getGridItemsHtml(bool $with_lock = true, bool $embed = false): string
    {
        if ($embed) {
            self::$embed = true;
        }

        $this->getCards();

        if ($with_lock) {
            $this->items[] = <<<HTML
         <div class="grid-stack-item lock-bottom"
            gs-no-resize="true"
            gs-no-move="true"
            gs-h="1"
            gs-w="{$this->grid_cols}"
            gs-x="0"
            gs-y="{$this->grid_rows}"></div>
HTML;
        }

       // append all elements to insert them in html
        return implode("", $this->items);
    }


    /**
     * Add a new grid item
     *
     * @param string $html content of the card
     * @param string $gridstack_id unique id identifying the card (used in gridstack)
     * @param int $x position in the grid
     * @param int $y position in the grid
     * @param int $width size in the grid
     * @param int $height size in the grid
     * @param array $data_option aditional options passed to the widget, contains at least thses keys:
     *                             - string 'color'
     * @return void
     */
    public function addGridItem(
        string $html = "",
        string $gridstack_id = "",
        int $x = -1,
        int $y = -1,
        int $width = 2,
        int $height = 2,
        array $data_option = []
    ) {

       // let grid-stack to autoposition item
        $autoposition = 'gs-auto-position="true"';
        $coordinates  = '';
        if ((int) $x >= 0 && (int) $y >= 0) {
            $autoposition = "";
            $coordinates  = "gs-x='$x' gs-y='$y'";
        }

        $color    = $data_option['color'] ?? "#FFFFFF";
        $fg_color = Toolbox::getFgColor($color, 100, true);

       // add card options in data attribute
        $data_option_attr = "";
        if (count($data_option)) {
            $data_option_attr = "data-card-options='" . json_encode($data_option, JSON_HEX_APOS) . "'";
        }

        $refresh_label = __("Refresh this card");
        $edit_label    = __("Edit this card");
        $delete_label  = __("Delete this card");

        $gridstack_id = htmlspecialchars($gridstack_id);

        $this->items[] = <<<HTML
         <div class="grid-stack-item"
               gs-id="{$gridstack_id}"
               gs-w="{$width}"
               gs-h="{$height}"
               {$coordinates}
               {$autoposition}
               {$data_option_attr}
               style="color: {$fg_color}">
            <span class="controls">
               <i class="refresh-item ti ti-refresh" title="{$refresh_label}"></i>
               <i class="edit-item ti ti-edit" title="{$edit_label}"></i>
               <i class="delete-item ti ti-x" title="{$delete_label}"></i>
            </span>
            <div class="grid-stack-item-content">{$html}</div>
         </div>
HTML;
    }


    /**
     * Display a mini form fo adding a new dashboard
     *
     * @return void (display)
     */
    public function displayAddDashboardForm()
    {
        $rand = mt_rand();

        echo "<form class='no-shadow display-add-dashboard-form'>";

        echo "<div class='mb-3'>";
        echo "<label for='title_$rand'>" . __("Title") . "</label>";
        echo "<div>";
        echo Html::input('title', ['id' => "title_$rand"]);
        echo "</div>";
        echo "</div>"; // .field

        echo Html::submit(_x('button', "Add"), [
            'icon'  => 'fas fa-plus',
            'class' => 'btn btn-primary submit-new-dashboard'
        ]);

        echo "</form>"; // .card.display-widget-form
    }


    /**
     * Display mini configuration form to add or edit a widget
     *
     * @param array $params with these keys:
     * - int    'gridstack_id': unique identifier of the card
     * - int    'x': position in the grid
     * - int    'y: position in the grid
     * - int    'width': size in the grid
     * - int    'height': size in the grid
     * - string 'rand': unique identifier for the dom
     * - string 'action': [display_add_widget|display_edit_widget] current action for the form
     * - array  'card_options': aditionnal options for the card, contains at least:
     *     - string 'card_id': identifier return by @see self::getAllDasboardCards
     *     - string 'color'
     *
     * @return void
     */
    public function displayWidgetForm(array $params = [])
    {
        $gridstack_id = $params['gridstack_id'] ?? "";
        $old_id       = $gridstack_id;
        $x            = (int) ($params['x'] ?? 0);
        $y            = (int) ($params['y'] ?? 0);
        $width        = (int) ($params['width'] ?? 0);
        $height       = (int) ($params['height'] ?? 0);
        $cardopt      = $params['card_options'] ?? ['color' => "#FAFAFA"];
        $card_id      = $cardopt['card_id'] ?? "";
        $widgettypes  = Widget::getAllTypes();
        $widgettype   = $cardopt['widgettype'] ?? "";
        $widget_def   = $widgettypes[$widgettype] ?? [];
        $use_gradient = $cardopt['use_gradient'] ?? 0;
        $point_labels = $cardopt['point_labels'] ?? 0;
        $limit        = $cardopt['limit'] ?? 7;
        $color        = $cardopt['color'];
        $edit         = $params['action'] === "display_edit_widget";
        $cards        = $this->getAllDasboardCards();
        $card         = $cards[$card_id] ?? [];
       // append card id to options
        if (!isset($cardopt['card_id'])) {
            $cardopt['card_id'] = $card_id;
        }

        $list_cards = [];
        array_walk($cards, function ($data, $index) use (&$list_cards) {
            $group = $data['group'] ?? __("others");
            $list_cards[$group][$index] = $data['label'] ?? $data['itemtype']::getTypeName();
        });

       // manage autoescaping
        if (isset($cardopt['markdown_content'])) {
            $cardopt['markdown_content'] = Html::cleanPostForTextArea($cardopt['markdown_content']);
        }

        TemplateRenderer::getInstance()->display('components/dashboard/widget_form.html.twig', [
            'gridstack_id' => $gridstack_id,
            'old_id'       => $old_id,
            'x'            => $x,
            'y'            => $y,
            'width'        => $width,
            'height'       => $height,
            'edit'         => $edit,
            'card'         => $card,
            'widget_def'   => $widget_def,
            'color'        => $color,
            'card_id'      => $card_id,
            'use_gradient' => $use_gradient,
            'point_labels' => $point_labels,
            'limit'        => $limit,
            'list_cards'   => $list_cards,
            'widget_types' => Widget::getAllTypes(),
            'widgettype'   => $widgettype,
            'card_options' => $cardopt,
        ]);
    }


    /**
     * Display mini form to add filter to the current dashboard
     *
     * @param array $params default values for
     * - 'used' already used filters
     *
     * @return void
     */
    public function displayFilterForm(array $params = [])
    {
        $default_params = [
            'used'  => [],
        ];
        $params = array_merge($default_params, $params);

        $used         = array_flip($params['used']);
        $filters      = Filter::getFilterChoices();
        $list_filters = array_diff_key($filters, $used);

        $rand = mt_rand();
        echo "<form class='display-filter-form'>";

        echo "<div class='field'>";
        echo "<label for='dropdown_card_id$rand'>" . __("Filters") . "</label>";
        echo "<div>";
        Dropdown::showFromArray('filter_id', $list_filters, [
            'display_emptychoice' => true,
            'rand'                => $rand,
        ]);
        echo "</div>";
        echo "</div>"; // .field

        echo Html::submit("<i class='fas fa-plus'></i>&nbsp;" . _x('button', "Add"), [
            'class' => 'btn btn-primary mt-2'
        ]);
        echo "</form>"; // form.card.display-filter-form
    }


    /**
     * Display a mini form for embedding current dashboard in another application.
     * Also, display a select for sharing current dashboard to another users/groups/entities/profiles
     *
     * @return void
     */
    public function displayEmbedForm()
    {
        global $CFG_GLPI;

        $entities_id  = $_SESSION['glpiactive_entity'];
        $is_recursive = $_SESSION['glpiactive_entity_recursive'];
        $token        = self::getToken($this->current, $entities_id, $is_recursive);

        $embed_url    = $CFG_GLPI['url_base'] .
         "/front/central.php?embed&dashboard=" . $this->current .
         "&entities_id=$entities_id" .
         "&is_recursive=$is_recursive" .
         "&token=$token";

        echo "<label>" . __("Embed in another application") . "</label><br>";
        echo "<fieldset class='embed_block'>";
        echo __("Direct link");
        echo "<div class='copy_to_clipboard_wrapper'>";
        echo Html::input('direct_link', [
            'value' => $embed_url,
        ]);
        echo "</div><br>";

        $iframe = "<iframe src='$embed_url' frameborder='0' width='800' height='600' allowtransparency></iframe>";
        echo __("Iframe");
        echo "<div class='copy_to_clipboard_wrapper'>";
        echo Html::input('iframe_code', [
            'value' => $iframe,
        ]);
        echo "</div>";
        echo "</fieldset><br>";

        $this->displayEditRightsForm();
    }


    /**
     * Display a mini form for sharing current dashboard to another users/groups/entities/profiles.
     *
     * @return void
     */
    public function displayEditRightsForm()
    {
        self::loadAllDashboards();
        $rand   = mt_rand();
        $values = [];

        echo "<form class='no-shadow display-rights-form'>";

        echo "<label for='dropdown_rights_id$rand'>" .
           __("Or share the dashboard to these target objects:") .
           "</label><br>";

        $values = [
            'profiles_id' => self::$all_dashboards[$this->current]['rights']['profiles_id'] ?? [],
            'entities_id' => self::$all_dashboards[$this->current]['rights']['entities_id'] ?? [],
            'users_id'    => self::$all_dashboards[$this->current]['rights']['users_id'] ?? [],
            'groups_id'   => self::$all_dashboards[$this->current]['rights']['groups_id'] ?? [],
        ];

        echo ShareDashboardDropdown::show($rand, $values);
        echo "<br>";

        echo "<div class='d-flex align-items-center my-3'>";
        echo __('Personal') . "&nbsp;";
        echo Html::showToolTip(__("A personal dashboard is not visible by other administrators unless you explicitly share the dashboard")) . "&nbsp";
        echo Dropdown::showYesNo(
            'is_private',
            (self::$all_dashboards[$this->current]['users_id'] == '0' ? '0' : '1'),
            -1,
            [
                'display' => false
            ]
        );
        echo "</div>";

        echo "<a href='#' class='btn btn-primary save_rights'>
         <i class='far fa-save'></i>
         <span>" . __("Save") . "</span>
      </a>";

        Html::closeForm(true);
    }


    /**
     * Return the html for the given card_id
     *
     * @param string $card_id identifier return by @see self::getAllDasboardCards
     * @param array $card_options contains these keys:
     * - array 'args':
     *    - string 'gridstack_id' unique identifier of the card in the grid, used to return html by cache
     *    - bool 'force' if true, cache will be bypassed
     *    - bool 'embed' is the dashboard emebeded or not
     *
     * @return string html of the card
     */
    public function getCardHtml(string $card_id = "", array $card_options = []): string
    {
        global $GLPI_CACHE;

        $gridstack_id = $card_options['args']['gridstack_id'] ?? $card_id;
        $dashboard    = $card_options['dashboard'] ?? "";

        $force = ($card_options['args']['force'] ?? $card_options['force'] ?? false);

       // retrieve card
        $notfound_html = "<div class='empty-card card-warning '>
         <i class='fas fa-exclamation-triangle'></i>" .
         __('empty card!') . "
      </div>";
        $render_error_html = "<div class='empty-card card-error '>
         <i class='fas fa-exclamation-triangle'></i>" .
         __('Error rendering card!') .
            "</br>" .
            $card_id .
            "</div>";

        $start = microtime(true);
        try {
            $cards = $this->getAllDasboardCards();
            if (!isset($cards[$card_id])) {
                return $notfound_html;
            }
            $card = $cards[$card_id];

            $use_cache = !$force
                && $_SESSION['glpi_use_mode'] != Session::DEBUG_MODE
                && (!isset($card['cache']) || $card['cache'] == true);
            $cache_age = 40;

            if ($use_cache) {
                // browser cache
                header_remove('Pragma');
                header('Cache-Control: public');
                header('Cache-Control: max-age=' . $cache_age);
                header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + $cache_age));
            }

            $html = "";

            // call provider to retrieve data
            if (isset($card['provider'])) {
                $provider_args = ($card['args'] ?? []) + [
                    'params' => [
                        'label' => $card['label'] ?? ""
                    ]
                ];
                if (isset($card_options['args']['apply_filters'])) {
                    $provider_args['params']['apply_filters'] = $card_options['args']['apply_filters'];
                }
                $widget_args = call_user_func_array($card['provider'], array_values($provider_args));
            }
            $widget_args = array_merge($widget_args ?? [], $card_options['args'] ?? []);

            // call widget function to construct html
            $all_widgets = Widget::getAllTypes();
            $widgettype = $card_options['args']['widgettype'] ?? "";
            $widgetfct = $all_widgets[$widgettype]['function'] ?? "";
            if (strlen($widgetfct)) {
                // clean urls in embed mode
                if (isset($card_options['embed']) && $card_options['embed']) {
                    unset($widget_args['url']);

                    if (isset($widget_args['data'])) {
                        $unset_url = function (&$array) use (&$unset_url) {
                            unset($array['url']);
                            foreach ($array as &$value) {
                                if (is_array($value)) {
                                    $unset_url($value, 'url');
                                }
                            }
                        };
                        $unset_url($widget_args['data']);
                    }
                }

                if (isset($card['filters'])) {
                    $widget_args['filters'] = $card['filters'];
                }

                // call widget function
                $html = call_user_func($widgetfct, $widget_args);
            }

            // display a warning for empty card
            if (strlen($html) === 0) {
                return $notfound_html;
            }

            $execution_time = round(microtime(true) - $start, 3);
        } catch (\Throwable $e) {
            $html = $render_error_html;
            $execution_time = round(microtime(true) - $start, 3);
            // Log the error message without exiting
            global $GLPI;
            $GLPI->getErrorHandler()->handleException($e, true);
        }

        if ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE) {
            $html .= <<<HTML
         <span class='debug-card'>
            {$execution_time}s
         </span>
HTML;
        }

        return $html;
    }


    /**
     * Return Html for a provided set of filters
     * @param array $filter_names
     *
     * @return string the html
     */
    public function getFiltersSetHtml(array $filters = []): string
    {
        $html = "";

        foreach ($filters as $filter_id => $filter_values) {
            $html .= $this->getFilterHtml($filter_id, $filter_values);
        }

        return $html;
    }


    /**
     * Return Html for a provided filter name
     *
     * @param string $filter_id the system name of a filter (ex dates)
     * @param string|array $filter_values init the input with these values,
     *                     will be a string if empty values
     *
     * @return string the html
     */
    public function getFilterHtml(string $filter_id = "", $filter_values = ""): string
    {
        foreach (Filter::getRegisteredFilterClasses() as $filter) {
            if ($filter::getId() == $filter_id) {
                return $filter::getHtml($filter_values);
            }
        }

        return "";
    }


    /**
     * Return all itemtypes possible for constructing cards.
     * User in @see self::getAllDasboardCards
     *
     * @return array [itemtype1, itemtype2]
     */
    protected function getMenuItemtypes(): array
    {
        $menu_itemtypes = [];
        $exclude   = [
            'Config',
        ];

        $menu = Html::getMenuInfos();
        array_walk($menu, static function ($firstlvl) use (&$menu_itemtypes) {
            $key = $firstlvl['title'];
            if (isset($firstlvl['types'])) {
                  $menu_itemtypes[$key] = array_merge($menu_itemtypes[$key] ?? [], $firstlvl['types']);
            }
        });

        foreach ($menu_itemtypes as &$firstlvl) {
            $firstlvl = array_filter($firstlvl, static function ($itemtype) use ($exclude) {
                if (
                    in_array($itemtype, $exclude)
                    || !is_subclass_of($itemtype, 'CommonDBTM')
                ) {
                      return false;
                }

                $testClass = new \ReflectionClass($itemtype);
                return !$testClass->isAbstract();
            });
        }

        return $menu_itemtypes;
    }

    /**
     * Get cache key for the "getAllDasboardCards" function data.
     * The data will contain some translated strings and thus must be kept in a
     * separate cache entry for each languages
     *
     * @return string
     */
    public static function getAllDashboardCardsCacheKey(?string $language = null): string
    {
        if ($language === null) {
            $language = Session::getLanguage() ?? '';
        }

        return sprintf(
            'getAllDashboardCards_%s_%s',
            sha1(json_encode(Filter::getRegisteredFilterClasses())),
            $language
        );
    }

    /**
     * Construct catalog of all possible cards addable in a dashboard.
     *
     * @param bool $force Force rebuild the catalog of cards
     *
     * @return array
     */
    public function getAllDasboardCards($force = false): array
    {
        global $CFG_GLPI, $GLPI_CACHE;

        $cards = $GLPI_CACHE->get(self::getAllDashboardCardsCacheKey());

        if ($cards === null || $force) {
            $cards = [];
            $menu_itemtypes = $this->getMenuItemtypes();

            foreach ($menu_itemtypes as $firstlvl => $itemtypes) {
                foreach ($itemtypes as $itemtype) {
                    $clean_itemtype = str_replace('\\', '_', $itemtype);
                    $cards["bn_count_$clean_itemtype"] = [
                        'widgettype' => ["bigNumber"],
                        'group'      => $firstlvl,
                        'itemtype'   => "\\$itemtype",
                        'label'      => sprintf(__("Number of %s"), $itemtype::getTypeName()),
                        'provider'   => "Glpi\\Dashboard\\Provider::bigNumber$itemtype",
                        'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                    ];
                }
            }

            foreach ($CFG_GLPI['itemdevices'] as $itemtype) {
                $fk_itemtype = $itemtype::getDeviceType();
                $label = sprintf(
                    __("Number of %s by type"),
                    $itemtype::getTypeName(Session::getPluralNumber()),
                    $fk_itemtype::getFieldLabel()
                );

                $cards["count_" . $itemtype . "_" . $fk_itemtype] = [
                    'widgettype' => ['summaryNumbers', 'multipleNumber', 'pie', 'donut', 'halfpie', 'halfdonut', 'bar', 'hbar'],
                    'itemtype'   => "\\$itemtype",
                    'group'      =>  _n('Device', 'Devices', 1),
                    'label'      => $label,
                    'provider'   => "Glpi\\Dashboard\\Provider::multipleNumber" . $itemtype . "By" . $fk_itemtype,
                    'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                ];

                $clean_itemtype = str_replace('\\', '_', $itemtype);
                $cards["bn_count_$clean_itemtype"] = [
                    'widgettype' => ["bigNumber"],
                    'group'      => _n('Device', 'Devices', 1),
                    'itemtype'   => "\\$itemtype",
                    'label'      => sprintf(__("Number of %s"), $itemtype::getTypeName()),
                    'provider'   => "Glpi\\Dashboard\\Provider::bigNumber$itemtype",
                    'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                ];
            }

            foreach ($CFG_GLPI['device_types'] as $itemtype) {
                $clean_itemtype = str_replace('\\', '_', $itemtype);
                $cards["bn_count_$clean_itemtype"] = [
                    'widgettype' => ["bigNumber"],
                    'group'      => _n('Device', 'Devices', 1),
                    'itemtype'   => "\\$itemtype",
                    'label'      => sprintf(__("Number of type of %s"), $itemtype::getTypeName()),
                    'provider'   => "Glpi\\Dashboard\\Provider::bigNumber$itemtype",
                    'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                ];
            }

            // add multiple width for Assets itemtypes grouped by their foreign keys
            $assets = array_merge($CFG_GLPI['asset_types'], ['Software']);
            foreach ($assets as $itemtype) {
                $fk_itemtypes = [
                    'State',
                    'Entity',
                    'Manufacturer',
                    'Location',
                ];

                if (class_exists($itemtype . 'Type')) {
                    $fk_itemtypes[] = $itemtype . 'Type';
                }
                if (class_exists($itemtype . 'Model')) {
                    $fk_itemtypes[] = $itemtype . 'Model';
                }

                foreach ($fk_itemtypes as $fk_itemtype) {
                    $label = sprintf(
                        __("%s by %s"),
                        $itemtype::getTypeName(Session::getPluralNumber()),
                        $fk_itemtype::getFieldLabel()
                    );

                    $cards["count_" . $itemtype . "_" . $fk_itemtype] = [
                        'widgettype' => ['summaryNumbers', 'multipleNumber', 'pie', 'donut', 'halfpie', 'halfdonut', 'bar', 'hbar'],
                        'itemtype'   => "\\Computer",
                        'group'      => _n('Asset', 'Assets', Session::getPluralNumber()),
                        'label'      => $label,
                        'provider'   => "Glpi\\Dashboard\\Provider::multipleNumber" . $itemtype . "By" . $fk_itemtype,
                        'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                    ];
                }
            }

            $tickets_cases = [
                'late'               => __("Late tickets"),
                'waiting_validation' => __("Tickets waiting for your approval"),
                'notold'             => __('Not solved tickets'),
                'incoming'           => __("New tickets"),
                'waiting'            => __('Pending tickets'),
                'assigned'           => __('Assigned tickets'),
                'planned'            => __('Planned tickets'),
                'solved'             => __('Solved tickets'),
                'closed'             => __('Closed tickets'),
            ];
            foreach ($tickets_cases as $case => $label) {
                $cards["bn_count_tickets_$case"] = [
                    'widgettype' => ["bigNumber"],
                    'itemtype'   => "\\Ticket",
                    'group'      => __('Assistance'),
                    'label'      => sprintf(__("Number of %s"), $label),
                    'provider'   => "Glpi\\Dashboard\\Provider::nbTicketsGeneric",
                    'args'       => [
                        'case'   => $case,
                        'params' => [
                            'validation_check_user' => true,
                        ]
                    ],
                    'cache'      => false,
                    'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                ];

                $cards["table_count_tickets_$case"] = [
                    'widgettype' => ["searchShowList"],
                    'itemtype'   => "\\Ticket",
                    'group'      => __('Assistance'),
                    'label'      => sprintf(__("List of %s"), $label),
                    'provider'   => "Glpi\\Dashboard\\Provider::nbTicketsGeneric",
                    'args'       => [
                        'case'   => $case,
                        'params' => [
                            'validation_check_user' => true,
                        ]
                    ],
                    'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                ];
            }

            // add specific ticket's cases
            $cards["nb_opened_ticket"] = [
                'widgettype' => ['line', 'area', 'bar'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => __("Number of tickets by month"),
                'provider'   => "Glpi\\Dashboard\\Provider::ticketsOpened",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            $cards["ticket_evolution"] = [
                'widgettype' => ['lines', 'areas', 'bars', 'stackedbars'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => __("Evolution of ticket in the past year"),
                'provider'   => "Glpi\\Dashboard\\Provider::getTicketsEvolution",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            $cards["ticket_status"] = [
                'widgettype' => ['lines', 'areas', 'bars', 'stackedbars'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => __("Tickets status by month"),
                'provider'   => "Glpi\\Dashboard\\Provider::getTicketsStatus",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            $cards["ticket_times"] = [
                'widgettype' => ['lines', 'areas', 'bars', 'stackedbars'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => __("Tickets times (in hours)"),
                'provider'   => "Glpi\\Dashboard\\Provider::averageTicketTimes",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            $cards["tickets_summary"] = [
                'widgettype' => ['summaryNumbers', 'multipleNumber', 'bar', 'hbar'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => __("Tickets summary"),
                'provider'   => "Glpi\\Dashboard\\Provider::getTicketSummary",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            $case = '';
            $cards["bn_count_tickets_expired_by_tech"] = [
                'widgettype' => ['hBars', 'stackedHBars'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => sprintf(__("Number of tickets by SLA status and technician")),
                'provider'   => "Glpi\\Dashboard\\Provider::nbTicketsByAgreementStatusAndTechnician",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            $cards["bn_count_tickets_expired_by_tech_group"] = [
                'widgettype' => ['hBars', 'stackedHBars'],
                'itemtype'   => "\\Ticket",
                'group'      => __('Assistance'),
                'label'      => sprintf(__("Number of tickets by SLA status and technician group")),
                'provider'   => "Glpi\\Dashboard\\Provider::nbTicketsByAgreementStatusAndTechnicianGroup",
                'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
            ];

            foreach (
                [
                    'ITILCategory' => __("Top ticket's categories"),
                    'Entity'       => __("Top ticket's entities"),
                    'RequestType'  => __("Top ticket's request types"),
                    'Location'     => __("Top ticket's locations"),
                ] as $itemtype => $label
            ) {
                $cards["top_ticket_$itemtype"] = [
                    'widgettype' => ['summaryNumbers', 'pie', 'donut', 'halfpie', 'halfdonut', 'multipleNumber', 'bar', 'hbar'],
                    'itemtype'   => "\\Ticket",
                    'group'      => __('Assistance'),
                    'label'      => $label,
                    'provider'   => "Glpi\\Dashboard\\Provider::multipleNumberTicketBy$itemtype",
                    'filters'    => Filter::getAppliableFilters($itemtype::getTable()),
                ];
            }

            foreach (
                [
                    'user_requester'  => __("Top ticket's requesters"),
                    'group_requester' => __("Top ticket's requester groups"),
                    'user_observer'   => __("Top ticket's observers"),
                    'group_observer'  => __("Top ticket's observer groups"),
                    'user_assign'     => __("Top ticket's assignees"),
                    'group_assign'    => __("Top ticket's assignee groups"),
                ] as $type => $label
            ) {
                $cards["top_ticket_$type"] = [
                    'widgettype' => ['pie', 'donut', 'halfpie', 'halfdonut', 'summaryNumbers', 'multipleNumber', 'bar', 'hbar'],
                    'itemtype'   => "\\Ticket",
                    'group'      => __('Assistance'),
                    'label'      => $label,
                    'provider'   => "Glpi\\Dashboard\\Provider::nbTicketsActor",
                    'args'       => [
                        'case' => $type,
                    ],
                    'filters'    => Filter::getAppliableFilters(Ticket::getTable()),
                ];
            }

            $cards["RemindersList"] = [
                'widgettype' => ["articleList"],
                'label'      => __("List of reminders"),
                'group'      => __('Tools'),
                'provider'   => "Glpi\\Dashboard\\Provider::getArticleListReminder",
                'filters'    => Filter::getAppliableFilters(Reminder::getTable()),
            ];

            $cards["markdown_editable"] = [
                'widgettype'   => ["markdown"],
                'label'        => __("Editable markdown card"),
                'group'        => __('Others'),
                'card_options' => [
                    'content' => __("Toggle edit mode to edit content"),
                ]
            ];
            $GLPI_CACHE->set(self::getAllDashboardCardsCacheKey(), $cards);
        }

        $more_cards = Plugin::doHookFunction(Hooks::DASHBOARD_CARDS);
        if (is_array($more_cards)) {
            $cards = array_merge($cards, $more_cards);
        }

        return $cards;
    }


    public function getRights($interface = 'central')
    {
        return [
            READ   => __('Read'),
            UPDATE => __('Update'),
            CREATE => __('Create'),
            PURGE  => [
                'short' => __('Purge'),
                'long'  => _x('button', 'Delete permanently')
            ]
        ];
    }


    /**
     * Save last dashboard viewed
     *
     * @param string $page current page
     * @param string $dashboard current dashboard
     *
     * @return void
     */
    public function setLastDashboard(string $page = "", string $dashboard = "")
    {
        $_SESSION['last_dashboards'][$page] = $dashboard;
    }


    /**
     * Restore last viewed dashboard
     *
     * @return string the dashboard key
     */
    public function restoreLastDashboard(): string
    {
        global $CFG_GLPI;
        $new_key = "";
        $target = Toolbox::cleanTarget($_REQUEST['_target'] ?? $_SERVER['REQUEST_URI'] ?? "");
        if (isset($_SESSION['last_dashboards']) && strlen($target) > 0) {
            $target = preg_replace('/^' . preg_quote($CFG_GLPI['root_doc'], '/') . '/', '', $target);
            if (!isset($_SESSION['last_dashboards'][$target])) {
                return "";
            }

            $new_key   = $_SESSION['last_dashboards'][$target];
            $dashboard = new Dashboard($new_key);
            if (!$dashboard->canViewCurrent()) {
                return "";
            }

            $this->current = $new_key;
        }

        return $new_key;
    }


    /**
     * Retrieve the default dashboard for a specific menu entry
     * First try from session
     * then on config
     * And Fallback on the first dashboard found
     *
     * @param string $menu
     * @param bool $strict if true, do not provide a fallback
     *
     * @return string the dashboard key
     */
    public static function getDefaultDashboardForMenu(string $menu = "", bool $strict = false): string
    {
        global $CFG_GLPI;

        $grid = new self();

        if (!$strict) {
            $restored = $grid->restoreLastDashboard();
            if (strlen($restored) > 0) {
                return $restored;
            }
        }

        // Try loading default from user preferences
        $config_key = 'default_dashboard_' . $menu;
        $default    = $_SESSION["glpi$config_key"] ?? "";
        if (strlen($default)) {
            $dasboard = new Dashboard($default);

            if ($dasboard->load() && $dasboard->canViewCurrent()) {
                return $default;
            }
        }

        // Try loading default from config
        $default = $CFG_GLPI[$config_key] ?? "";
        if (strlen($default)) {
            $dasboard = new Dashboard($default);

            if ($dasboard->load() && $dasboard->canViewCurrent()) {
                return $default;
            }
        }

        // if default not found, return first dashboard
        if (!$strict) {
            self::loadAllDashboards();
            $first_dashboard = array_shift(self::$all_dashboards);
            if (isset($first_dashboard['key'])) {
                return $first_dashboard['key'];
            }
        }

        return "";
    }


    public static function dropdownDashboard(string $name = "", array $params = []): string
    {
        $to_show = Dashboard::getAll(false, true, $params['context'] ?? 'core');
        $can_view_all = $params['can_view_all'] ?? false;

        $options_dashboards = [];
        foreach ($to_show as $key => $dashboard) {
            if (self::canViewSpecificicDashboard($key, $can_view_all)) {
                $options_dashboards[$key] = $dashboard['name'] ?? $key;
            }
        }

        return Dropdown::showFromArray($name, $options_dashboards, $params);
    }
}
			
			


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