From fb8027763482f3e7ff05ec47e63a7ac5aaab1be4 Mon Sep 17 00:00:00 2001 From: TheWitness <1439914+TheWitness@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:24:11 -0400 Subject: [PATCH 1/3] issue: shorten tags graph drilldown button title to 'Tags' The graph action menu now renders each drilldown button's title as a visible label, so 'Show tags for this graph' reads as an overlong menu entry. Shorten it to 'Tags', add a unit test exercising plugin_tags_graph_button() so the changed line stays covered, and regenerate locales/po/cacti.pot. --- CHANGELOG.md | 1 + include/functions.php | 2 +- locales/po/cacti.pot | 10 +++------ tests/Unit/TagsGraphButtonTest.php | 35 ++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 tests/Unit/TagsGraphButtonTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index aa97965..0d2c588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ --- develop --- +* issue: Shorten the graph drilldown button title from 'Show tags for this graph' to 'Tags' so it reads cleanly as a label in Cacti's graph action menu * security: Replace the confirmation pages' inline `onClick='cactiReturnTo()'` Cancel buttons with the CSP-safe `cactiReturnTo` class so the pages no longer trip Cacti's Content-Security-Policy script-src-attr directive; see the README for the one-time `applySkin()` snippet needed when running Cacti below 1.2.31 (1.2.31+ binds the class automatically) * security: Move the filter selects' inline onChange handlers into the ready block (and bind them via change, not click) so the page no longer trips Cacti's Content-Security-Policy script-src-attr directive * dev: Measure CI coverage with xdebug instead of pcov so the plugin's own sources are instrumented (pcov auto-scopes to the Composer root and skipped cacti/plugins/, leaving the patch-coverage gate with nothing to measure) diff --git a/include/functions.php b/include/functions.php index 3a87e2f..ef5b31b 100644 --- a/include/functions.php +++ b/include/functions.php @@ -703,7 +703,7 @@ function plugin_tags_graph_button($data) { $redir = $config['url_path'] . 'plugins/tags/tags.php?host_id=' . $host_id; - $fav = ''; + $fav = ''; print '' . $fav . '
'; } diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot index e0517a6..8752eb2 100644 --- a/locales/po/cacti.pot +++ b/locales/po/cacti.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Cacti \n" "Report-Msgid-Bugs-To: developers@cacti.net\n" -"POT-Creation-Date: 2026-10-07 16:11-0400\n" +"POT-Creation-Date: 2026-10-12 16:23-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Cacti Developers >\n" "Language-Team: Cacti Developers \n" @@ -370,8 +370,8 @@ msgstr "" msgid "Restart, uptime was %s" msgstr "" -#: include/functions.php -msgid "Show tags for this graph" +#: include/functions.php setup.php tags.php +msgid "Tags" msgstr "" #: setup.php @@ -382,10 +382,6 @@ msgstr "" msgid "Management" msgstr "" -#: setup.php tags.php -msgid "Tags" -msgstr "" - #: setup.php msgid "Tags Edit" msgstr "" diff --git a/tests/Unit/TagsGraphButtonTest.php b/tests/Unit/TagsGraphButtonTest.php new file mode 100644 index 0000000..727a196 --- /dev/null +++ b/tests/Unit/TagsGraphButtonTest.php @@ -0,0 +1,35 @@ + array('local_graph_id' => 42))); + $html = ob_get_clean(); + + expect($html)->toContain('class="iconLink"') + ->and($html)->toContain('fas fa-tags') + ->and($html)->toContain('title="Tags"') + ->and($html)->toContain('plugins/tags/tags.php?host_id=7') + ->and($html)->not->toContain('Show tags for this graph'); +}); From 816877be63f4f34d7b24ae0a9bbcf4002c9b790c Mon Sep 17 00:00:00 2001 From: TheWitness Date: Tue, 13 Oct 2026 10:10:24 -0400 Subject: [PATCH 2/3] feature: make the tags graph button an Enable/Disable Tags toggle Mirror thold's VRULE toggle: the graph drilldown button now flips a session draw-state (sess_config_array['tags_draw']) and shows 'Enable Tags' or 'Disable Tags' per that state, and plugin_tags_rrd_graph_graph_options() skips drawing the tag legend lines when the toggle is off. Two complete i18n strings (not an appended On/Off fragment) so the label stays grammatical across translations. Rewrite the unit test to cover all toggle branches + the legend gate, add the isset_request_var bootstrap stub, use the full GPL header, and regenerate cacti.pot. --- CHANGELOG.md | 2 +- include/functions.php | 53 ++++++++++++++++---- locales/po/cacti.pot | 16 ++++-- tests/Unit/TagsGraphButtonTest.php | 80 ++++++++++++++++++++++++++---- tests/bootstrap-unit.php | 6 +++ 5 files changed, 130 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2c588..fb7ccf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ --- develop --- -* issue: Shorten the graph drilldown button title from 'Show tags for this graph' to 'Tags' so it reads cleanly as a label in Cacti's graph action menu +* feature: Turn the graph drilldown button into an 'Enable Tags'/'Disable Tags' toggle that reflects the current session state and shows or hides this plugin's tag legend lines on graphs, rather than a static link, so it reads cleanly as a label in Cacti's graph action menu * security: Replace the confirmation pages' inline `onClick='cactiReturnTo()'` Cancel buttons with the CSP-safe `cactiReturnTo` class so the pages no longer trip Cacti's Content-Security-Policy script-src-attr directive; see the README for the one-time `applySkin()` snippet needed when running Cacti below 1.2.31 (1.2.31+ binds the class automatically) * security: Move the filter selects' inline onChange handlers into the ready block (and bind them via change, not click) so the page no longer trips Cacti's Content-Security-Policy script-src-attr directive * dev: Measure CI coverage with xdebug instead of pcov so the plugin's own sources are instrumented (pcov auto-scopes to the Composer root and skipped cacti/plugins/, leaving the patch-coverage gate with nothing to measure) diff --git a/include/functions.php b/include/functions.php index ef5b31b..e00fc4f 100644 --- a/include/functions.php +++ b/include/functions.php @@ -480,6 +480,12 @@ function plugin_tags_device_remove($ids) { function plugin_tags_rrd_graph_graph_options($data) { global $config, $tags_colors; + // Respect the graph drilldown 'Enable/Disable Tags' toggle (session state + // set by plugin_tags_graph_button()); skip drawing when tags are disabled. + if (isset($_SESSION['sess_config_array']['tags_draw']) && $_SESSION['sess_config_array']['tags_draw'] == 'off') { + return $data; + } + require($config['base_path'] . '/plugins/tags/include/arrays.php'); $tags = []; @@ -682,8 +688,9 @@ function plugin_tags_rrd_graph_graph_options($data) { /** * Hook implementation for Cacti's 'graph_buttons'/'graph_buttons_thumbnails' - * filters. Prints a link/icon that opens tags.php filtered to the current - * graph's device. Called by Cacti core via + * filters. Prints the drilldown action icon that toggles whether this + * plugin's tag legend lines are drawn on graphs, showing 'Enable Tags' or + * 'Disable Tags' per the current session state. Called by Cacti core via * api_plugin_hook('graph_buttons'/'graph_buttons_thumbnails', ...) while * rendering a graph's action buttons. * @@ -691,20 +698,44 @@ function plugin_tags_rrd_graph_graph_options($data) { * the current graph. * * @return void Outputs HTML directly. - * - * @global array $config Cacti global configuration array; used to build - * the tags.php link URL. */ function plugin_tags_graph_button($data) { - global $config; + if (isset_request_var('tags_draw') || isset($_SESSION['sess_config_array']['tags_draw'])) { + if (isset_request_var('tags_draw')) { + if (get_nfilter_request_var('tags_draw') == 'on') { + $draw = 'off'; + } else { + $draw = 'on'; + } - $host_id = db_fetch_cell_prepared('SELECT host_id FROM graph_local - WHERE id = ?',[$data[1]['local_graph_id']]); + $_SESSION['sess_config_array']['tags_draw'] = $draw; + } else { + $draw = $_SESSION['sess_config_array']['tags_draw']; + } + } else { + // Tags are drawn by default, preserving the plugin's prior behaviour. + $draw = 'on'; + $_SESSION['sess_config_array']['tags_draw'] = $draw; + } + + $url = $_SERVER['REQUEST_URI']; + $url = str_replace('&tags_draw=on', '', $url); + $url = str_replace('&tags_draw=off', '', $url); + + if (!substr_count($url, '?')) { + $separator = '?'; + } else { + $separator = '&'; + } - $redir = $config['url_path'] . 'plugins/tags/tags.php?host_id=' . $host_id; + // Two complete strings so the label stays grammatical when translated. + if ($draw == 'on') { + $title = __esc('Disable Tags', 'tags'); + } else { + $title = __esc('Enable Tags', 'tags'); + } - $fav = ''; - print '' . $fav . '
'; + print '
'; } /** diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot index 8752eb2..eb57b59 100644 --- a/locales/po/cacti.pot +++ b/locales/po/cacti.pot @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: Cacti \n" "Report-Msgid-Bugs-To: developers@cacti.net\n" -"POT-Creation-Date: 2026-10-12 16:23-0400\n" +"POT-Creation-Date: 2026-10-13 10:03-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Cacti Developers >\n" +"Last-Translator: Cacti Developers \n" "Language-Team: Cacti Developers \n" "Language: \n" "MIME-Version: 1.0\n" @@ -370,8 +370,12 @@ msgstr "" msgid "Restart, uptime was %s" msgstr "" -#: include/functions.php setup.php tags.php -msgid "Tags" +#: include/functions.php +msgid "Disable Tags" +msgstr "" + +#: include/functions.php +msgid "Enable Tags" msgstr "" #: setup.php @@ -382,6 +386,10 @@ msgstr "" msgid "Management" msgstr "" +#: setup.php tags.php +msgid "Tags" +msgstr "" + #: setup.php msgid "Tags Edit" msgstr "" diff --git a/tests/Unit/TagsGraphButtonTest.php b/tests/Unit/TagsGraphButtonTest.php index 727a196..779f88f 100644 --- a/tests/Unit/TagsGraphButtonTest.php +++ b/tests/Unit/TagsGraphButtonTest.php @@ -2,13 +2,23 @@ /* +-------------------------------------------------------------------------+ | Copyright (C) 2004-2026 The Cacti Group | + | | + | 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 2 | + | of the License, or (at your option) any later version. | + +-------------------------------------------------------------------------+ + | Cacti: The Complete RRDtool-based Graphing Solution | + +-------------------------------------------------------------------------+ + | http://www.cacti.net/ | +-------------------------------------------------------------------------+ */ /* - * Unit coverage for plugin_tags_graph_button() in include/functions.php: the - * 'graph_buttons'/'graph_buttons_thumbnails' hook prints the drilldown action - * icon that links a graph to its host's tags page. + * Unit coverage for plugin_tags_graph_button() and the tag-draw gate in + * plugin_tags_rrd_graph_graph_options() (include/functions.php): the + * 'graph_buttons' hook prints a drilldown toggle that enables/disables + * drawing this plugin's tag legend lines on graphs for the session. */ beforeAll(function () { @@ -16,20 +26,68 @@ }); beforeEach(function () { - $GLOBALS['__test_db_calls'] = array(); - $GLOBALS['__test_db_fetch_cell_prepared_return'] = ''; + $GLOBALS['__test_request'] = array(); + $_SESSION = array(); + $_SERVER['REQUEST_URI'] = '/cacti/graph_view.php?action=preview'; }); -it('prints the iconLink button with the shortened Tags title and host link', function () { - $GLOBALS['__test_db_fetch_cell_prepared_return'] = 7; - +it('shows the Disable Tags toggle by default with tags drawn', function () { ob_start(); plugin_tags_graph_button(array(1 => array('local_graph_id' => 42))); $html = ob_get_clean(); - expect($html)->toContain('class="iconLink"') + expect($html)->toContain('class="iconLink tagsDraw"') ->and($html)->toContain('fas fa-tags') - ->and($html)->toContain('title="Tags"') - ->and($html)->toContain('plugins/tags/tags.php?host_id=7') + ->and($html)->toContain('title="Disable Tags"') + ->and($html)->toContain('tags_draw=on') ->and($html)->not->toContain('Show tags for this graph'); + + expect($_SESSION['sess_config_array']['tags_draw'])->toBe('on'); +}); + +it('flips an on request to off and shows Enable Tags, prefixing ? when the url has none', function () { + test_set_request(array('tags_draw' => 'on')); + $_SERVER['REQUEST_URI'] = '/cacti/graph_view.php'; + + ob_start(); + plugin_tags_graph_button(array(1 => array('local_graph_id' => 42))); + $html = ob_get_clean(); + + expect($html)->toContain('title="Enable Tags"') + ->and($html)->toContain('?tags_draw=off'); + + expect($_SESSION['sess_config_array']['tags_draw'])->toBe('off'); +}); + +it('flips an off request back to on and shows Disable Tags', function () { + test_set_request(array('tags_draw' => 'off')); + + ob_start(); + plugin_tags_graph_button(array(1 => array('local_graph_id' => 42))); + $html = ob_get_clean(); + + expect($html)->toContain('title="Disable Tags"') + ->and($html)->toContain('tags_draw=on'); + + expect($_SESSION['sess_config_array']['tags_draw'])->toBe('on'); +}); + +it('reads the draw state from the session when no request var is set', function () { + $_SESSION['sess_config_array']['tags_draw'] = 'off'; + + ob_start(); + plugin_tags_graph_button(array(1 => array('local_graph_id' => 42))); + $html = ob_get_clean(); + + expect($html)->toContain('title="Enable Tags"') + ->and($html)->toContain('tags_draw=off'); +}); + +it('skips drawing tag legends when the session toggle is off', function () { + $_SESSION['sess_config_array']['tags_draw'] = 'off'; + + $data = array('graph_id' => 10, 'start' => 0, 'end' => 100, 'txt_graph_items' => 'ORIGINAL'); + $result = plugin_tags_rrd_graph_graph_options($data); + + expect($result)->toBe($data); }); diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index 70f0ec5..c1033fa 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -312,6 +312,12 @@ function get_nfilter_request_var($name) { } } +if (!function_exists('isset_request_var')) { + function isset_request_var($name) { + return isset($GLOBALS['__test_request'][$name]); + } +} + if (!function_exists('get_filter_request_var')) { function get_filter_request_var($name) { return isset($GLOBALS['__test_request'][$name]) ? $GLOBALS['__test_request'][$name] : ''; From 7b228bb300ba7131fc22cf2b20a046f8009b113b Mon Sep 17 00:00:00 2001 From: TheWitness Date: Tue, 13 Oct 2026 11:06:33 -0400 Subject: [PATCH 3/3] fix: strip any existing tags_draw param so the toggle URL never duplicates it --- include/functions.php | 7 +++++-- tests/Unit/TagsGraphButtonTest.php | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/functions.php b/include/functions.php index e00fc4f..fce77dc 100644 --- a/include/functions.php +++ b/include/functions.php @@ -718,9 +718,12 @@ function plugin_tags_graph_button($data) { $_SESSION['sess_config_array']['tags_draw'] = $draw; } + // Drop any existing tags_draw parameter (? or & form) so a click never + // leaves a duplicated, conflicting tags_draw in the URL. $url = $_SERVER['REQUEST_URI']; - $url = str_replace('&tags_draw=on', '', $url); - $url = str_replace('&tags_draw=off', '', $url); + $url = preg_replace('/([?&])tags_draw=(?:on|off)/', '$1', $url); + $url = str_replace('?&', '?', $url); + $url = rtrim($url, '?&'); if (!substr_count($url, '?')) { $separator = '?'; diff --git a/tests/Unit/TagsGraphButtonTest.php b/tests/Unit/TagsGraphButtonTest.php index 779f88f..73168e0 100644 --- a/tests/Unit/TagsGraphButtonTest.php +++ b/tests/Unit/TagsGraphButtonTest.php @@ -83,6 +83,18 @@ ->and($html)->toContain('tags_draw=off'); }); +it('replaces an existing first-parameter tags_draw instead of duplicating it', function () { + test_set_request(array('tags_draw' => 'on')); + $_SERVER['REQUEST_URI'] = '/cacti/graph_view.php?tags_draw=on'; + + ob_start(); + plugin_tags_graph_button(array(1 => array('local_graph_id' => 42))); + $html = ob_get_clean(); + + expect($html)->toContain('?tags_draw=off') + ->and($html)->not->toContain('tags_draw=on'); +}); + it('skips drawing tag legends when the session toggle is off', function () { $_SESSION['sess_config_array']['tags_draw'] = 'off';