From d79e1fe431ae73246fb4f6c431f9782d5af1ae3e Mon Sep 17 00:00:00 2001 From: Muris <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:16:28 +1030 Subject: [PATCH 1/9] Set optional map titles when creating configuration files --- CHANGELOG.md | 2 + locales/po/cacti.pot | 10 ++- tests/Support/NewMapEngineRegression.php | 101 ++++++++++++++++++++++ tests/Unit/NewMapEngineRegressionTest.php | 47 ++++++++++ weathermap-cacti-plugin-mgmt.php | 22 +++-- 5 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 tests/Support/NewMapEngineRegression.php create mode 100644 tests/Unit/NewMapEngineRegressionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 63aa9aae..f39d2af0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! --- develop --- +* feature: Allow setting a map title when creating configuration files + * issue#278: Preserve map and editor navigation context across graph, viewer and management pages * feature#279: Show larger responsive map preview cards with separated borders and centered titles diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot index f91fbb16..2b55f157 100644 --- a/locales/po/cacti.pot +++ b/locales/po/cacti.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Cacti \n" +"Project-Id-Version: Cacti\n" "Report-Msgid-Bugs-To: developers@cacti.net\n" -"POT-Creation-Date: 2026-10-09 11:22+1030\n" +"POT-Creation-Date: 2026-10-10 00:53+1030\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Cacti Developers \n" "Language-Team: Cacti Developers \n" @@ -1039,7 +1039,7 @@ msgstr "" msgid "Map Properties" msgstr "" -#: weathermap-cacti-plugin-editor.php +#: weathermap-cacti-plugin-editor.php weathermap-cacti-plugin-mgmt.php msgid "Map Title" msgstr "" @@ -1518,6 +1518,10 @@ msgstr "" msgid "Name including .conf" msgstr "" +#: weathermap-cacti-plugin-mgmt.php +msgid "Optional map title" +msgstr "" + #: weathermap-cacti-plugin-mgmt.php msgid "Source Map" msgstr "" diff --git a/tests/Support/NewMapEngineRegression.php b/tests/Support/NewMapEngineRegression.php new file mode 100644 index 00000000..25dc57f4 --- /dev/null +++ b/tests/Support/NewMapEngineRegression.php @@ -0,0 +1,101 @@ + & 'title'\nTITLE injected"); + $saved = new WeatherMap(); + $saved->ReadConfig($directory . '/new.conf'); + wm_engine_assert($saved->title === "My & 'title' TITLE injected",'title round trip and line-break normalization'); + $map = new WeatherMap(); + $map->width = 910; + $map->title = 'Source title'; + $map->WriteConfig($directory . '/source.conf'); + $before = file_get_contents($directory . '/source.conf'); + newMap('copy.conf','source.conf',''); + $copy = new WeatherMap(); + $copy->ReadConfig($directory . '/copy.conf'); + wm_engine_assert($copy->title === 'Source title' && $copy->width == 910,'blank title retains source'); + newMap('renamed.conf','source.conf','New title'); + $renamed = new WeatherMap(); + $renamed->ReadConfig($directory . '/renamed.conf'); + wm_engine_assert($renamed->title === 'New title' && $renamed->width == 910,'override title preserves layout'); + wm_engine_assert(file_get_contents($directory . '/source.conf') === $before,'source unchanged'); + + print "PASS\n"; +} finally { + foreach (glob($directory . '/*') as $file) { + unlink($file); + }rmdir($directory); +} diff --git a/tests/Unit/NewMapEngineRegressionTest.php b/tests/Unit/NewMapEngineRegressionTest.php new file mode 100644 index 00000000..e1b3cd5a --- /dev/null +++ b/tests/Unit/NewMapEngineRegressionTest.php @@ -0,0 +1,47 @@ +&1',$lines,$status); + expect($status)->toBe(0)->and(implode("\n",$lines))->toBe('PASS'); +}); diff --git a/weathermap-cacti-plugin-mgmt.php b/weathermap-cacti-plugin-mgmt.php index 52cff71c..00e1aa76 100644 --- a/weathermap-cacti-plugin-mgmt.php +++ b/weathermap-cacti-plugin-mgmt.php @@ -302,9 +302,9 @@ break; case 'newmap': if (isset_request_var('srcmap') && get_nfilter_request_var('srcmap') != '-1') { - newMap(get_nfilter_request_var('newfile'), get_nfilter_request_var('srcmap')); + newMap(get_nfilter_request_var('newfile'), get_nfilter_request_var('srcmap'), get_nfilter_request_var('newtitle')); } else { - newMap(get_nfilter_request_var('newfile')); + newMap(get_nfilter_request_var('newfile'), '', get_nfilter_request_var('newtitle')); } header('Location: weathermap-cacti-plugin-mgmt.php?action=addmap_picker&header=false'); @@ -1349,6 +1349,8 @@ function addmap_filter() { '> + + '> @@ -1418,6 +1420,7 @@ function clearFilter() { var json = { __csrf_magic: csrfMagicToken, newfile: $('#newfile').val(), + newtitle: $('#newtitle').val(), srcmap: $('#srcmap').val() }; @@ -3343,13 +3346,14 @@ function weathermap_group_delete($id) { * @param string $sourcemapfile Optional existing config file to copy * settings from instead of creating a blank * map. + * @param string $title Optional display title for the new map. * * @return void * * @global string $weathermap_confdir The configured path to the * weathermap configs directory. */ -function newMap($mapfile, $sourcemapfile = '') { +function newMap($mapfile, $sourcemapfile = '', $title = '') { global $weathermap_confdir; if ($mapfile == basename($mapfile)) { @@ -3358,7 +3362,7 @@ function newMap($mapfile, $sourcemapfile = '') { $mapfile = dirname($mapfile) . '/' . clean_up_name(basename($mapfile, '.conf')) . '.conf'; } - if (!file_exists($sourcemapfile) || $sourcemapfile == basename($sourcemapfile)) { + if ($sourcemapfile != '' && (!file_exists($sourcemapfile) || $sourcemapfile == basename($sourcemapfile))) { $sourcemapfile = $weathermap_confdir . '/' . $sourcemapfile; } @@ -3373,13 +3377,19 @@ function newMap($mapfile, $sourcemapfile = '') { if ($sourcemapfile != '') { if (file_exists($sourcemapfile) && is_readable($sourcemapfile)) { $map->ReadConfig($sourcemapfile); - $map->WriteConfig($mapfile); + if (is_string($title) && trim($title) !== '') { + $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); + } + $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created from %s', basename($mapfile), basename($sourcemapfile), 'weathermap'), MESSAGE_LEVEL_INFO); } else { raise_message('map_message', __('The Source Map File name is not readable or does not exist!', 'weathermap'), MESSAGE_LEVEL_ERROR); } } elseif ($mapfile != '') { - $map->WriteConfig($mapfile); + if (is_string($title) && trim($title) !== '') { + $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); + } + $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created.', basename($mapfile), 'weathermap'), MESSAGE_LEVEL_INFO); } } else { From 80809d7f7e091985ae3c41bd809e6b56c8d1401b Mon Sep 17 00:00:00 2001 From: Muris <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:22:58 +1030 Subject: [PATCH 2/9] Refine touched formatting and verify saved options --- weathermap-cacti-plugin-mgmt.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/weathermap-cacti-plugin-mgmt.php b/weathermap-cacti-plugin-mgmt.php index 00e1aa76..716fb7f7 100644 --- a/weathermap-cacti-plugin-mgmt.php +++ b/weathermap-cacti-plugin-mgmt.php @@ -1349,8 +1349,8 @@ function addmap_filter() { '> - - '> + + '> @@ -1420,7 +1420,7 @@ function clearFilter() { var json = { __csrf_magic: csrfMagicToken, newfile: $('#newfile').val(), - newtitle: $('#newtitle').val(), + newtitle: $('#newtitle').val(), srcmap: $('#srcmap').val() }; @@ -3378,18 +3378,18 @@ function newMap($mapfile, $sourcemapfile = '', $title = '') { if (file_exists($sourcemapfile) && is_readable($sourcemapfile)) { $map->ReadConfig($sourcemapfile); if (is_string($title) && trim($title) !== '') { - $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); - } - $map->WriteConfig($mapfile); + $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); + } + $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created from %s', basename($mapfile), basename($sourcemapfile), 'weathermap'), MESSAGE_LEVEL_INFO); } else { raise_message('map_message', __('The Source Map File name is not readable or does not exist!', 'weathermap'), MESSAGE_LEVEL_ERROR); } } elseif ($mapfile != '') { if (is_string($title) && trim($title) !== '') { - $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); - } - $map->WriteConfig($mapfile); + $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); + } + $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created.', basename($mapfile), 'weathermap'), MESSAGE_LEVEL_INFO); } } else { From b2b797f8d0e59f1f36b82b1a179d7de5151a480a Mon Sep 17 00:00:00 2001 From: Muris <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:28:35 +1030 Subject: [PATCH 3/9] Reference PR 297 in the plugin changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f39d2af0..8dc44c5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! --- develop --- -* feature: Allow setting a map title when creating configuration files +* feature#297: Allow setting a map title when creating configuration files * issue#278: Preserve map and editor navigation context across graph, viewer and management pages From ffbf971d7a42aabb29559d7600a63001b0684d7f Mon Sep 17 00:00:00 2001 From: Muris <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:31:02 +1030 Subject: [PATCH 4/9] Give feature regression files distinct names for independent merging --- ...wMapEngineRegression.php => NewMapTitleEngineRegression.php} | 0 ...NewMapEngineRegressionTest.php => NewMapTitleEngineTest.php} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/Support/{NewMapEngineRegression.php => NewMapTitleEngineRegression.php} (100%) rename tests/Unit/{NewMapEngineRegressionTest.php => NewMapTitleEngineTest.php} (97%) diff --git a/tests/Support/NewMapEngineRegression.php b/tests/Support/NewMapTitleEngineRegression.php similarity index 100% rename from tests/Support/NewMapEngineRegression.php rename to tests/Support/NewMapTitleEngineRegression.php diff --git a/tests/Unit/NewMapEngineRegressionTest.php b/tests/Unit/NewMapTitleEngineTest.php similarity index 97% rename from tests/Unit/NewMapEngineRegressionTest.php rename to tests/Unit/NewMapTitleEngineTest.php index e1b3cd5a..75542919 100644 --- a/tests/Unit/NewMapEngineRegressionTest.php +++ b/tests/Unit/NewMapTitleEngineTest.php @@ -42,6 +42,6 @@ it('PersistsAndRendersTheNewMapOptionsUsingTheRealEngine',function () { $lines = []; - exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(dirname(__DIR__) . '/Support/NewMapEngineRegression.php') . ' 2>&1',$lines,$status); + exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(dirname(__DIR__) . '/Support/NewMapTitleEngineRegression.php') . ' 2>&1',$lines,$status); expect($status)->toBe(0)->and(implode("\n",$lines))->toBe('PASS'); }); From 0a2c1271aaa021e654201c5c8c980f3ce18d67aa Mon Sep 17 00:00:00 2001 From: Muris <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:35:37 +1030 Subject: [PATCH 5/9] Retain the real entry-point directory in the title creation fixture --- tests/Support/NewMapTitleEngineRegression.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Support/NewMapTitleEngineRegression.php b/tests/Support/NewMapTitleEngineRegression.php index 25dc57f4..66116d12 100644 --- a/tests/Support/NewMapTitleEngineRegression.php +++ b/tests/Support/NewMapTitleEngineRegression.php @@ -72,7 +72,7 @@ function wm_engine_assert($value,$message) { try { $source = file_get_contents($plugin . '/weathermap-cacti-plugin-mgmt.php'); $start = strpos($source,'function newMap('); - eval(substr($source,$start)); + eval(str_replace('__DIR__', var_export($plugin, true), substr($source, $start))); $weathermap_confdir = $directory; newMap('new.conf','',"My & 'title'\nTITLE injected"); $saved = new WeatherMap(); From e2d812d11864739da693faa04102dc0f2dfe13fd Mon Sep 17 00:00:00 2001 From: alcatron <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:04:58 +1030 Subject: [PATCH 6/9] Sanitize creation titles consistently with the editor (PR #297) --- CHANGELOG.md | 2 + tests/Support/NewMapTitleEngineRegression.php | 46 ++++++------------- tests/Unit/NewMapTitleEngineTest.php | 42 +++++------------ weathermap-cacti-plugin-mgmt.php | 10 ++-- 4 files changed, 34 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc44c5a..f38d3d4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! --- develop --- +* issue#297: Apply existing editor title sanitization to new and copied maps + * feature#297: Allow setting a map title when creating configuration files * issue#278: Preserve map and editor navigation context across graph, viewer and management pages diff --git a/tests/Support/NewMapTitleEngineRegression.php b/tests/Support/NewMapTitleEngineRegression.php index 66116d12..c0a0dd0f 100644 --- a/tests/Support/NewMapTitleEngineRegression.php +++ b/tests/Support/NewMapTitleEngineRegression.php @@ -1,41 +1,23 @@ & 'title'\nTITLE injected"); $saved = new WeatherMap(); $saved->ReadConfig($directory . '/new.conf'); - wm_engine_assert($saved->title === "My & 'title' TITLE injected",'title round trip and line-break normalization'); + wm_engine_assert($saved->title === 'My <network> & 'title' TITLE injected','title round trip and line-break normalization'); $map = new WeatherMap(); $map->width = 910; $map->title = 'Source title'; @@ -87,10 +69,10 @@ function wm_engine_assert($value,$message) { $copy = new WeatherMap(); $copy->ReadConfig($directory . '/copy.conf'); wm_engine_assert($copy->title === 'Source title' && $copy->width == 910,'blank title retains source'); - newMap('renamed.conf','source.conf','New title'); + newMap('renamed.conf','source.conf','New & copy'); $renamed = new WeatherMap(); $renamed->ReadConfig($directory . '/renamed.conf'); - wm_engine_assert($renamed->title === 'New title' && $renamed->width == 910,'override title preserves layout'); + wm_engine_assert($renamed->title === 'New <title> & copy' && $renamed->width == 910,'override title preserves layout'); wm_engine_assert(file_get_contents($directory . '/source.conf') === $before,'source unchanged'); print "PASS\n"; diff --git a/tests/Unit/NewMapTitleEngineTest.php b/tests/Unit/NewMapTitleEngineTest.php index 75542919..9d143c17 100644 --- a/tests/Unit/NewMapTitleEngineTest.php +++ b/tests/Unit/NewMapTitleEngineTest.php @@ -1,46 +1,28 @@ <?php /* +-------------------------------------------------------------------------+ - | Copyright (C) 2022-2026 The Cacti Group, Inc. | + | Copyright (C) 2004-2026 The Cacti Group, Howard Jones | | | - | Based on the Original Plugin developed by Howard Jones | + | 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. | | | - | Copyright (C) 2005-2022 Howard Jones and contributors | - | | - | Permission is hereby granted, free of charge, to any person obtaining | - | a copy of this software and associated documentation files | - | (the "Software"), to deal in the Software without restriction, | - | including without limitation the rights to use, copy, modify, merge, | - | publish, distribute, sublicense, and/or sell copies of the Software, | - | and to permit persons to whom the Software is furnished to do so, | - | subject to the following conditions: | - | | - | The above copyright notice and this permission notice shall be | - | included in all copies or substantial portions of the Software. | - | | - | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | - | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | - | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | - | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | - | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | - | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | - | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | - | SOFTWARE. | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | +-------------------------------------------------------------------------+ | Cacti: The Complete RRDtool-based Graphing Solution | +-------------------------------------------------------------------------+ - | Extensions to Howard Jones' original work are designed, written, and | - | maintained by the Cacti Group. | - | | - | Howard Jones was the original author of Weathermap. You can reach | - | him at: howie@thingy.com | + | This code is designed, written, and maintained by the Cacti Group. See | + | about.php and/or the AUTHORS file for specific developer information. | +-------------------------------------------------------------------------+ - | http://www.network-weathermap.com/ | | http://www.cacti.net/ | +-------------------------------------------------------------------------+ */ -it('PersistsAndRendersTheNewMapOptionsUsingTheRealEngine',function () { +it('PersistsAndRendersTheNewMapOptionsUsingTheRealEngine', function () { $lines = []; exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(dirname(__DIR__) . '/Support/NewMapTitleEngineRegression.php') . ' 2>&1',$lines,$status); expect($status)->toBe(0)->and(implode("\n",$lines))->toBe('PASS'); diff --git a/weathermap-cacti-plugin-mgmt.php b/weathermap-cacti-plugin-mgmt.php index 716fb7f7..25255cc7 100644 --- a/weathermap-cacti-plugin-mgmt.php +++ b/weathermap-cacti-plugin-mgmt.php @@ -3356,6 +3356,8 @@ function weathermap_group_delete($id) { function newMap($mapfile, $sourcemapfile = '', $title = '') { global $weathermap_confdir; + require_once __DIR__ . '/lib/editor.inc.php'; + if ($mapfile == basename($mapfile)) { $mapfile = $weathermap_confdir . '/' . clean_up_name(basename($mapfile, '.conf')) . '.conf'; } else { @@ -3378,16 +3380,16 @@ function newMap($mapfile, $sourcemapfile = '', $title = '') { if (file_exists($sourcemapfile) && is_readable($sourcemapfile)) { $map->ReadConfig($sourcemapfile); if (is_string($title) && trim($title) !== '') { - $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); - } - $map->WriteConfig($mapfile); + $map->title = wm_editor_sanitize_string(trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title))); + } + $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created from %s', basename($mapfile), basename($sourcemapfile), 'weathermap'), MESSAGE_LEVEL_INFO); } else { raise_message('map_message', __('The Source Map File name is not readable or does not exist!', 'weathermap'), MESSAGE_LEVEL_ERROR); } } elseif ($mapfile != '') { if (is_string($title) && trim($title) !== '') { - $map->title = trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title)); + $map->title = wm_editor_sanitize_string(trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title))); } $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created.', basename($mapfile), 'weathermap'), MESSAGE_LEVEL_INFO); From 49479edd20e4ab73e9c034feffabf3791fc472e9 Mon Sep 17 00:00:00 2001 From: alcatron <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:24:04 +1030 Subject: [PATCH 7/9] Escape translated map title form fields --- CHANGELOG.md | 2 ++ tests/Unit/NewMapTitleEscapingTest.php | 37 ++++++++++++++++++++++++++ weathermap-cacti-plugin-mgmt.php | 4 +-- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/NewMapTitleEscapingTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f38d3d4a..dd06bf67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! --- develop --- +* issue#297: Escape translated map title labels and placeholders + * issue#297: Apply existing editor title sanitization to new and copied maps * feature#297: Allow setting a map title when creating configuration files diff --git a/tests/Unit/NewMapTitleEscapingTest.php b/tests/Unit/NewMapTitleEscapingTest.php new file mode 100644 index 00000000..3c9e3e4f --- /dev/null +++ b/tests/Unit/NewMapTitleEscapingTest.php @@ -0,0 +1,37 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Copyright (C) 2004-2026 The Cacti Group, Howard Jones | + | | + | 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. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + +-------------------------------------------------------------------------+ + | Cacti: The Complete RRDtool-based Graphing Solution | + +-------------------------------------------------------------------------+ + | This code is designed, written, and maintained by the Cacti Group. See | + | about.php and/or the AUTHORS file for specific developer information. | + +-------------------------------------------------------------------------+ + | http://www.cacti.net/ | + +-------------------------------------------------------------------------+ +*/ + +it('EscapesTranslatedTitleFieldsInTheActualCreateForm', function () { + $source = file_get_contents(dirname(__DIR__, 2) . '/weathermap-cacti-plugin-mgmt.php'); + $start = strpos($source, "<td><label for='newtitle'>"); + $end = strpos($source, '</td>', strpos($source, "<input id='newtitle'", $start)) + strlen('</td>'); + $fragment = substr($source, $start, $end - $start); + $payload = "Title' autofocus onfocus='alert(1) <script>"; + // Substitute translated output before the escape helper executes. + $fragment = str_replace(["'Map Title'", "'Optional map title'"], var_export($payload, true), $fragment); + ob_start(); + eval('?>' . $fragment); + $html = ob_get_clean(); + expect($html)->toContain(''', '<script>')->not->toContain("placeholder='Title' autofocus", '<script>'); +}); diff --git a/weathermap-cacti-plugin-mgmt.php b/weathermap-cacti-plugin-mgmt.php index 25255cc7..58a1ea57 100644 --- a/weathermap-cacti-plugin-mgmt.php +++ b/weathermap-cacti-plugin-mgmt.php @@ -1349,8 +1349,8 @@ function addmap_filter() { <td> <input id='newfile' class='ui-state-default ui-corner-all' name='newfile' type='text' size='25' value='' placeholder='<?php print __('Name including .conf', 'weathermaps'); ?>'> </td> - <td><label for='newtitle'><?php print __('Map Title', 'weathermap'); ?></label></td> - <td><input id='newtitle' class='ui-state-default ui-corner-all' name='newtitle' type='text' size='25' value='' placeholder='<?php print __('Optional map title', 'weathermap'); ?>'></td> + <td><label for='newtitle'><?php print __esc('Map Title', 'weathermap'); ?></label></td> + <td><input id='newtitle' class='ui-state-default ui-corner-all' name='newtitle' type='text' size='25' value='' placeholder='<?php print __esc('Optional map title', 'weathermap'); ?>'></td> <td> <?php print __('Source Map', 'weathermaps'); ?> </td> From 3c9c49264f8cfb4481508d8c9ba9ffd1e44a0ef6 Mon Sep 17 00:00:00 2001 From: alcatron <27321443+alcatron@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:24:42 +1030 Subject: [PATCH 8/9] Preserve map titles for normalized empty overrides --- CHANGELOG.md | 2 +- tests/Support/NewMapTitleEngineRegression.php | 16 ++++++++++++++++ weathermap-cacti-plugin-mgmt.php | 10 ++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd06bf67..4c7e6afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! --- develop --- -* issue#297: Escape translated map title labels and placeholders +* issue#297: Escape translated map title fields and retain existing titles for normalized empty overrides * issue#297: Apply existing editor title sanitization to new and copied maps diff --git a/tests/Support/NewMapTitleEngineRegression.php b/tests/Support/NewMapTitleEngineRegression.php index c0a0dd0f..93714dd8 100644 --- a/tests/Support/NewMapTitleEngineRegression.php +++ b/tests/Support/NewMapTitleEngineRegression.php @@ -69,6 +69,22 @@ function wm_engine_assert($value,$message) { $copy = new WeatherMap(); $copy->ReadConfig($directory . '/copy.conf'); wm_engine_assert($copy->title === 'Source title' && $copy->width == 910,'blank title retains source'); + foreach (["\x01", "\x7f", " \r\n\x02 ", null, []] as $index => $empty_title) { + $filename = 'empty-' . $index . '.conf'; + newMap($filename, 'source.conf', $empty_title); + $empty_copy = new WeatherMap(); + $empty_copy->ReadConfig($directory . '/' . $filename); + wm_engine_assert($empty_copy->title === 'Source title' && $empty_copy->width == 910, 'normalized empty override preserves source title and layout'); + } + newMap('blank-control.conf', '', "\x01"); + $blank = new WeatherMap(); + $blank->ReadConfig($directory . '/blank-control.conf'); + $defaults = new WeatherMap(); + wm_engine_assert($blank->title === $defaults->title, 'normalized empty title preserves a new map default'); + newMap('mixed.conf', 'source.conf', " \x01New\x7ftitle "); + $mixed = new WeatherMap(); + $mixed->ReadConfig($directory . '/mixed.conf'); + wm_engine_assert($mixed->title === 'New title', 'mixed control characters normalize before applying a real override'); newMap('renamed.conf','source.conf','New <title> & copy'); $renamed = new WeatherMap(); $renamed->ReadConfig($directory . '/renamed.conf'); diff --git a/weathermap-cacti-plugin-mgmt.php b/weathermap-cacti-plugin-mgmt.php index 58a1ea57..6e5e2fc4 100644 --- a/weathermap-cacti-plugin-mgmt.php +++ b/weathermap-cacti-plugin-mgmt.php @@ -3358,6 +3358,8 @@ function newMap($mapfile, $sourcemapfile = '', $title = '') { require_once __DIR__ . '/lib/editor.inc.php'; + $title = is_string($title) ? wm_editor_sanitize_string(trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title))) : ''; + if ($mapfile == basename($mapfile)) { $mapfile = $weathermap_confdir . '/' . clean_up_name(basename($mapfile, '.conf')) . '.conf'; } else { @@ -3379,8 +3381,8 @@ function newMap($mapfile, $sourcemapfile = '', $title = '') { if ($sourcemapfile != '') { if (file_exists($sourcemapfile) && is_readable($sourcemapfile)) { $map->ReadConfig($sourcemapfile); - if (is_string($title) && trim($title) !== '') { - $map->title = wm_editor_sanitize_string(trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title))); + if ($title !== '') { + $map->title = $title; } $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created from %s', basename($mapfile), basename($sourcemapfile), 'weathermap'), MESSAGE_LEVEL_INFO); @@ -3388,8 +3390,8 @@ function newMap($mapfile, $sourcemapfile = '', $title = '') { raise_message('map_message', __('The Source Map File name is not readable or does not exist!', 'weathermap'), MESSAGE_LEVEL_ERROR); } } elseif ($mapfile != '') { - if (is_string($title) && trim($title) !== '') { - $map->title = wm_editor_sanitize_string(trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title))); + if ($title !== '') { + $map->title = $title; } $map->WriteConfig($mapfile); raise_message('map_message', __('New Map file %s created.', basename($mapfile), 'weathermap'), MESSAGE_LEVEL_INFO); From dcc5584aa634190ee4e93a3cd6182a7eaccb23d1 Mon Sep 17 00:00:00 2001 From: alcatron <27321443+alcatron@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:29:17 +1030 Subject: [PATCH 9/9] Reject titles exceeding the config reader line limit --- locales/po/cacti.pot | 4 ++++ tests/Support/NewMapTitleEngineRegression.php | 17 +++++++++++++++++ weathermap-cacti-plugin-mgmt.php | 6 ++++++ 3 files changed, 27 insertions(+) diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot index 559cb584..177febe1 100644 --- a/locales/po/cacti.pot +++ b/locales/po/cacti.pot @@ -2037,3 +2037,7 @@ msgstr "" #: weathermap-cacti-plugin.php msgid "Paused" msgstr "" + +#: weathermap-cacti-plugin-mgmt.php +msgid "Map title is too long. Use at most 4088 bytes after escaping." +msgstr "" diff --git a/tests/Support/NewMapTitleEngineRegression.php b/tests/Support/NewMapTitleEngineRegression.php index 93714dd8..636f6d7c 100644 --- a/tests/Support/NewMapTitleEngineRegression.php +++ b/tests/Support/NewMapTitleEngineRegression.php @@ -91,6 +91,23 @@ function wm_engine_assert($value,$message) { wm_engine_assert($renamed->title === 'New <title> & copy' && $renamed->width == 910,'override title preserves layout'); wm_engine_assert(file_get_contents($directory . '/source.conf') === $before,'source unchanged'); + foreach ([str_repeat('T', 4088), str_repeat('é', 2044)] as $index => $boundary_title) { + newMap('boundary-' . $index . '.conf', '', $boundary_title); + $boundary = new WeatherMap(); + $boundary->ReadConfig($directory . '/boundary-' . $index . '.conf'); + wm_engine_assert($boundary->title === $boundary_title, 'maximum byte-length title round-trips intact'); + } + $include = $directory . '/injected.conf'; + file_put_contents($include, "WIDTH 9999\n"); + foreach ([str_repeat('T', 4089), str_repeat('é', 2045), str_repeat('&', 818), str_repeat('T', 4089) . 'INCLUDE ' . $include] as $index => $oversized_title) { + foreach (['', 'source.conf'] as $source_map) { + $filename = 'oversized-' . $index . '-' . ($source_map === '' ? 'blank' : 'copy') . '.conf'; + newMap($filename, $source_map, $oversized_title); + wm_engine_assert(!file_exists($directory . '/' . $filename), 'oversized title cannot create a config or inject directives'); + } + } + wm_engine_assert(file_get_contents($directory . '/source.conf') === $before, 'oversized copied-map override leaves its source untouched'); + print "PASS\n"; } finally { foreach (glob($directory . '/*') as $file) { diff --git a/weathermap-cacti-plugin-mgmt.php b/weathermap-cacti-plugin-mgmt.php index 6e5e2fc4..3b54bc85 100644 --- a/weathermap-cacti-plugin-mgmt.php +++ b/weathermap-cacti-plugin-mgmt.php @@ -3360,6 +3360,12 @@ function newMap($mapfile, $sourcemapfile = '', $title = '') { $title = is_string($title) ? wm_editor_sanitize_string(trim(preg_replace('/[\x00-\x1f\x7f]/', ' ', $title))) : ''; + // fgets(..., 4096) must read the complete TITLE line, including its newline. + if (strlen($title) > 4088) { + raise_message('map_message', __esc('Map title is too long. Use at most 4088 bytes after escaping.', 'weathermap'), MESSAGE_LEVEL_ERROR); + return; + } + if ($mapfile == basename($mapfile)) { $mapfile = $weathermap_confdir . '/' . clean_up_name(basename($mapfile, '.conf')) . '.conf'; } else {