Files
Cactiplugin_thold/.github/copilot-instructions.md
T
TheWitnessandCopilot 9b217e0e3a INFO: set compat to 1.2.29 (#838)
* 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>
2026-10-01 01:40:00 -05:00

11 KiB

GitHub Copilot Instructions

Priority Guidelines

When generating code for this repository:

  1. Version Compatibility: This is a Cacti plugin (thold, "Thresholds", version 1.8.2) targeting Cacti 1.2.29+
  2. Context Files: Prioritize patterns and standards defined in this file (.github/copilot-instructions.md)
  3. Codebase Patterns: When context files don't provide specific guidance, scan the codebase for established patterns
  4. Architectural Consistency: Maintain plugin-based architecture extending Cacti core
  5. 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-MIB for SNMP trap/inform definitions
  • Optional: gettext for 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 $config array; 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

  1. Data Collection: Cacti poller collects data.
  2. Interception: thold_poller_output() (in includes/polling.php) receives the data.
  3. Processing: Standard mode processes immediately within the poller hook; Daemon mode queues data for thold_daemon.php to process asynchronously.
  4. Alerting: If a threshold is breached, includes/functions.php handles 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

  1. Keep all new functions and globals under the single thold_ prefix.
  2. Prefer db_*_prepared() over string-concatenated SQL.
  3. Keep daemon-mode and poller-hook-mode threshold evaluation logic consistent.
  4. 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.json or composer.lock in 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's composer.json, not a plugin-local one.
  • Do not add a plugin-local .phpstan.neon/phpstan.neon or .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 raw count()/sizeof() builtins in new or edited code.

Internationalization (i18n)

  • Translatable strings are managed with GNU gettext via locales/build_gettext.sh. locales/po/cacti.pot is the source template; Weblate owns syncing the per-language .po/.mo files from it.
  • Never commit the per-language .po or compiled .mo files (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.pot is 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(), run locales/build_gettext.sh before pushing and stage locales/po/cacti.pot only. build_gettext.sh also rewrites the .po/.mo files as a side effect; revert those before committing (git checkout -- locales/po/*.po locales/LC_MESSAGES), or run only the xgettext step that targets cacti.pot.

References

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 dynamic WHERE clauses using the $sql_where/$sql_params prepared-statement pattern, not string concatenation via db_qstr().
  • Use html_escape_request_var(). Prefer it over the html_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's lib/plugins.php) instead of raw CREATE 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, @param lines, 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.