* INFO: set compat to 1.2.32 * docs: update Copilot instructions Cacti baseline to 1.2.32 (matches INFO compat) * INFO: back off compat to 1.2.29 * Add manifest.json + upgrade-time file pruning * Prune phpunit.xml on upgrade and ignore markdown-lint configs phpunit.xml ships in the repo for CI but is a dev-only test artifact, so it is removed from manifest.json 'expected', pruned from installs on upgrade (like tests/), and excluded from the manifest drift check. The retired markdown-lint configs (.mdlrc, .md_style.rb) are deleted, and .md* is now ignored like .git*: protected from pruning and excluded from drift if it reappears. * Move thold_functions.php and thold_webapi.php into includes/; flatten css The main threshold library becomes includes/functions.php and the web API helper becomes includes/webapi.php; every caller (setup.php, includes/*, the CLI/poller/notify entry points and the test suite) loads them from includes/. Each per-theme stylesheet css/<theme>/main.css becomes css/<theme>.css so users can drop their own css/<theme>.css into the css base, and the page_head hook looks there. Old paths are tombstoned so existing installs drop the stale copies on upgrade. * Regenerate cacti.pot for the includes/ file relocations Moving the plugin's library files into includes/ changed the source-reference lines in the translation template; regenerate it so the 'Verify translation template' CI check passes. * Harden the hook-include test and make RPN integer ops explicit The HookFunctionsIncludeTest invokes real hooks with placeholder input purely to execute their relocated include_once lines; it now swallows engine notices/warnings/deprecations during those calls (and restores the error handler and config afterward) so it cannot trip the suite's failOnDeprecation or leak state into other tests. The RPN % and ^ operators now cast their operands to int explicitly, matching their long-standing integer-truncation behaviour while silencing the implicit float-to-int conversion deprecation. * Harden prune against tampered-manifest path traversal The upgrade-time prune now rejects any tombstone containing '.'/'..' segments (which could escape the plugin directory or resolve to its root) and treats ancestors of whitelist entries as protected, so a tombstone on a parent directory can no longer delete a whitelisted file beneath it. * docs: update and align the Project Structure block Point the structure tree at the relocated files (libraries now under includes/, data files under docs/, stylesheets under css/) and align the trailing '# ...' comments to a single column so they no longer drift right. * docs/tests: standardize test header and correct manifest-contract notes - Use the full license header (from the plugin's own setup.php) in tests/Unit/PruneFilesTest.php instead of the abbreviated copyright banner. - Document that the manifest drift check and the upgrade-time prune also handle phpunit.xml and .md* files, matching the implemented behavior. * refactor: rename prune helpers to the plugin-slug function prefix The repository naming contract reserves the plugin_<name>_ prefix for plugin lifecycle / hook-registration functions; all other functions use the plain <name>_ prefix. Rename the internal upgrade helpers accordingly: plugin_<name>_prune_files() -> <name>_prune_files() plugin_<name>_rmtree() -> <name>_rmtree() The call site, unit tests, and the copilot-instructions.md references are updated to match. No behavioral change. * chore: set the Cacti compatibility floor to 1.2.29 Align the compat metadata (and the contributor-guide references) with the team decision to standardize the minimum supported Cacti version at 1.2.29; the previously proposed 1.2.32 floor was not adopted. --------- Co-authored-by: Copilot <copilot@users.noreply.github.com>
11 KiB
GitHub Copilot Instructions
Priority Guidelines
When generating code for this repository:
- Version Compatibility: This is a Cacti plugin (
thold, "Thresholds", version 1.8.2) targeting Cacti 1.2.29+ - Context Files: Prioritize patterns and standards defined in this file (
.github/copilot-instructions.md) - Codebase Patterns: When context files don't provide specific guidance, scan the codebase for established patterns
- Architectural Consistency: Maintain plugin-based architecture extending Cacti core
- Code Quality: Prioritize security, maintainability, and compatibility in all generated code
Technology Stack
Core Technologies
- PHP: 8.1+ (CI matrix tests 8.1-8.4)
- Platform: Cacti Plugin Architecture (Cacti 1.2.29+)
- Database: MySQL/MariaDB with InnoDB engine
- Alerting: Email, Syslog, and SNMP Traps/Informs notification channels
Key Dependencies
- Cacti core framework (
api_plugin_*,db_*) CACTI-THOLD-MIBfor SNMP trap/inform definitions- Optional:
gettextfor internationalization
Project Structure
thold/ # Repository root (install to plugins/thold/ in Cacti)
├── extras/ # Supplementary assets
├── includes/ # functions.php (core logic), webapi.php (web API), polling.php (poller hooks), settings.php (config UI), tab.php
├── service/ # systemd unit for thold_daemon
├── tests/ # Test suite (phpunit.xml)
├── css/ # CSS theme overlays
├── cli_import.php / cli_thresholds.php # CLI threshold management utilities
├── notify_lists.php / notify_queue.php # Notification list/queue administration
├── thold.php # Main threshold administration UI
├── thold_daemon.php # Standalone high-scale daemon (bypasses poller hook)
├── thold_graph.php / thold_notify.php # Graph-threshold view / notification dispatch
├── thold_process.php / thold_templates.php # Background processing / threshold templates
├── poller_thold.php # Background poller entry point (CLI)
├── INFO # Plugin metadata (name, version, compat)
├── README.md
└── setup.php # Plugin install/uninstall/upgrade hooks
Naming Conventions
Function Names
- All functions and global variables MUST be prefixed
thold_:thold_functions.php,thold_poller_output(),thold_config_settings(). - Match the existing prefix used by the function you are editing; do not introduce a new naming scheme.
Database Tables
All plugin tables are prefixed plugin_thold_.
Variables and Constants
- Access Cacti configuration via the global
$configarray; use$config['base_path']for absolute file paths.
Code Style
Indentation and Formatting
- Tabs: Use tabs (not spaces) for indentation throughout all PHP files.
- Braces: Opening brace on the same line for functions and control structures.
- Spacing: Space after control structure keywords (
if,foreach,while).
File Headers
ALL PHP files MUST include the standard GPL v2 license header used throughout this repository (see setup.php), crediting "The Cacti Group".
Security Standards
SQL Query Security
ALWAYS use prepared statements for database operations:
// CORRECT
db_execute_prepared($sql, $params);
db_fetch_assoc($sql); // only for queries with no variable input
db_fetch_cell($sql); // only for queries with no variable input
// WRONG - never concatenate request input into SQL
db_fetch_row("SELECT * FROM plugin_thold_thresholds WHERE id = $id");
Input Validation and Sanitization
Sanitize inputs using sanitize_thold_sort_string() or Cacti's built-in input validation functions (get_filter_request_var(), get_nfilter_request_var()); never read $_GET/$_POST directly.
get_filter_request_var() (and its gfrv() shorthand, where available) called with only the
$name argument (no regex/filter as the 2nd/3rd argument) already validates the value as numeric
and returns it as a string -- it does not return an int, and it halts execution if the request
value is not numeric. Because of this, do NOT cast its output to (int) when the result is only
used for string output (e.g. print/echo, string concatenation, embedding in HTML/JS); the cast
is redundant. Only cast when the value is genuinely used in an integer/numeric context (e.g.
arithmetic, strict === comparisons).
Database Operations
Use Cacti's global database functions: db_execute_prepared($sql, $params) for writes, db_fetch_assoc($sql) / db_fetch_cell($sql) for reads.
Internationalization
Use __('String', 'thold') for all user-facing strings to support internationalization.
Plugin Architecture
Data Flow
- Data Collection: Cacti poller collects data.
- Interception:
thold_poller_output()(inincludes/polling.php) receives the data. - Processing: Standard mode processes immediately within the poller hook; Daemon mode queues data for
thold_daemon.phpto process asynchronously. - Alerting: If a threshold is breached,
includes/functions.phphandles notification dispatch.
Plugin Hooks
Register hooks in setup.php (see the full list of ~30 hooks covering device/graph/data-source actions, poller integration, and template change events); keep new hooks registered the same way via api_plugin_register_hook($plugin, 'hook_name', 'callback', 'file.php').
Daemon Mode
thold_daemon.php is a standalone daemon for high-scalability environments, bypassing the standard poller hook — requires systemd service installation (service/systemd/thold_daemon.service). Keep daemon-mode processing logic in sync with the standard poller-hook processing path in includes/polling.php.
Best Practices
- Keep all new functions and globals under the single
thold_prefix. - Prefer
db_*_prepared()over string-concatenated SQL. - Keep daemon-mode and poller-hook-mode threshold evaluation logic consistent.
- Wrap all user-facing strings with
__('text', 'thold').
Common Pitfalls to Avoid
// WRONG - concatenated SQL
$sql = "SELECT * FROM plugin_thold_thresholds WHERE id = $id";
// CORRECT
$row = db_fetch_row_prepared('SELECT * FROM plugin_thold_thresholds WHERE id = ?', array($id));
Version Control
Testing changes in a safe environment is crucial, especially when dealing with database interactions and alerting mechanisms. Document all changes in CHANGELOG.md.
CI & Dependency Baselines
- Do not commit a
composer.jsonorcomposer.lockin this plugin's own repo root — the shared CI workflow installs Pest/dev dependencies into Cacti's own Composer-managed vendor tree (checked out alongside the plugin). Use Cacti'scomposer.json, not a plugin-local one. - Do not add a plugin-local
.phpstan.neon/phpstan.neonor.php-cs-fixer.php/.php-cs-fixer.dist.php— lint/static-analysis steps run against Cacti's own config from the Cacti core checkout, targeting this plugin's directory. Use the Cacti version, not a plugin-local config. - Prefer Cacti's
cacti_count()/cacti_sizeof()wrappers over the rawcount()/sizeof()builtins in new or edited code.
Internationalization (i18n)
- Translatable strings are managed with GNU gettext via
locales/build_gettext.sh.locales/po/cacti.potis the source template; Weblate owns syncing the per-language.po/.mofiles from it. - Never commit the per-language
.poor compiled.mofiles (locales/po/*.po,locales/LC_MESSAGES/*.mo) in a plugin PR. Weblate is the sole owner of those catalogs, and regenerating them here produces spurious diffs and merge conflicts.locales/po/cacti.potis the ONLY translation artifact a PR may add or modify. - When a pull request adds or changes a string wrapped in
__()/__n()/__esc()/__x()/__xn()/__gettext(), runlocales/build_gettext.shbefore pushing and stagelocales/po/cacti.potonly.build_gettext.shalso rewrites the.po/.mofiles as a side effect; revert those before committing (git checkout -- locales/po/*.po locales/LC_MESSAGES), or run only thexgettextstep that targetscacti.pot.
References
- Cacti DB Functions
- Cacti Documentation
README.mdfor feature descriptionsCHANGELOG.mdfor version history
Security & Quality Conventions
These conventions apply across the Cacti plugin fleet and should be followed whenever touching existing code or adding new code, not just in dedicated cleanup passes:
- No hardcoded third-party hosts. Never hardcode a third-party IP address, hostname, or URL in plugin code (even for tooling/download helpers). Expose it as a plugin setting instead, with secure-by-default values (e.g. an SSL-verification setting that defaults to verify-on).
- Prepared statements over
db_qstr(). Build dynamicWHEREclauses using the$sql_where/$sql_paramsprepared-statement pattern, not string concatenation viadb_qstr(). - Use
html_escape_request_var(). Prefer it over thehtml_escape(get_request_var(...))call chain. - Harden
unserialize(). Always pass['allow_classes' => false]as the second argument. - i18n text domain. Every
__()/__esc()call must include this plugin's text domain as the final argument, except when deliberately comparing against a literal, untranslated Cacti-core label. - Plugin table-creation API. Use
api_plugin_db_table_create()/api_plugin_db_add_column()(from Cacti core'slib/plugins.php) instead of rawCREATE TABLE/ALTER TABLE ... ADD COLUMN. Both are idempotent (safe no-ops when already applied), so the same call can run unconditionally from both the install AND upgrade paths. - PHPDoc shape. Every function gets a PHPDoc block: a one-line description, a blank comment
line,
@paramlines, a blank comment line, then@return. Infer parameter/return types from actual usage; don't change the function's real type-hints in the same pass (let static analysis flag mismatches separately). Skip vendored third-party library files.
File manifest & upgrade pruning
The plugin ships a root manifest.json with three arrays: tombstones (files/directories older versions shipped that have since moved or been removed), expected (the top-level files and directories that ship today, directories written with a trailing /), and whitelist (paths holding user data that must never be touched). Keep expected current: CI runs tests/bin/validate-manifest.php, which fails on any drift between expected and the real top-level tree (it ignores tests/, phpunit.xml, .git*, .md*, and whitelisted paths). Custom customer CSS/theme files belong in expected, and stylesheets live in css/ (not themes/). On upgrade, thold_prune_files() deletes the tombstoned paths, the dev-only tests/ tree, and the phpunit.xml test config, leaves whitelist, .git*, and .md* alone, and logs (without removing) any top-level entry the manifest does not account for. As a safety measure it refuses any tombstone that resolves outside the plugin directory (a tampered manifest.json) and logs a warning for any file or directory it cannot remove. When you move or delete a shipped file, add its old path to tombstones and update expected in the same change.