Skip to content
Draft
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ tmp/*
exports/*
*.swp
locales/po/*.mo
.omc/
18 changes: 18 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "cacti/plugin_reportit",
"description": "plugin_reportit plugin for Cacti",
"license": "GPL-2.0-or-later",
"require-dev": {
"pestphp/pest": "^1.23"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"autoload-dev": {
"files": [
"tests/bootstrap.php"
]
}
}
2 changes: 1 addition & 1 deletion lib/funct_shared.php
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ function in_process($report_id, $status = 1) {
db_execute_prepared('UPDATE plugin_reportit_reports
SET state = ?, last_state = ?
WHERE id = ?',
array($status, $now, $report_id));;
array($status, $now, $report_id));
}

function stat_process($report_id) {
Expand Down
2 changes: 1 addition & 1 deletion poller_reportit.php
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ function autorrdlist($reportid) {
FROM plugin_reportit_reports AS a
INNER JOIN plugin_reportit_templates AS b
ON a.template_id = b.id
WHERE a.id = ?', array($reportid));;
WHERE a.id = ?', array($reportid));

$sql_params = array();

Expand Down
14 changes: 14 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Pest configuration file.
*/

require_once __DIR__ . '/bootstrap.php';
80 changes: 80 additions & 0 deletions tests/Security/Php74CompatibilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify plugin source files do not use PHP 8.0+ syntax.
* Cacti 1.2.x plugins must remain compatible with PHP 7.4.
*/

$files = array(
'lib/funct_calculate.php',
'lib/funct_export.php',
'lib/funct_html.php',
'lib/funct_online.php',
'lib/funct_reports.php',
'lib/funct_shared.php',
'lib/funct_validate.php',
'setup.php',
);

$readFileContents = function (string $relativeFile): string {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
throw new RuntimeException("Failed to resolve path for compatibility check: {$relativeFile}");
}

$contents = file_get_contents($path);

if ($contents === false) {
throw new RuntimeException("Failed to read file for compatibility check: {$relativeFile}");
}

return $contents;
};

it('does not use str_contains (PHP 8.0)', function () use ($files, $readFileContents) {
foreach ($files as $relativeFile) {
$contents = $readFileContents($relativeFile);

expect(preg_match('/\bstr_contains\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_contains() which requires PHP 8.0"
);
}
});

it('does not use str_starts_with (PHP 8.0)', function () use ($files, $readFileContents) {
foreach ($files as $relativeFile) {
$contents = $readFileContents($relativeFile);

expect(preg_match('/\bstr_starts_with\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_starts_with() which requires PHP 8.0"
);
}
});

it('does not use str_ends_with (PHP 8.0)', function () use ($files, $readFileContents) {
foreach ($files as $relativeFile) {
$contents = $readFileContents($relativeFile);

expect(preg_match('/\bstr_ends_with\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_ends_with() which requires PHP 8.0"
);
}
});

it('does not use nullsafe operator (PHP 8.0)', function () use ($files, $readFileContents) {
foreach ($files as $relativeFile) {
$contents = $readFileContents($relativeFile);

expect(preg_match('/\?->/', $contents))->toBe(0,
"{$relativeFile} uses nullsafe operator which requires PHP 8.0"
);
}
});
58 changes: 58 additions & 0 deletions tests/Security/PreparedStatementConsistencyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify migrated files use prepared DB helpers exclusively.
* Catches regressions where raw db_execute/db_fetch_* calls creep back in.
*/

it('uses prepared DB helpers in migrated plugin files', function () {
$targetFiles = array(
'lib/funct_calculate.php',
'lib/funct_export.php',
'lib/funct_html.php',
'lib/funct_validate.php',
);

$rawPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)\s*\(/';
$preparedPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)_prepared\s*\(/';

foreach ($targetFiles as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

expect($path)->not->toBeFalse(
"Failed to resolve target file {$relativeFile}"
);

$contents = file_get_contents($path);

expect($contents)->not->toBeFalse(
"Failed to read target file {$relativeFile}"
);

$lines = explode("\n", $contents);
$rawCallsOutsideComments = 0;

foreach ($lines as $line) {
$trimmed = ltrim($line);

if (strpos($trimmed, '//') === 0 || strpos($trimmed, '*') === 0 || strpos($trimmed, '#') === 0) {
continue;
}

if (preg_match($rawPattern, $line) && !preg_match($preparedPattern, $line)) {
$rawCallsOutsideComments++;
}
}

expect($rawCallsOutsideComments)->toBe(0,
"File {$relativeFile} contains raw (unprepared) DB calls"
);
}
});
44 changes: 44 additions & 0 deletions tests/Security/SetupStructureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify setup.php defines required plugin hooks and info function.
*/

$setupPath = realpath(__DIR__ . '/../../setup.php');

if ($setupPath === false) {
throw new RuntimeException('Unable to resolve setup.php for structure tests.');
}

$source = file_get_contents($setupPath);

if ($source === false) {
throw new RuntimeException('Unable to read setup.php for structure tests.');
}

it('defines plugin_reportit_install function', function () use ($source) {
expect($source)->toContain('function plugin_reportit_install');
});

it('defines plugin_reportit_version function', function () use ($source) {
expect($source)->toContain('function plugin_reportit_version');
});

it('defines plugin_reportit_uninstall function', function () use ($source) {
expect($source)->toContain('function plugin_reportit_uninstall');
});

it('returns version array with name key', function () use ($source) {
expect($source)->toMatch('/[\'\""]name[\'\""]\s*=>/');
});

it('returns version array with version key', function () use ($source) {
expect($source)->toMatch('/[\'\""]version[\'\""]\s*=>/');
});
Loading