From 90b0beef950a4b727b9364d1c3d6e772c65c024c Mon Sep 17 00:00:00 2001 From: whamulti <80924754+whamulti@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:55:47 -0300 Subject: [PATCH 1/3] Fix GLPI 11 compatibility with current Metabase API versions Several API response fields the plugin reads under their old names were renamed by Metabase, and the newer native-question query format ("MBQL 5") wasn't handled at all: - ordered_cards -> dashcards on GET /api/dashboard/:id - sizeX/sizeY -> size_x/size_y on dashboard cards - dataset_query.native.{query,template-tags} moved to dataset_query.stages[0].{native,template-tags} - getCards('root') never matched cards in the root collection, since the API returns id:"root" for that collection but collection_id:null on the cards themselves All fixed with backward-compatible `??` fallbacks so older Metabase instances keep working. Also fixes a more severe regression introduced in 1.4.2: embedded_token was added to secured_configs and is expected to be sodium-encrypted, but the upgrade migration only flipped the is_embedded_token_encrypted flag without ever actually encrypting the existing plain-text value. dashboard.class.php unconditionally decrypts embedded_token before signing the dashboard JWT, so any site that already had a token configured before upgrading got an empty signing key and an uncaught Lcobucci\JWT\Signer\InvalidKeyProvided exception on every visit to the embedded dashboard tab. The migration now actually encrypts the value, mirroring what the password migration a few lines above already does. Fixes #148 --- inc/apiclient.class.php | 9 +++++++-- inc/config.class.php | 35 ++++++++++++++++++++++++----------- public/metabase.js | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/inc/apiclient.class.php b/inc/apiclient.class.php index 6675668..4ca7611 100644 --- a/inc/apiclient.class.php +++ b/inc/apiclient.class.php @@ -532,7 +532,8 @@ public function getDashboardCards($id) { $data = $this->httpQuery("dashboard/$id", [], 'GET'); - return $data['ordered_cards'] ?? false; + // Metabase renamed 'ordered_cards' to 'dashcards' in newer API versions. + return $data['dashcards'] ?? $data['ordered_cards'] ?? false; } public function createOrUpdateCard($card_name, $params = []) @@ -657,11 +658,15 @@ public function getCards($collection_id = null) return $cards; } + // Metabase's API returns id:"root" for the root collection, but cards + // in it have collection_id: null - normalize before comparing. + $normalized_id = $collection_id === 'root' ? null : $collection_id; + $cards = array_filter( $cards, fn($card) => is_array($card) && array_key_exists('collection_id', $card) - && $collection_id === $card['collection_id'], + && $normalized_id === $card['collection_id'], ); return $cards; diff --git a/inc/config.class.php b/inc/config.class.php index 8ac029f..3cb74be 100644 --- a/inc/config.class.php +++ b/inc/config.class.php @@ -528,10 +528,16 @@ public static function displayQuestionJson($question_id) 'display' => $card['display'], 'visualization_settings' => $card['visualization_settings'], 'template_tags' => [], - 'sql' => $card['dataset_query']['native']['query'], + // Metabase MBQL 5 moved the native query out of + // dataset_query.native.query into dataset_query.stages[0].native. + 'sql' => $card['dataset_query']['native']['query'] + ?? $card['dataset_query']['stages'][0]['native'], ]; - foreach ($card['dataset_query']['native']['template-tags'] as $tag_name => $tag) { + $template_tags = $card['dataset_query']['native']['template-tags'] + ?? $card['dataset_query']['stages'][0]['template-tags'] + ?? []; + foreach ($template_tags as $tag_name => $tag) { $extract['template_tags'][$tag_name] = [ 'type' => $tag['type'], 'display_name' => $tag['display-name'], @@ -582,13 +588,21 @@ public static function displayDashboardJson($dashboard_id) $parameters_id[$parameter['id']] = $parameter['slug']; } - foreach ($dashboard['ordered_cards'] as $card) { + // Metabase renamed 'ordered_cards' to 'dashcards' in newer API versions. + $dashcards = $dashboard['dashcards'] ?? $dashboard['ordered_cards'] ?? []; + foreach ($dashcards as $card) { + // MBQL 5 replaced the top-level dataset_query.type === 'native' + // flag with a dataset_query.stages[0].native structure. + $is_native = isset($card['card']['dataset_query']['type']) + ? $card['card']['dataset_query']['type'] === 'native' + : isset($card['card']['dataset_query']['stages'][0]['native']); + if ( isset($card['card_id']) // only question (TODO support markdown cards) - && $card['card']['dataset_query']['type'] === 'native' + && $is_native ) { // only native questions $key = null; - foreach ($_SESSION['metabase']['reports'] as $session_key => $session_report) { + foreach ($_SESSION['metabase']['reports'] ?? [] as $session_key => $session_report) { if ($session_report['title'] === $card['card']['name']) { $key = $session_key; } @@ -598,15 +612,14 @@ public static function displayDashboardJson($dashboard_id) $extract['reports'][$key] = [ 'col' => $card['col'], 'row' => $card['row'], - 'sizeX' => $card['sizeX'], - 'sizeY' => $card['sizeY'], + 'sizeX' => $card['sizeX'] ?? $card['size_x'], + 'sizeY' => $card['sizeY'] ?? $card['size_y'], ]; foreach ($card['parameter_mappings'] as $mapping) { $mapping_key = $mapping['target'][1][1]; - $field_id = $card['card']['dataset_query'] - ['native']['template-tags'] - [$mapping_key]['dimension'][1]; + $field_id = $card['card']['dataset_query']['native']['template-tags'][$mapping_key]['dimension'][1] + ?? $card['card']['dataset_query']['stages'][0]['template-tags'][$mapping_key]['dimension'][1]; $field_name = array_search($field_id, $_SESSION['metabase']['fields']); if ($field_name !== false) { $extract['reports'][$key]['parameter_mappings'] @@ -618,7 +631,6 @@ public static function displayDashboardJson($dashboard_id) } self::displayPrettyJson($extract); - Html::printCleanArray($dashboard); } public static function displayPrettyJson($array = []) @@ -809,6 +821,7 @@ public static function install(Migration $migration) // Encrypt embedded_token, previously stored in plain text if (!array_key_exists('is_embedded_token_encrypted', $current_config) || !$current_config['is_embedded_token_encrypted']) { if (!empty($current_config['embedded_token'])) { + $current_config['embedded_token'] = (new GLPIKey())->encrypt($current_config['embedded_token']); Config::setConfigurationValues( 'plugin:metabase', [ diff --git a/public/metabase.js b/public/metabase.js index c84232a..c75fabf 100644 --- a/public/metabase.js +++ b/public/metabase.js @@ -48,7 +48,7 @@ $(function() { var type = $(this).data('type'); glpi_ajax_dialog({ dialogclass: 'modal-lg', - url: CFG_GLPI.root_doc + '/' + GLPI_PLUGINS_PATH.metabase + '/ajax/extract_json.php', + url: CFG_GLPI.root_doc + GLPI_PLUGINS_PATH.metabase + '/ajax/extract_json.php', params: { id: id, type: type From 865fcc9be60191503fe4848298a947ff2abcb169 Mon Sep 17 00:00:00 2001 From: whamulti <80924754+whamulti@users.noreply.github.com> Date: Wed, 26 Aug 2026 08:10:37 -0300 Subject: [PATCH 2/3] Add CHANGELOG entry for GLPI 11 compatibility fixes --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 921fffc..4a197c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Fixed + +- Fix question/dashboard extraction against current Metabase API versions (`ordered_cards` → `dashcards`, `sizeX`/`sizeY` → `size_x`/`size_y`, native question query moved to `dataset_query.stages[0].*`) +- Fix root collection questions never matching in `getCards('root')` +- Fix extraction AJAX URL resolving to the wrong host when `root_doc` is empty +- Fix `embedded_token` migration not actually encrypting the value on upgrade, breaking the embedded dashboard with an `InvalidKeyProvided` exception for any site that had a token configured before updating to 1.4.2 + ## [1.4.2] - 2026-08-04 ### Fixed From 1ad169d72080b828d6ad4d1f6b0b4658f801bfe5 Mon Sep 17 00:00:00 2001 From: whamulti <80924754+whamulti@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:14:51 -0300 Subject: [PATCH 3/3] Simplify CHANGELOG entry per review suggestion Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a197c5..72e91b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- Fix question/dashboard extraction against current Metabase API versions (`ordered_cards` → `dashcards`, `sizeX`/`sizeY` → `size_x`/`size_y`, native question query moved to `dataset_query.stages[0].*`) -- Fix root collection questions never matching in `getCards('root')` -- Fix extraction AJAX URL resolving to the wrong host when `root_doc` is empty -- Fix `embedded_token` migration not actually encrypting the value on upgrade, breaking the embedded dashboard with an `InvalidKeyProvided` exception for any site that had a token configured before updating to 1.4.2 +- Fixed Metabase compatibility with current API versions +- Fix `embedded-token` migration for existing configurations. ## [1.4.2] - 2026-08-04