From c1fb7179e1d8fa5cef7dd08f5113d4a84ac2f48e Mon Sep 17 00:00:00 2001 From: Curtis Conard Date: Sun, 6 Apr 2025 06:37:15 -0400 Subject: [PATCH] allow choosing glpi type when importing or merging mobile devices --- CHANGELOG.md | 3 +++ ajax/import.php | 15 ++++++----- ajax/merge.php | 6 ++++- front/merge.php | 55 +++++++++++++++++++++++--------------- templates/import.html.twig | 26 +++++++++++++++--- templates/merge.html.twig | 35 +++++++++++++++++------- 6 files changed, 98 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 714326e..f3dc38f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [UNRELEASED] +### Added +- Added support to choose importing/merging mobile devices as Computers or Phones + ### Fixes - SQL error when merging the Jamf device linked to a GLPI asset diff --git a/ajax/import.php b/ajax/import.php index 8173f0e..a6b2bd3 100644 --- a/ajax/import.php +++ b/ajax/import.php @@ -56,21 +56,22 @@ if (isset($_REQUEST['import_ids']) && is_array($_REQUEST['import_ids'])) { // Get data for each item to import $toimport = $DB->request([ - 'SELECT' => ['type', 'jamf_type', 'jamf_items_id'], - 'FROM' => PluginJamfImport::getTable(), - 'WHERE' => [ - 'id' => $_REQUEST['import_ids'], - ], + 'SELECT' => ['id', 'type', 'jamf_type', 'jamf_items_id'], + 'FROM' => PluginJamfImport::getTable(), + 'WHERE' => [ + 'id' => $_REQUEST['import_ids'] + ] ]); // Trigger extension attribute definition sync PluginJamfMobileSync::syncExtensionAttributeDefinitions(); PluginJamfComputerSync::syncExtensionAttributeDefinitions(); // Import the requested device(s) foreach ($toimport as $data) { + $glpi_itemtype = $_REQUEST['itemtype_overrides'][$data['id']] ?? $data['type']; if ($data['jamf_type'] === 'MobileDevice') { - PluginJamfMobileSync::import($data['type'], $data['jamf_items_id']); + PluginJamfMobileSync::import($glpi_itemtype, $data['jamf_items_id']); } else { - PluginJamfComputerSync::import($data['type'], $data['jamf_items_id']); + PluginJamfComputerSync::import($glpi_itemtype, $data['jamf_items_id']); } } } else { diff --git a/ajax/merge.php b/ajax/merge.php index a465874..ca6d09a 100644 --- a/ajax/merge.php +++ b/ajax/merge.php @@ -54,6 +54,10 @@ // Trigger extension attribute definition sync PluginJamfMobileSync::syncExtensionAttributeDefinitions(); PluginJamfComputerSync::syncExtensionAttributeDefinitions(); + $supported_glpi_types = [ + 'Computer' => PluginJamfComputerSync::getSupportedGlpiItemtypes(), + 'MobileDevice' => PluginJamfMobileSync::getSupportedGlpiItemtypes() + ]; // An array of item IDs is required if (isset($_REQUEST['item_ids']) && is_array($_REQUEST['item_ids'])) { $failures = 0; @@ -65,7 +69,7 @@ $jamf_id = $data['jamf_id']; $itemtype = $data['itemtype']; - if (($itemtype !== 'Computer') && ($itemtype !== 'Phone')) { + if (!in_array($itemtype, $supported_glpi_types[$data['jamf_type']])) { // Invalid itemtype for a mobile device throw new RuntimeException('Invalid itemtype!'); } diff --git a/front/merge.php b/front/merge.php index 1b3ed93..3b0e580 100644 --- a/front/merge.php +++ b/front/merge.php @@ -64,35 +64,46 @@ $linked[$data['itemtype']][] = $data; } +$supported_glpi_types = [ + 'Computer' => PluginJamfComputerSync::getSupportedGlpiItemtypes(), + 'MobileDevice' => PluginJamfMobileSync::getSupportedGlpiItemtypes() +]; + foreach ($pending as &$data) { - $itemtype = $data['type']; - /** @var CommonDBTM $item */ - $item = new $itemtype(); - $jamftype = ('PluginJamf' . $data['jamf_type']); - $guesses = $DB->request([ - 'SELECT' => ['id'], - 'FROM' => $itemtype::getTable(), - 'WHERE' => [ - 'OR' => [ - 'uuid' => $data['udid'], - 'name' => Sanitizer::sanitize($data['name']), + $queries = []; + foreach ($supported_glpi_types[$data['jamf_type']] as $type) { + $queries[] = [ + 'SELECT' => [ + new QueryExpression($DB::quoteValue($type) . ' AS ' . $DB::quoteName('itemtype')), + 'id' + ], + 'FROM' => $type::getTable(), + 'WHERE' => [ + 'OR' => [ + 'uuid' => $data['udid'], + 'name' => Sanitizer::sanitize($data['name']) + ], + 'is_deleted' => 0, + 'is_template' => 0 ], - 'is_deleted' => 0, - 'is_template' => 0, - ], - 'ORDER' => new QueryExpression("CASE WHEN uuid='" . $data['udid'] . "' THEN 0 ELSE 1 END"), - 'LIMIT' => 1, - ]); - if (count($guesses)) { - $data['guessed_item'] = $guesses->current()['id']; - } else { - $data['guessed_item'] = 0; + 'ORDER' => new QueryExpression("CASE WHEN uuid='" . $data['udid'] . "' THEN 0 ELSE 1 END"), + 'LIMIT' => 1 + ]; + } + $guesses = $DB->request(new QueryUnion($queries)); + $data['guessed_item'] = null; + foreach ($guesses as $guess) { + $data['guessed_item'][$guess['itemtype']] = $guess['id']; } } TemplateRenderer::getInstance()->display('@jamf/merge.html.twig', [ 'pending' => $pending, 'total_count' => $importcount, - 'linked' => $linked, + 'linked' => $linked, + 'supported_glpi_types' => array_map( + static fn ($ts) => array_combine($ts, array_map(static fn ($t) => $t::getTypeName(1), $ts)), + $supported_glpi_types + ) ]); Html::footer(); diff --git a/templates/import.html.twig b/templates/import.html.twig index 385435c..3a07cc0 100644 --- a/templates/import.html.twig +++ b/templates/import.html.twig @@ -28,6 +28,7 @@ * ------------------------------------------------------------------------- */ #} +{% import 'components/form/fields_macros.html.twig' as fields %}
{{ include('components/pager.html.twig', { @@ -57,14 +58,26 @@ {% set import_checkbox %} {% endset %} - + {{ import_checkbox }} {{ data.jamf_items_id }} {{ data.jamf_type }} {{ data.name }} - {{ data.type }} + {% if data.jamf_type == 'MobileDevice' %} + + {{ fields.dropdownArrayField('glpi_type', data.type, { + 'Phone': 'Phone'|itemtype_name, + 'Computer': 'Computer'|itemtype_name, + }, null, { + no_label: true, + full_width: true, + }) }} + + {% else %} + {{ data.type|itemtype_name }} + {% endif %} {{ data.udid is not empty ? data.udid : _x('message', 'Not collected during discovery', 'jamf') }} @@ -90,12 +103,19 @@ const import_ids = $(':checkbox:checked').filter(':not([name^="_checkall"])').map(function() { return this.name.replace("import","").substring(1).split('_'); }).toArray(); + const itemtype_overrides = {}; + $('select[name="glpi_type"]').each((i, e) => { + const itemtype = $(e).val(); + const id = $(e).closest('tr').attr('data-import-id'); + itemtype_overrides[id] = itemtype; + }); $.ajax({ type: "POST", url: "{{ get_plugin_web_dir('jamf') }}/ajax/import.php", data: { action: "import", - import_ids: import_ids + import_ids: import_ids, + itemtype_overrides: itemtype_overrides }, contentType: 'application/json', beforeSend: () => { diff --git a/templates/merge.html.twig b/templates/merge.html.twig index 2e7740a..7153354 100644 --- a/templates/merge.html.twig +++ b/templates/merge.html.twig @@ -58,18 +58,31 @@ {{ data.name }} - {{ data.type }} + + {% if supported_glpi_types[data.jamf_type]|length > 0 %} + {{ fields.dropdownArrayField('glpi_type', data.type, supported_glpi_types[data.jamf_type], null, { + no_label: true, + full_width: true, + }) }} + {% else %} + {{ data.type|itemtype_name }} + {% endif %} + {{ data.jamf_type }} {{ data.udid is not empty ? data.udid : _x('message', 'Not collected during discovery', 'jamf') }} {{ data.date_discover|formatted_datetime }} - {{ fields.dropdownField(data.type, 'items_id', data.guessed_item, null, { - no_label: true, - full_width: true, - used: linked[data.type]|default([])|column('items_id') - }) }} + {% for glpi_type in supported_glpi_types[data.jamf_type] %} + + {{ fields.dropdownField(glpi_type, 'items_id', data.guessed_item[glpi_type], null, { + no_label: true, + full_width: true, + used: linked[glpi_type]|default([])|column('items_id'), + }) }} + + {% endfor %} {% endfor %} @@ -93,12 +106,11 @@ for (let i = 1; i < row_count; i++) { const row = table.rows[i]; const jamf_id = row.cells[0].innerText; - const itemtype = row.cells[2].innerText; + const itemtype = $(row.cells[2]).find('select')[0].value; const jamf_type = row.cells[3].innerText; - const glpi_sel = $(row.cells[6]).find('select')[0]; + const glpi_sel = $(row.cells[6]).find('span[data-itemtype]:not(.d-none) select')[0]; const glpi_id = glpi_sel.value; if (glpi_id && glpi_id > 0) { - data = []; post_data[glpi_id] = {'itemtype': itemtype, 'jamf_id': jamf_id, 'jamf_type': jamf_type}; } } @@ -115,6 +127,11 @@ } }); } + $('select[name="glpi_type"]').on('change', (e) => { + const selection = $(e.target).val(); + $(e.target).closest('tr').find('span[data-itemtype]').addClass('d-none'); + $(e.target).closest('tr').find('span[data-itemtype="' + selection + '"]').removeClass('d-none'); + });