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';