Skip to content
Open
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
10 changes: 10 additions & 0 deletions .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ jobs:
echo "Syntax errors found!"
exit 1
fi

- name: Run Plugin Regression Tests
run: |
cd ${{ github.workspace }}/cacti/plugins/syslog
if [ -d tests/regression ]; then
for test in tests/regression/*.php; do
[ -f "$test" ] || continue
php "$test"
done
fi


- name: Run Cacti Poller
Expand Down
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 ---

* issue#258: Execute CREATE TABLE SQL when a replicated rules table is missing
* issue: Making changes to support Cacti 1.3
* issue: Don't use MyISAM for non-analytical tables
* issue: The install advisor for Syslog was broken in current Cacti releases
Expand Down
8 changes: 7 additions & 1 deletion setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ function syslog_replace_data($table, &$data) {
$sqlData = array();
$sqlQuery = array();
$columns = array_keys($data[0]);
$create_sql = '';

$create = db_fetch_row('SHOW CREATE TABLE ' . $table);
if (isset($create["CREATE TABLE `$table`"]) || isset($create['Create Table'])) {
Expand All @@ -828,7 +829,12 @@ function syslog_replace_data($table, &$data) {
}

if (!syslog_db_table_exists($table)) {
syslog_db_execute($create);
if ($create_sql == '') {
cacti_log('WARNING: Unable to derive CREATE TABLE SQL for `' . $table . '` during Syslog replication.', false, 'REPLICATE');
return;
}

syslog_db_execute($create_sql);
syslog_db_execute("TRUNCATE TABLE $table");
Comment on lines 831 to 838
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$create_sql is only set when the SHOW CREATE TABLE result contains expected keys. If db_fetch_row() returns an empty/false value (e.g., permissions issue, table missing, or unexpected column label), this will call syslog_db_execute($create_sql) with an undefined variable and fail to create the destination table. Initialize $create_sql (e.g., to an empty string) and add a guard that logs/returns early when the CREATE statement cannot be derived before executing it.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed -- $create_sql is initialized to '' before the SHOW CREATE TABLE fetch, and the guard at line 833 returns with a warning log when the value cannot be derived.

}

Expand Down
121 changes: 121 additions & 0 deletions tests/regression/issue258_replication_create_sql_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

$existing_functions = array(
'db_fetch_row',
'syslog_db_table_exists',
'syslog_db_execute',
'syslog_db_execute_prepared'
);

foreach ($existing_functions as $existing_function) {
if (function_exists($existing_function)) {
fwrite(STDERR, "This test must run in isolated mode; function already exists: $existing_function\n");
exit(1);
}
}

if (!function_exists('cacti_sizeof')) {
function cacti_sizeof($value) {
if (is_array($value) || $value instanceof Countable) {
return count($value);
}

return 0;
}
}

$GLOBALS['syslog_replace_data_execute_calls'] = array();
$GLOBALS['syslog_replace_data_prepared_calls'] = array();
$GLOBALS['issue258_logs'] = array();
$GLOBALS['issue258_show_create'] = array('Create Table' => 'CREATE TABLE `syslog_alert` (`id` INT NOT NULL)');

if (!function_exists('db_fetch_row')) {
function db_fetch_row($sql) {
return $GLOBALS['issue258_show_create'];
}
}

if (!function_exists('syslog_db_table_exists')) {
function syslog_db_table_exists($table) {
return false;
}
}

if (!function_exists('syslog_db_execute')) {
function syslog_db_execute($sql) {
$GLOBALS['syslog_replace_data_execute_calls'][] = $sql;

return true;
}
Comment on lines +32 to +49
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regression script conditionally defines DB wrapper stubs using function_exists(). If someone runs it in an environment where Cacti/syslog functions are already loaded (so the stubs are skipped), the script will call the real syslog_replace_data() against a real DB and may create/modify syslog rule tables before failing its assertions. Consider adding an explicit safety check up front (e.g., abort when core functions like db_fetch_row/syslog_db_execute already exist) to ensure the test only runs in an isolated/stubbed context.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regression test runs in isolation via PHP CLI, not within the Cacti runtime. The function_exists() guards prevent redefinition errors if test helpers are shared across test files in the same run.

}

if (!function_exists('syslog_db_execute_prepared')) {
function syslog_db_execute_prepared($sql, $params) {
$GLOBALS['syslog_replace_data_prepared_calls'][] = array(
'sql' => $sql,
'params' => $params
);

return true;
}
}

if (!function_exists('cacti_log')) {
function cacti_log($message, $output = false, $facility = 'SYSTEM', $level = '') {
$GLOBALS['issue258_logs'][] = $message;
}
}

require_once dirname(__DIR__, 2) . '/setup.php';

$data = array(
array(
'id' => 1,
'hash' => 'abc123',
'name' => 'sample'
)
);

syslog_replace_data('syslog_alert', $data);

$executed = $GLOBALS['syslog_replace_data_execute_calls'];

if (cacti_sizeof($executed) < 2) {
fwrite(STDERR, "Expected CREATE + TRUNCATE calls to run.\n");
exit(1);
}

if ($executed[0] !== 'CREATE TABLE `syslog_alert` (`id` INT NOT NULL)') {
fwrite(STDERR, "Expected CREATE TABLE SQL to be executed from create_sql.\n");
exit(1);
}

if ($executed[1] !== 'TRUNCATE TABLE syslog_alert') {
fwrite(STDERR, "Expected TRUNCATE TABLE to run after CREATE TABLE.\n");
exit(1);
}

$prepared = $GLOBALS['syslog_replace_data_prepared_calls'];

if (cacti_sizeof($prepared) !== 1) {
fwrite(STDERR, "Expected a single prepared INSERT statement execution.\n");
exit(1);
}

$GLOBALS['syslog_replace_data_execute_calls'] = array();
$GLOBALS['syslog_replace_data_prepared_calls'] = array();
$GLOBALS['issue258_show_create'] = false;

syslog_replace_data('syslog_alert', $data);

if (cacti_sizeof($GLOBALS['syslog_replace_data_execute_calls']) !== 0) {
fwrite(STDERR, "Expected no execute calls when CREATE TABLE SQL is unavailable.\n");
exit(1);
}

if (cacti_sizeof($GLOBALS['syslog_replace_data_prepared_calls']) !== 0) {
fwrite(STDERR, "Expected no prepared inserts when CREATE TABLE SQL is unavailable.\n");
exit(1);
}

echo "issue258_replication_create_sql_test passed\n";