From 4daa6003a31cd8bc2b173ac7058f1091f1f798aa Mon Sep 17 00:00:00 2001 From: Alexandre Delaunay Date: Wed, 4 Dec 2024 11:36:47 +0100 Subject: [PATCH] add location of items card for dashboard --- public/pics/charts/map.png | Bin 0 -> 5644 bytes src/Glpi/Dashboard/Grid.php | 7 ++ src/Glpi/Dashboard/Provider.php | 71 +++++++++++++++++++++ src/Glpi/Dashboard/Widget.php | 109 ++++++++++++++++++++++++++++++-- 4 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 public/pics/charts/map.png diff --git a/public/pics/charts/map.png b/public/pics/charts/map.png new file mode 100644 index 0000000000000000000000000000000000000000..91f55d8396d64babc0a55124a3fddf2a08471a27 GIT binary patch literal 5644 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 bqG|Ws1s|H;z-RGa0AsT>wKXA~N5%dRE2VHT literal 0 HcmV?d00001 diff --git a/src/Glpi/Dashboard/Grid.php b/src/Glpi/Dashboard/Grid.php index a850e7cfde1..eddd21ee5d7 100644 --- a/src/Glpi/Dashboard/Grid.php +++ b/src/Glpi/Dashboard/Grid.php @@ -1430,6 +1430,13 @@ public function getAllDasboardCards($force = false): array ] ]; + $cards["items_map"] = [ + 'widgettype' => ["map"], + 'label' => __("Location of items"), + 'group' => __('Others'), + 'provider' => "Glpi\\Dashboard\\Provider::getItemsCoordinates", + ]; + if (GLPI_ENVIRONMENT_TYPE !== GLPI::ENV_DEVELOPMENT) { // Do not cache dashboard cards on `development` envs $GLPI_CACHE->set(self::getAllDashboardCardsCacheKey(), $cards); diff --git a/src/Glpi/Dashboard/Provider.php b/src/Glpi/Dashboard/Provider.php index fe74c298f3c..fb93d9b6b91 100644 --- a/src/Glpi/Dashboard/Provider.php +++ b/src/Glpi/Dashboard/Provider.php @@ -58,6 +58,7 @@ use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QueryFunction; use Glpi\DBAL\QuerySubQuery; +use Location; use Session; use Stat; use Ticket; @@ -1702,6 +1703,76 @@ public static function getTicketSummary(array $params = []) } + public static function getItemsCoordinates(array $params = []): array + { + global $CFG_GLPI; + + $DB = DBConnection::getReadConnection(); + $default_params = [ + 'label' => "", + 'icon' => "", + 'apply_filters' => [], + 'limit' => 5000, + ]; + $params = array_merge($default_params, $params); + + $data = []; + $nb_added = 0; + foreach ($CFG_GLPI['location_types'] as $itemtype) { + if (!$DB->fieldExists($itemtype::getTable(), 'locations_id') || !$DB->fieldExists($itemtype::getTable(), 'name')) { + continue; + } + + $i_table = $itemtype::getTable(); + $l_table = Location::getTable(); + $iterator = $DB->request([ + 'SELECT' => [ + "$i_table.id", + "$i_table.name", + "$l_table.latitude", + "$l_table.longitude", + ], + 'FROM' => $l_table, + 'INNER JOIN' => [ + $i_table => [ + 'ON' => [ + $i_table => getForeignKeyFieldForItemType(Location::class), + $l_table => 'id', + ] + ], + ], + 'WHERE' => [ + 'latitude' => ['!=', ''], + 'longitude' => ['!=', ''], + ], + ]); + + foreach ($iterator as $result) { + if ($nb_added >= $params['limit']) { + break 2; + } + $data[] = [ + 'id' => $result['id'], + 'type' => $itemtype::getTypeName(1), + 'url' => $itemtype::getFormURLWithID($result['id']), + 'icon' => $itemtype::getIcon(), + 'name' => $result['name'], + 'latitude' => $result['latitude'], + 'longitude' => $result['longitude'], + ]; + + $nb_added++; + } + } + + return [ + 'data' => $data, + 'label' => $params['label'], + 'icon' => $params['icon'], + ]; + } + + public static function formatMonthyearDates(string $monthyear): array { $rawdate = explode('-', $monthyear); diff --git a/src/Glpi/Dashboard/Widget.php b/src/Glpi/Dashboard/Widget.php index 4306e6c4707..74ae2a12ebc 100644 --- a/src/Glpi/Dashboard/Widget.php +++ b/src/Glpi/Dashboard/Widget.php @@ -43,10 +43,6 @@ use Laminas\Json\Expr as Json_Expr; use Laminas\Json\Json; use Mexitek\PHPColors\Color; -use League\CommonMark\Environment\Environment; -use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; -use League\CommonMark\MarkdownConverter; -use League\CommonMark\Extension\GithubFlavoredMarkdownExtension; use Plugin; use Symfony\Component\DomCrawler\Crawler; use Search; @@ -288,6 +284,13 @@ public static function getAllTypes(): array 'width' => 3, 'height' => 4, ], + 'map' => [ + 'label' => __("Map"), + 'function' => 'Glpi\\Dashboard\\Widget::map', + 'image' => $CFG_GLPI['root_doc'] . '/pics/charts/map.png', + 'width' => 4, + 'height' => 4, + ] ]; $more_types = Plugin::doHookFunction(Hooks::DASHBOARD_TYPES); @@ -1849,6 +1852,104 @@ public static function articleList(array $params): string } + public static function map(array $params): string + { + $default = [ + 'data' => [], + 'label' => '', + 'alt' => '', + 'url' => '', + 'color' => '', + 'icon' => '', + 'limit' => 99999, + 'class' => "map", + 'rand' => mt_rand(), + 'filters' => [], + ]; + $p = array_merge($default, $params); + + $color = new Color($p['color']); + $is_light = $color->isLight(); + + $fg_color = Toolbox::getFgColor($p['color'], $is_light ? 65 : 40); + $id = "map-" . $p['rand']; + $class = $p['class']; + $class .= count($p['filters']) > 0 ? " filter-" . implode(' filter-', $p['filters']) : ""; + + $points = json_encode($p['data'] ?? []); + + $html = << +
+ {$p['label']} + + + HTML; + $js = << { + const markers = cluster.getAllChildMarkers(); + let n = markers.length; + + let c = ' marker-cluster-'; + if (n < 10) { + c += 'small'; + } else if (n < 100) { + c += 'medium'; + } else { + c += 'large'; + } + + return new L.DivIcon({ + html: '
' + n + '
', + className: 'marker-cluster' + c, + iconSize: new L.Point(40, 40) + }); + } + }); + + const points = {$points}; + $.each(points, (index, point) => { + const _icon = L.divIcon({ + 'className': 'badge bg-blue-lt', + 'iconSize': [24, 20], + 'html': '', + }); + const _title = ` + \${point.type} - \${point.name} + `; + + const _marker = L.marker([point.latitude, point.longitude], { + icon: _icon, + title: point.name + }); + _marker.count = point.count; + _marker.bindPopup(_title); + _markers.addLayer(_marker); + + }); + + map.addLayer(_markers); + map.fitBounds( + _markers.getBounds(), { + padding: [50, 50], + maxZoom: 12 + } + ); + }); + JAVASCRIPT; + $js = \Html::scriptBlock($js); + + return $html . $js; + } + + public static function getPalette(string $palette_name, int $nb_series = 0): array { $palette_obj = new Palette($palette_name);