Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

--- develop ---

* security: Move the plugin's remaining inline event handlers to CSP-safe bindings so the pages no longer trip Cacti's Content-Security-Policy script-src-attr directive: confirmation Cancel buttons use the `cactiReturnTo` class, the database/filter selects are bound via change() in their ready blocks, and the main filter's sort/report selects and time-shift icons are bound in flowview_display_filter's ready block
* dev: Replace the translation-template CI check that regenerated locales/po/cacti.pot and compared it with a diff-based gate (tests/bin/check-i18n-pot.php) requiring cacti.pot to be updated only when a pull request adds, removes, or modifies an i18n function call
* issue: Prevent flow-capture from fatally erroring (TypeError in spl_object_hash) when flowview_db_check_reconnect() held a null shared-database handle; normalize a non-object connection to false so db_check_reconnect() checks and reconnects the default connection instead
* 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)
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@ transfer into the Cacti database. Check the README.md in the service folder to
describe this process. Any time you add a new listener, you must restart this
service.

## Cacti compatibility

If you are running a version of Cacti below 1.2.31, please add the function
below to the `applySkin()` function in `include/layout.js` to enable the Cancel
buttons on forms to work:
Comment thread
TheWitness marked this conversation as resolved.

```js
$(document).off('click.cactiReturnTo', '.cactiReturnTo')
.on('click.cactiReturnTo', '.cactiReturnTo', function(event) {
event.preventDefault();
cactiReturnTo($(this).attr('data-url'));
});
```

## Upgrading

After upgrading the plugin (when new plugin files have been copied into place),
Expand Down
30 changes: 21 additions & 9 deletions flowview_databases.php
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ function view_db_table($tab, &$tabs): void {
<?php print __('Source', 'flowview');?>
</td>
<td>
<select id='source' name='source' onChange='applyFilter()'>
<select id='source' name='source'>
<option value='-1'<?php print (get_request_var('source') == '-1' ? ' selected>':'>') . __('Any', 'flowview');?></option>
<?php
$sources = array_rekey(
Expand All @@ -968,7 +968,7 @@ function view_db_table($tab, &$tabs): void {
<?php print __('Entries', 'flowview');?>
</td>
<td>
<select id='rows' name='rows' onChange='applyFilter()'>
<select id='rows' name='rows'>
<option value='-1'<?php print (get_request_var('rows') == '-1' ? ' selected>':'>') . __('Default', 'flowview');?></option>
<?php
if (cacti_sizeof($item_rows) > 0) {
Expand Down Expand Up @@ -1063,6 +1063,10 @@ function() {
}

$(function() {
$('#source, #rows, #verified, #version').change(function() {
applyFilter();
});

$('#refresh').on('click', function() {
applyFilter();
});
Expand Down Expand Up @@ -1217,7 +1221,7 @@ function form_actions(): void {
</td>
</tr>\n";

$save_html = "<button type='button' class='ui-button ui-corner-all ui-widget' onClick='cactiReturnTo()'>" . __esc('Cancel', 'flowview') . "</button>
$save_html = "<button type='button' class='ui-button ui-corner-all ui-widget cactiReturnTo'>" . __esc('Cancel', 'flowview') . "</button>
<button type='submit' class='ui-button ui-corner-all ui-widget ui-state-active' title='" . __n('Delete DNS Entry', 'Delete DNS Entries', cacti_sizeof($dns_array), 'flowview') . "'>" . __esc('Continue', 'flowview') . '</button>';
}
} else {
Expand Down Expand Up @@ -1313,7 +1317,7 @@ function view_dns_cache(): void {
<?php print __('Verified', 'flowview');?>
</td>
<td>
<select id='verified' name='verified' onChange='applyFilter()'>
<select id='verified' name='verified'>
<option value='-1'<?php print (get_request_var('verified') == '-1' ? ' selected>':'>') . __('Any', 'flowview');?></option>
<option value='0'<?php print (get_request_var('verified') == '0' ? ' selected>':'>') . __('Unverified', 'flowview');?></option>
<option value='1'<?php print (get_request_var('verified') == '1' ? ' selected>':'>') . __('Verified', 'flowview');?></option>
Expand All @@ -1323,7 +1327,7 @@ function view_dns_cache(): void {
<?php print __('Source', 'flowview');?>
</td>
<td>
<select id='source' name='source' onChange='applyFilter()'>
<select id='source' name='source'>
<option value='-1'<?php print (get_request_var('source') == '-1' ? ' selected>':'>') . __('Any', 'flowview');?></option>
<?php
$sources = array_rekey(
Expand All @@ -1345,7 +1349,7 @@ function view_dns_cache(): void {
<?php print __('Entries', 'flowview');?>
</td>
<td>
<select id='rows' name='rows' onChange='applyFilter()'>
<select id='rows' name='rows'>
<option value='-1'<?php print (get_request_var('rows') == '-1' ? ' selected>':'>') . __('Default', 'flowview');?></option>
<?php
if (cacti_sizeof($item_rows) > 0) {
Expand Down Expand Up @@ -1389,6 +1393,10 @@ function purgeFilter() {
}

$(function() {
$('#source, #rows, #verified, #version').change(function() {
applyFilter();
});

$('#refresh').on('click', function() {
applyFilter();
});
Expand Down Expand Up @@ -1614,7 +1622,7 @@ function view_routes($tab): void {
<?php print __('IP Version', 'flowview');?>
</td>
<td>
<select id='version' name='verified' onChange='applyFilter()'>
<select id='version' name='verified'>
<option value='-1'<?php print (get_request_var('version') == '-1' ? ' selected>':'>') . __('Any', 'flowview');?></option>
<option value='0'<?php print (get_request_var('version') == '0' ? ' selected>':'>') . __('IPv4', 'flowview');?></option>
<option value='1'<?php print (get_request_var('version') == '1' ? ' selected>':'>') . __('IPv6', 'flowview');?></option>
Expand All @@ -1624,7 +1632,7 @@ function view_routes($tab): void {
<?php print __('Source', 'flowview');?>
</td>
<td>
<select id='source' name='source' onChange='applyFilter()'>
<select id='source' name='source'>
<option value='-1'<?php print (get_request_var('source') == '-1' ? ' selected>':'>') . __('Any', 'flowview');?></option>
<?php
$sources = array_rekey(
Expand All @@ -1646,7 +1654,7 @@ function view_routes($tab): void {
<?php print __('Entries', 'flowview');?>
</td>
<td>
<select id='rows' name='rows' onChange='applyFilter()'>
<select id='rows' name='rows'>
<option value='-1'<?php print (get_request_var('rows') == '-1' ? ' selected>':'>') . __('Default', 'flowview');?></option>
<?php
if (cacti_sizeof($item_rows) > 0) {
Expand Down Expand Up @@ -1742,6 +1750,10 @@ function() {
}

$(function() {
$('#source, #rows, #verified, #version').change(function() {
applyFilter();
});

$('#refresh').on('click', function() {
applyFilter();
});
Expand Down
2 changes: 1 addition & 1 deletion flowview_devices.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ function actions_devices(): void {
<input type='hidden' name='action' value='actions'>
<input type='hidden' name='selected_items' value='" . (isset($device_array) ? serialize($device_array) : '') . "'>
<input type='hidden' name='drp_action' value='" . get_filter_request_var('drp_action') . "'>
<button type='button' class='ui-button ui-corner-all ui-widget' onClick='javascript:document.location=\"flowview_devices.php\"'>" . __esc('Cancel', 'flowview') . "</button>
<button type='button' class='ui-button ui-corner-all ui-widget cactiReturnTo' data-url='flowview_devices.php'>" . __esc('Cancel', 'flowview') . "</button>
$save_html
</td>
</tr>\n";
Expand Down
2 changes: 1 addition & 1 deletion flowview_filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ function actions_filters(): void {
<input type='hidden' name='action' value='actions'>
<input type='hidden' name='selected_items' value='" . (isset($filter_array) ? serialize($filter_array) : '') . "'>
<input type='hidden' name='drp_action' value='" . get_nfilter_request_var('drp_action') . "'>
<button type='button' class='ui-button ui-corner-all ui-widget' onClick='cactiReturnTo()'>" . __esc('Cancel', 'flowview') . "</button>
<button type='button' class='ui-button ui-corner-all ui-widget cactiReturnTo'>" . __esc('Cancel', 'flowview') . "</button>
$save_html
</td>
</tr>";
Expand Down
2 changes: 1 addition & 1 deletion flowview_schedules.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ function actions_schedules(): void {
<input type='hidden' name='action' value='actions'>
<input type='hidden' name='selected_items' value='" . (isset($schedule_array) ? serialize($schedule_array) : '') . "'>
<input type='hidden' name='drp_action' value='" . get_nfilter_request_var('drp_action') . "'>
<button type='button' class='ui-button ui-corner-all ui-widget' onClick='cactiReturnTo()'>" . __esc('Cancel', 'flowview') . "</button>
<button type='button' class='ui-button ui-corner-all ui-widget cactiReturnTo'>" . __esc('Cancel', 'flowview') . "</button>
$save_html
</td>
</tr>";
Expand Down
28 changes: 20 additions & 8 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ function flowview_display_filter(): void {
<?php print __('Report', 'flowview');?>
</td>
<td>
<select id='report' name='report' onChange='applyFilter(false)'>
<select id='report' name='report'>
<?php
$reports = [];

Expand Down Expand Up @@ -831,7 +831,7 @@ function flowview_display_filter(): void {
<?php print __('Sort Field', 'flowview');?>
</td>
<td>
<select id='sortfield' name='sortfield' onChange='applyFilter(false)'>
<select id='sortfield' name='sortfield'>
<?php
$columns[0] = __('Select a Filter First', 'flowview');

Expand Down Expand Up @@ -886,7 +886,7 @@ function flowview_display_filter(): void {
<?php print __('Lines', 'flowview');?>
</td>
<td>
<select id='cutofflines' name='cutofflines' onChange='applyFilter(false)'>
<select id='cutofflines' name='cutofflines'>
<?php
if (cacti_sizeof($cutoff_lines)) {
if (get_request_var('report') != 's99') {
Expand Down Expand Up @@ -920,7 +920,7 @@ function flowview_display_filter(): void {
<?php print __('Octets', 'flowview');?>
</td>
<td>
<select id='cutoffoctets' name='cutoffoctets' onChange='applyFilter(false)'>
<select id='cutoffoctets' name='cutoffoctets'>
<?php
if (cacti_sizeof($cutoff_octets)) {
foreach($cutoff_octets as $key => $value) {
Expand All @@ -946,7 +946,7 @@ function flowview_display_filter(): void {
<?php print __('Timespan', 'flowview');?>
</td>
<td>
<select id='predefined_timespan' name='predefined_timespan' onChange='applyTimespan()'>
<select id='predefined_timespan' name='predefined_timespan'>
<?php
if (cacti_sizeof($graph_timespans)) {
foreach($graph_timespans as $key => $value) {
Expand Down Expand Up @@ -976,7 +976,7 @@ function flowview_display_filter(): void {
<i title='<?php print __esc('End Date Selector', 'flowview');?>' class='calendar fa fa-calendar-alt' id='endDate'></i>
</td>
<td>
<i title='<?php print __esc('Shift Time Backward', 'flowview');?>' onclick='timeshiftFilterLeft()' class='shiftArrow fa fa-backward'></i>
<i title='<?php print __esc('Shift Time Backward', 'flowview');?>' class='shiftArrow fa fa-backward timeshiftBackward'></i>
</td>
<td>
<select id='predefined_timeshift' title='<?php print __esc('Define Shifting Interval', 'flowview');?>'>
Expand All @@ -992,7 +992,7 @@ function flowview_display_filter(): void {
</select>
</td>
<td>
<i title='<?php print __esc('Shift Time Forward', 'flowview');?>' onclick='timeshiftFilterRight()' class='shiftArrow fa fa-forward'></i>
<i title='<?php print __esc('Shift Time Forward', 'flowview');?>' class='shiftArrow fa fa-forward timeshiftForward'></i>
</td>
</tr>
</table>
Expand Down Expand Up @@ -1157,10 +1157,22 @@ function flowview_display_filter(): void {
changeQuery(true);
});

$('#domains, #usenat, #exclude, #graph_type, #graph_height, #device_id, #ex_addr').off('change').on('change', function() {
$('#domains, #usenat, #exclude, #graph_type, #graph_height, #device_id, #ex_addr, #report, #sortfield, #cutofflines, #cutoffoctets').off('change').on('change', function() {
applyFilter(false);
});

$('#predefined_timespan').off('change').on('change', function() {
applyTimespan();
});

$('.timeshiftBackward').off('click').on('click', function() {
timeshiftFilterLeft();
});

$('.timeshiftForward').off('click').on('click', function() {
timeshiftFilterRight();
});

$('#flowview_filter').on('submit', function(event) {
event.preventDefault();
applyFilter(false);
Expand Down
63 changes: 63 additions & 0 deletions tests/Unit/FlowviewFilterControlsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
*/

/*
* Unit coverage for the filter controls rendered by flowview_display_filter()
* in includes/functions.php. The time-shift icons use CSP-safe
* timeshiftBackward / timeshiftForward classes (bound via a delegated handler)
* rather than inline onclick attributes.
*/

beforeAll(function () {
require_once __DIR__ . '/../../setup.php';
require_once __DIR__ . '/../../includes/functions.php';

// The full filter render calls a handful of Cacti UI helpers the unit
// harness does not otherwise need; stub the ones it does not already define.
if (!function_exists('html_start_box')) { function html_start_box(...$args) {} }
if (!function_exists('html_end_box')) { function html_end_box(...$args) {} }
if (!function_exists('title_trim')) { function title_trim($text, $length = 0) { return (string) $text; } }
if (!function_exists('html_escape_request_var')) { function html_escape_request_var($name) { return ''; } }

// includes/arrays.php (required inside flowview_display_filter) re-keys its
// device/template lookups with Cacti core's array_rekey(), which the stub
// harness does not provide; mirror core's behaviour so the require succeeds.
if (!function_exists('array_rekey')) {
function array_rekey($array, $key, $value) {
$ret = array();
if (is_array($array)) {
foreach ($array as $item) {
$k = $item[$key];
if (is_array($value)) {
foreach ($value as $v) { $ret[$k][$v] = $item[$v]; }
} else {
$ret[$k] = $item[$value];
}
}
}
return $ret;
}
}
});

it('renders the time-shift controls with CSP-safe classes and no inline handlers', function () {
ob_start();

try {
flowview_display_filter();
} catch (\Throwable $e) {
// The complete filter form references optional helpers that are not part
// of the isolated unit harness; the CSP-relevant controls are emitted
// before any such reference, so the captured output still covers them.
}

$output = ob_get_clean();

expect($output)->toContain('timeshiftBackward');
expect($output)->toContain('timeshiftForward');
expect($output)->not->toMatch('/\son(?:click|change)\s*=/i');
});
Loading