Commit Graph
59 Commits
Author SHA1 Message Date
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
TheWitness 41dc700357 Add PHPDoc blocks to undocumented functions (partial - thold_functions.php follow-up owed) (#828)
* Add PHPDoc blocks: small files batch (cli_import, poller_thold, includes/tab, includes/database, cli_thresholds, thold_notify, notify_queue)

* Add PHPDoc blocks: includes/polling.php, includes/settings.php (WIP checkpoint)

* Add PHPDoc blocks: thold_process.php, thold_daemon.php (WIP checkpoint)

* Add PHPDoc blocks: thold_webapi.php (WIP checkpoint)

* Add PHPDoc blocks: thold.php (WIP checkpoint)

* Add PHPDoc blocks: thold_graph.php (WIP checkpoint)

* Add PHPDoc blocks: notify_lists.php (WIP checkpoint)

* Add PHPDoc blocks: thold_templates.php (WIP checkpoint)

* Add PHPDoc blocks: setup.php batch 1-2 (WIP checkpoint)

* Add PHPDoc blocks: setup.php batch 3-4 (WIP checkpoint)

* Add PHPDoc blocks: setup.php batch 5 (WIP checkpoint)

* Add PHPDoc blocks: setup.php batch 6-7 (WIP checkpoint)

* Add PHPDoc blocks: setup.php complete

* Address review feedback and refresh translation template

- Fix thold_update_host_status() return type (int|null, since it
  falls through with no explicit return in the normal case)
- Fix @global $device_change_fields description in
  thold_config_arrays() (appends this plugin's fields, not unused)
- Fix @global $config description in thold_graph_button() (used to
  build the Create Threshold link's URL)
- Fix thold_data_source_action_prepare()/thold_graphs_action_prepare()
  return types (array|void; only the non-plugin_thold_create branch
  returns $save)
- Fix @global $timearray description in list_tholds() (used to render
  each baseline threshold's reference time range)
- Fix @global $thold_types description in thold_show_log() (used to
  render each log row's type column)
- Regenerate locales/po/cacti.pot (source line references only)
- Fix .gitignore pattern for compiled .mo files (locales/LC_MESSAGES/, not locales/po/)

* test(coverage): allowlist untestable entry points touched by PHPDoc pass

This PR only adds PHPDoc blocks (no functional changes), but touching every file in the plugin newly triggers the 'Enforce coverage of changed lines' CI gate for files that were never require()'d by any test before now:

- cli_import.php, cli_thresholds.php: CLI entry points (include cli_check.php / chdir + execute at the top level).

- poller_thold.php: poller entry point, same category.

- thold.php, thold_graph.php, thold_templates.php, notify_lists.php: authenticated web entry points (chdir + include(auth.php) at the top level), like notify_queue.php/thold_notify.php already allowlisted above.

- thold_daemon.php: daemon entry point, same category as thold_process.php already allowlisted above.

- includes/settings.php, includes/tab.php: Settings-page/header-tab-bar hook callbacks, only meaningful inside a live Cacti page render.

- thold_webapi.php: web-page action handlers that read \ and the database directly.

None of these can be require()'d into the isolated unit test process, matching the existing allowlist entries' precedent exactly. Verified locally: running patch-coverage.php against the PR's actual base ref confirms all 11 files are no longer reported as unexpected-unmeasured.

* docs: add PHPDoc blocks to remaining thold_functions.php functions

* chore: refresh translation template after PHPDoc line-number shifts
2026-09-25 12:30:04 -04:00
Thomas VincentandTheWitness 38dec82d55 fix: keep counter sample time pairs synchronized (#817)
* fix: preserve counter sample time pairs

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: preserve samples in daemon and poller paths

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: exercise poller sample persistence

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: bound counter gap recovery

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* style: format counter recovery tests

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: treat unusable counter baselines as unknown

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: preserve unknown counter maximum semantics

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: cover validated poller interval fallback

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: retain trustworthy counter baselines

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: propagate unknown rate samples safely

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: validate effective counter step

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: preserve alerts across unknown rate gaps

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: preserve unknown values through transforms

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: fail closed on missing expression sources

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: keep unavailable thresholds eligible

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: preserve cached expression rates

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: deduplicate backward-clock warnings

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: adopt shared Cacti plugin CI workflow, add Security/Integration coverage

- Replace plugin-ci-workflow.yml with the Cacti/plugin_evidence-based template: pinned actions, CACTI/COMPOSER_ALLOW_SUPERUSER global env vars, sudo on every composer command, and an actual Pest execution step (previously missing - this workflow never ran Pest at all).
- Remove php-unit-tests.yml; its Docker-based Pest run is now redundant with plugin-ci-workflow.yml's Pest step.
- Preserve this plugin's existing, more advanced tests/bootstrap-unit.php, TestCase.php, CactiStubs helper and pinned tests/.cacti-version (1.2.31, strict match) rather than overwriting with plugin_evidence's simpler generic versions - only additive stubs (api_plugin_register_hook/realm, get_current_page, plugin_test_read_source) were appended.
- Add tests/Security (SetupStructureTest, Php82CompatibilityTest) and tests/Integration (PluginLifecycleTest covering plugin_thold_version/uninstall/upgrade gating) to round out the Security/Unit/Integration directory convention used across other plugins.
- Update phpunit.xml to run all three suites and include setup.php as covered source.

* ci: retrigger checks

* fix: address Copilot review findings

- includes/polling.php: stop manufacturing a numeric zero for an unavailable current sample when thold_consider_unknown_zero is enabled - it was bypassing thold_check_threshold()'s new unavailable-sample guard entirely. Preserve the informational NOTE/debug logging.
- thold_functions.php: thold_build_cdef() now fails closed (returns '') when a |query_...| CDEF operand cannot be resolved, instead of silently continuing arithmetic on a manufactured zero and returning a fabricated numeric result. Added TholdBuildCdefTest.php covering the literal, unresolved-operand, and resolved-operand paths.
- tests/Unit/TholdCalculateLowerUpperTest.php: updated to match thold_calculate_lower_upper()'s current fail-closed contract (returns '' when the high word is absent/out of range, previously asserted stale pass-through values) and its float return type; added coverage for a non-numeric current value and an out-of-range high word.
- .github/workflows/plugin-ci-workflow.yml: drop PHP 8.1 from the matrix (Pest 3 requires PHP >= 8.2) and add a pcov coverage driver to the setup-php step so --coverage-clover does not fail with 'No code coverage driver available'.

* ci: replace plugin-ci-workflow.yml whole-cloth from plugin_evidence

A prior merge did not cleanly bring in plugin_evidence's current CI
template, leaving this file in an inconsistent state. Replaced it
entirely with plugin_evidence's .github/workflows/plugin-ci-workflow.yml,
with only the light corrections needed for this plugin:

- evidence -> thold in checkout path, plugin_manage --plugin, .cacti-version
  path, and coverage upload path
- Install System Dependencies keeps apache2 (needed by the later
  Configure Apache step; evidence's own template omits it)
- Run Pest Unit Tests step drops the trailing tests/Security path arg
  since thold's phpunit.xml testsuite already covers
  tests/Security, tests/Unit, and tests/Integration
- Actions pinned to their latest release commit SHA
  (actions/checkout, actions/upload-artifact, shivammathur/setup-php),
  COMPOSER_ALLOW_SUPERUSER env var and an explicit coverage: xdebug
  driver added, and sudo added to the composer validate/lint/phpcsfixer/
  phpstan invocations for consistency with the rest of the workflow

* fix(tests): tolerate the moving 1.2.x/develop branch in bootstrap-unit.php

The new workflow's 'Set expected Cacti version' step writes the literal
branch name (env.CACTI, e.g. '1.2.x') into tests/.cacti-version, but this
file's version check compared it verbatim against the checked-out
cacti_version file (e.g. '1.2.32'), throwing on every run. Matches the
exception plugin_evidence's bootstrap-unit.php already uses.

* fix(ci): run composer unprivileged so Pest's vendor cache is writable

Setup Permissions chowns the checkout to www-data:runner (group-writable) before Composer runs. Running composer install as root via sudo left include/vendor owned by root, so the later non-root 'Run Pest Unit Tests' step couldn't create pestphp/pest-plugin-mutate's cache directory (mkdir(): Permission denied). Composer no longer needs sudo here: the runner user is already a member of the runner group with write access from Setup Permissions, so dropping sudo leaves vendor/ owned by runner and writable by the later Pest step. Applied the same fix to the (currently dead, CACTI==1.2.x-gated) lint/phpcsfixer/phpstan steps for consistency.

* fix(ci): restore vendor ownership for Pest instead of running composer unprivileged

Running composer without sudo failed differently: Setup Permissions chowns the whole checkout to www-data:runner before Composer runs, and composer's own file-touching (utime) on files it does not own fails even with group-write access (touch(): Utime failed: Operation not permitted) - group write only covers content changes, not mtime updates, which require file ownership. Revert to running composer as root (matching Setup Permissions/Install Cacti via CLI's existing pattern) and add an explicit 'Restore vendor ownership for Pest' step that chowns include/vendor and composer.lock to runner:runner right after Composer finishes, so the later non-root 'Run Pest Unit Tests' step can write pestphp/pest-plugin-mutate's cache directory and Pest's own result cache under vendor/.

* fix(thold): bind graph_id parameter, quote shell tag substitutions

Root-caused and fixed the actual source bugs behind ~40 of the 55 failing
Pest tests (the rest are pre-existing/unrelated or test-stub mismatches
fixed separately):

- get_allowed_thresholds()/get_allowed_threshold_logs(): $graph_id was
  still interpolated directly into the WHERE clause (`gl.id=$graph_id`)
  and the row/count queries used unprepared db_fetch_assoc()/
  db_fetch_cell(), even though GetAllowedThresholdsTest and
  OptionalCoreFunctionTest were already written expecting a bound `gl.id
  = ?` placeholder via db_fetch_assoc_prepared()/db_fetch_cell_prepared().
  Added a new $sql_params parameter for the caller's own WHERE-clause
  bindings (appended before the graph_id placeholder), matching the
  tests' expected bind order and query text exactly. No existing caller
  passes $graph_id, so this is behavior-preserving for them.

- thold_replace_threshold_tags(): added the $shell parameter
  TholdReplaceThresholdTagsTest/TholdCommandExecutionTest already call it
  with. Device/threshold-derived values (DESCRIPTION, HOSTNAME, LOCATION,
  SITE, CURRENTVALUE, NOTES, DNOTES/DEVICENOTE, EXTERNALID, THRESHOLDNAME)
  are now escapeshellarg()'d when $shell is true; email/HTML callers are
  unaffected. thold_command_execution() now passes $shell=true when
  building the exec()/queued command for all three breach directions -
  the exec() call sites already carried a comment claiming
  "cacti_escapeshellarg protection" that did not actually exist.

- NotificationQueueClaimTest: cacti_log() calls are now recorded into
  CactiStubs::$calls (added elsewhere in this PR, needed by
  TholdGetCurrentvalTest), which added an extra 'cacti_log' entry to 3
  assertions that expected an exact list/emptiness of $calls. Updated the
  3 expectations to include it.

- NotificationEmailDeduplicationTest: stubbed the wrong function
  (db_fetch_assoc instead of db_fetch_assoc_prepared, which is what
  process_device_notifications() actually calls), so the fixture record
  was never returned and no mail was ever built. Fixed the stub name; the
  sha256 dedup path it asserts on was already implemented correctly.

Verified locally against a real Cacti 1.2.x checkout with a Windows PHP
CLI (GetAllowedThresholdsTest, OptionalCoreFunctionTest,
NotificationEmailDeduplicationTest, TholdReplaceThresholdTagsTest all
green; TholdCommandExecutionTest and 3 NotificationQueueClaimTest cases
only fail locally on Windows due to escapeshellarg()'s CRT-quoting
convention and missing /bin/*, posix_*, LOG_LOCAL0 - all Linux-only,
matching the actual CI environment).

* fix(thold): correct command-output log topic, harden THOLDTYPE test isolation

- thold_command_execution() called thold_process_command_output() with
  topic 'thold' but the function's switch only matches 'thold_cmd'/
  'thold_dhost_cmd'/'thold_uhost_cmd', so cacti_log() was silently never
  called for inline (non-queued) command execution. Fixed the topic
  string, restoring the exit-status/output logging
  TholdCommandExecutionTest's inline-execution tests check for.

- TholdReplaceThresholdTagsTest::setUpBeforeClass() loaded
  includes/arrays.php via the shared require_once-based test loader,
  which is a no-op if an earlier test class in the same run already
  loaded the same real path, so \ is not guaranteed to reach
  this class's global scope. Force a fresh include as a fallback so
  <THOLDTYPE> substitution is not dependent on suite run order.

* fix(thold): fix RPN evaluator crashes and silent bad-result pushes

- The v1==0 && v2==0 && operator=='/' special case (0/0 -> 0) had a stray
  break that exited the switch before the array_push() below it ran, so
  the special-cased result was popped off but never pushed back, leaving
  the stack empty instead of [0].
- The zero-divisor guard only covered '/', so '%' with a zero divisor fell
  through to eval('... % 0'), which throws an uncaught DivisionByZeroError
  on PHP 8 instead of flagging rpn_error. Extended the guard to also cover
  modulo.
- Unary functions (SQRT, LOG, etc.) evaled their operand without checking
  it was numeric first, so a non-numeric operand (e.g. the 'U'/unknown
  sentinel) produced an undefined-constant Error instead of flagging
  rpn_error. Added the same is_numeric guard the binary path already had.
- Unary functions that produce a mathematically undefined result (sqrt of
  a negative, log of zero/a negative) silently pushed NAN/-INF onto the
  stack instead of flagging an error, which would have compared as false
  against every threshold. Now flags rpn_error and drops the result
  instead of pushing it.

* fix(thold): log time-based restoral even when it does not cross the retrigger threshold

thold_check_threshold()'s time-based arm (case 2) only logged a restoral
(ST_NOTIFYRS) inside the two branches that also send a notification, each
gated on the freshly re-queried failure/warning-failure counts reaching
their trigger. When a threshold had been alerting but the fresh counts
did not reach trigger again (the common case, since those counts measure
consecutive alert rows in the log rather than the prior persisted state),
execution fell into the bare final else, which reset the fail counters
but never logged the restoral or cleared thold_alert - so an operator
never saw the all-clear in the log and the threshold stayed marked as
alerting. Added a branch that logs ST_NOTIFYRS and resets thold_alert to
normal whenever the threshold was previously alerting, without sending a
notification, matching this arm's documented behavior of logging every
restoral while only mailing on retrigger.

* fix: address remaining Copilot review findings on PR #817

- get_current_value(): return missing_value instead of round()-ing a
  non-numeric ('U'/'nan'/blank or fail-closed CDEF) sample, avoiding a
  PHP 8 TypeError and manufactured-zero alerting.
- PluginLifecycleTest: reset CactiStubs in beforeEach so assertions on
  global call history/queued returns do not depend on suite order.
- README: restore the notification-queue retry/worker-claim docs that
  were dropped instead of retained alongside the new sampling section.

* security: fail-closed RPN evaluator errors; shell-escape thold_expand_string()

thold_functions.php:

- thold_calculate_expression() converted an RPN evaluation error (unbalanced
  expression, unsupported token, divide/modulo-by-zero) into a numeric 0
  return. 0 passes is_numeric(), so includes/polling.php's fail-closed
  "unavailable sample" guard (which only catches non-numeric values) never
  saw it, and the poller could alert/recover on a manufactured zero instead
  of treating the reading as unavailable. Both error returns now return ''
  instead, matching every other early-return failure path already in this
  function.

- thold_expand_string() gained a $shell parameter (mirroring
  thold_replace_threshold_tags()'s existing $q()/escapeshellarg() pattern).
  It previously called Cacti core's expand_title() unconditionally, which
  substitutes |host_*|/|query_*| tokens (hostname, SNMP community/password,
  sysDescr/sysContact/sysLocation, polled data-query fields, etc) with no
  shell escaping. Any of those values can be set by the polled device
  itself, so thold_command_execution() calling thold_expand_string() after
  thold_replace_threshold_tags($shell=true) reintroduced shell injection
  despite the initial hardening, since expand_title() ran unescaped on the
  already-quoted command string.

  In $shell mode, each |host_*|/|query_*| token in the string is now
  resolved in isolation through the same core substitute_host_data()/
  substitute_snmp_query_data() helpers expand_title() itself uses (so the
  resolved value matches), then escapeshellarg()'d before it's substituted
  into the command. The plugin's own direct substitutions (data source
  name/description, graph title, interface speed) and its
  thold_substitute_custom_data()/thold_substitute_host_data() helpers are
  now shell-aware the same way. Non-shell callers (email/HTML rendering)
  are unaffected; thold_command_execution()'s three exec() paths now pass
  $shell = true.

tests/Unit/TholdCalculateExpressionTest.php: updated the two existing
error-path assertions from 0 to '', and added divide-by-zero/modulo-by-zero
coverage.

tests/Unit/TholdExpandStringTest.php + tests/bootstrap-unit.php: added
substitute_host_data()/null_out_substitutions() stubs and a test asserting
a malicious |host_description| value is escapeshellarg()'d in shell mode
and left untouched otherwise.

* security: shell-escape the <URL> tag in trigger commands

The <a href='...'>...</a> markup substituted for <URL> contains literal
single quotes. In \ mode this landed in the command unquoted, so
the quotes could terminate the shell argument early (and any
metacharacters in the configured base_url would remain executable)
despite the rest of thold_replace_threshold_tags()'s hardening. The
whole substituted value is now passed through the same \() helper as
every other tag, quoting it as one token in shell mode while leaving
email/HTML output unchanged. Added a regression test.

* fix: address remaining Copilot review findings on PR #817 (round 2)

- Guard modulo against fractional zero divisors: the % operator truncates
  both operands to int, so a divisor like 0.5 became 0 and reached eval(),
  throwing an uncaught DivisionByZeroError instead of failing closed.
- Shell-escape <DSNAME> in thold_replace_threshold_tags(); every other
  substituted value already went through the $q() helper in shell mode.
- Apply the same lastchanged/acknowledgment restoral side effects in the
  early-restoral branch of thold_check_threshold() as the two full restoral
  branches above it, so an acknowledged time-based alert that recovers
  before either failure count retriggers no longer stays acknowledged.
- Log daemon sample-persistence failures unconditionally via
  thold_cacti_log() instead of the --debug-gated thold_daemon_debug(), so
  sample loss is visible in normal daemon operation.
- Restore the changed-line coverage gate the shared CI workflow dropped
  when it replaced the old Docker-based Pest workflow: fetch full history
  for the plugin checkout and run tests/bin/patch-coverage.php against the
  PR base SHA. Allowlist thold_process.php (daemon entry point, cannot be
  required into the isolated unit test process).
- Add/extend unit tests covering each of the above.

* fix(ci): measure includes/polling.php for changed-line coverage

phpunit.xml's <source><include> only listed thold_functions.php and
setup.php, so Clover never recorded hits for includes/polling.php even
though it has no top-level side effects and is already require()'d
directly by PollerSchedulingTest. patch-coverage.php then correctly
flagged this PR's changes to thold_poller_output() (the new
thold_polling_sample_row()/status-row insert path, rrd_heartbeat column)
as changed-but-unmeasured production lines and failed the build.

Add includes/polling.php to the source include list, and add tests
exercising thold_poller_output()'s non-daemon path for both outcomes:
a numeric reading persisted as a sample_row, and an unavailable reading
persisted as a status-only row, plus the post-write cleanup call.

* test: cover remaining changed lines flagged by patch-coverage

Adds/extends unit tests for lines the coverage gate flagged as changed
but unexercised:

- includes/polling.php: the thold_consider_unknown_zero "would have
  been zero" debug/log branch in thold_poller_output()'s non-daemon
  path (still preserves the unavailable state either way).
- thold_functions.php: thold_substitute_host_data()'s cache-hit and
  build-fresh (|host_management_ip|) paths, and
  thold_substitute_custom_data()'s shell-escaped custom value - neither
  had any direct test coverage before. New TholdSubstituteHostDataTest.
- thold_functions.php: thold_expand_string()'s |query_ifSpeed| fallback,
  the SNMP-query-resolved host/query token branch in $shell mode, and
  the top-level |host_management_ip| fallback that reads $device_id
  from Cacti's real lib/variables.php (a side effect of an include()
  inside the function's own scope). The last of those needs its own
  one-off library_path fixture rather than the shared one, since
  include_once() means the shared fixture's injected value would only
  ever take effect on the first call across the whole test run.
- thold_functions.php: get_current_value()'s fail-closed return for a
  non-numeric stored RRD value (matches the existing empty-value-series
  contract already covered next to it).

* fix: gate sample pair advancement on interval eligibility; resolve tags before quoting; add complete headers

* test: cover rrd_heartbeat/rrd_step fallback branches in sample eligibility

* fix: re-anchor stale rate baselines and stop re-scanning substituted values

thold_sample_persistence() froze lasttime/oldvalue once a sample arrived
after a heartbeat-exceeding gap, but thold_get_currentval() already fails
that cycle's rate closed using the pair as it stood before this call, so
freezing bought no extra protection while permanently disqualifying every
later poll (whose elapsed time is measured against that same frozen
lasttime and only grows). Always re-anchor to the current sample instead;
thold_sample_interval_eligible() now only decides whether to log the
re-anchor.

thold_substitute_custom_data() and thold_expand_string() (both its
host/query token loop and its |graph_title|/|data_source_description|/
|data_source_name| substitutions) quoted each value and then applied it
with str_replace(), one token at a time. A later str_replace() call
re-scans the whole string, including text just inserted by an earlier
one, so a value that happens to contain another token's literal
placeholder could be substituted a second time outside of its own
quoting. Build a token => quoted-value map per substitution site and
apply it in a single strtr() pass instead, same as
thold_replace_threshold_tags(). Also switched thold_expand_string()'s and
thold_substitute_custom_data()'s shell-escaping to the established
cacti_escapeshellarg() wrapper instead of raw escapeshellarg(), matching
every other quoting site in this file.

Added regression coverage: a second poll recovering a real rate after a
stale-gap re-anchor, and a re-scan test for each of the three affected
substitution sites.

* fix: close cross-phase/cross-function shell re-scan and log flooding

thold_expand_string()'s per-phase strtr() passes were each individually
re-scan-safe, but a value resolved in one phase (host/query, custom data)
could still contain another phase's token literal text (e.g. a device
description of |data_source_name|) and get re-substituted by that later
phase, breaking out of the earlier phase's quoting. Separately,
thold_command_execution() called thold_replace_threshold_tags() (quoting
<TAG> values) and then passed the result into thold_expand_string(), which
re-scans its input for |pipe| tokens - a <TAG> value containing a literal
pipe token (e.g. a description of |host_hostname|) was expanded a second
time with the real, unescaped device value spliced inside the tag's
quoting.

Introduce thold_defer_placeholder(): every substitution that needs shell
quoting now inserts an opaque, non-token placeholder instead of its quoted
value immediately, and records the real value in a  map. Thread
that map through thold_expand_string(), thold_substitute_custom_data(),
thold_substitute_host_data(), and thold_replace_threshold_tags(); each
resolves its own map internally when called standalone (preserving
existing behavior/tests), or accepts a caller-supplied map by reference to
defer resolution further. thold_command_execution() now expands pipe
tokens first, then threshold tags, sharing one deferred map across both
calls and resolving every placeholder in a single final strtr() only after
all token scanning for the command is complete.

Also stop thold_sample_persistence()'s stale-gap re-anchor warning from
firing on every poll of a persistently stale source: it now only logs on
the transition (prior lastread was still numeric), matching
thold_log_unavailable_transition()'s own transition-only pattern.

Renamed testStaleGapDoesNotAdvanceARateBearingPairButOtherCasesStillAdvance
to testStaleGapReanchorsARateBearingPairAndAllOtherCasesStillAdvance, since
the pair now always re-anchors.

Added regression coverage for the cross-phase reorder within
thold_expand_string(), the cross-function reorder in
thold_command_execution(), and the transition-only re-anchor warning.

* fix: preserve unavailable sentinel, prepared sample inserts, PHP 8.4 nullability

thold_calculate_expression()'s pipe-token branch converted an empty (failed)
thold_expand_string() result to '0' after the resolution attempt, so an
unresolved |query_*| or host token became a manufactured valid zero instead
of failing the expression closed like every other branch in this function.
Drop that fallback; an empty operand now falls through the RPN loop's
unsupported-field case and returns '' as intended.

thold_polling_sample_row() built its sample row as a raw SQL value tuple
string (id, tcheck, quoted lastread, FROM_UNIXTIME(lasttime), quoted
oldvalue) for direct concatenation into an unprepared db_execute() call,
unlike the status-only row it introduced alongside it. It now returns the
same structured fields as the status row, and includes/polling.php builds
'(?, ?, ?, FROM_UNIXTIME(?), ?)' placeholders with a flattened parameter
list for db_execute_prepared(), matching the status-only path.

rray & = null is an implicitly nullable parameter, deprecated
in PHP 8.4; phpunit.xml enables failOnDeprecation, so loading this file
could fail the PHP 8.4 CI job. Declared explicitly as ?array in all 4
functions that take it.

Added a regression test for the unresolved-pipe-token fail-closed fix, and
updated the sample-row assertions in TholdGetCurrentvalTest and
PollerSchedulingTest for the new structured/prepared-statement shape.

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-09-21 04:03:48 -07:00
Thomas Vincent d2e3b0ee14 fix(thold): correct counter delta, wrap modulus and percent denominator (#791)
* test: add the PHP 8.1 unit-test harness

Same harness as #773 and #788, with gmp added to the image so the 64-bit
counter arithmetic can be tested exactly.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(thold): correct the counter delta and the percent denominator

A previous counter reading of exactly zero was treated as no reading at all,
so the first interval after a device reboot reported a rate of zero. The wrap
modulus was 2^32-1 and 2^64-1 rather than 2^32 and 2^64, losing one count per
wrap, and the 64-bit literal exceeded PHP_INT_MAX so it was parsed as a float
and lost about eleven bits before the subtraction.

The percent-of denominator was cast to int, so a denominator below one
truncated to zero and forced the result to zero, keeping any configured low
threshold in permanent breach.

Refs #785

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(daemon): store the previous reading in oldvalue, not a timestamp

When a data source produced no sample this cycle the daemon wrote
$currenttime - $rrd_step into oldvalue, so the next poll computed a delta
against a Unix timestamp, took the overflow branch and fabricated a rate in
the billions. The non-daemon path already carries the previous oldvalue
forward; this matches it.

Refs #785

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: follow Cacti's test layout and composer scripts

Adopts the conventions from Cacti core: tests/bootstrap-unit.php, tests/Helpers
for the stubs, a phpunit.xml carrying error_reporting -1 and
CACTI_TEST_BOOTSTRAP, and composer lint / test / test:coverage scripts so CI
runs the same commands a developer does. The dev toolchain is Cacti's, pinned
to the same platform php 8.1.0.

Cacti core runs Pest and this suite does not, because pest ^2 does not resolve
on PHP 8.1 -- the platform Cacti's own composer.json pins. Releases up to
v2.36.0 conflict with phpunit 10.5.62 and later, every earlier 10.x release is
blocked by advisory PKSA-z3gr-8qht-p93v, and v2.36.1, which does resolve,
requires PHP 8.2. The stack installs on 8.2 and above; 8.1 is the floor this
plugin's CI matrix targets. The tests are written in the plain PHPUnit class
style that Cacti's tests/Pest.php explicitly supports, so they run unchanged
under Pest wherever it is installable.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci: keep plugin PR integration checks on pinned Cacti

* fix(counter): handle non-integer 64-bit readings safely

* ci: bound package index refreshes

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-17 22:00:04 -04:00
TheWitness 0f526404f7 Updating copyrights to 2026 (#740) 2026-02-14 14:16:33 -05:00
TheWitnessandCopilot 2d3b623d7a Updating workflow to include code validation PHP8.4 and MariaDB 10.6 (#739)
* Adding Worflows for code validation

* Apply php-cs-fixer rules

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-06 06:59:30 -05:00
TheWitness a7991dc6cf QA: Update Copyrights to 2025 2025-06-16 09:26:09 -04:00
TheWitness 4b14a89172 Allow Templates and Thresholds to have an External ID
feature: Allow Templates and Thresholds to include an External ID
2024-01-30 20:22:37 -05:00
TheWitness fc61176e04 Fixing #635 Non-Templated Thold Issue 2023-12-03 13:43:50 -05:00
TheWitness 2ca8a0083c Fixing #635 and notification delay
* Problem with non-templated tholds
* This notification delay change is not final.
* Users running the 1.8 Alpha will have to re-upgrade their databases
2023-11-11 15:14:16 -05:00
TheWitness d5505511c1 Fixing Issue #627 - Thold Still Triggering when disabled
Threshold enabled setting issues, templated and non templated
2023-10-07 11:14:00 -04:00
TheWitness 78a2ea1e03 QA: Fixing #625 - Enable/Disable Thold
This is an interim fix and needs additional QA.  You may have to resave your templates to repair them too.
2023-10-04 08:07:35 -04:00
TheWitness f80527beca QA: Fixing register_process() vs. register_process_start() process 2023-08-19 08:15:07 -04:00
Jing Chen c51e51d1e9 Fixed: Apply Cacti 1.2.25 function cacti_unserialize to support both of PHP 5.4 and 7.0+ (#604) 2023-08-09 07:14:09 -04:00
netniV 4d9210e76b Update copyright for 2023 2023-01-04 22:31:06 +00:00
TheWitness 9e7443b3f0 Updating copyrights 2023-01-03 19:38:50 -05:00
TheWitness 33a1342835 Fixing Issue #542 and Copyright Years 2022-02-13 13:21:17 -05:00
TheWitness 3de7e47568 Fix Issue where tholds processed incorrectly by the daemon 2021-09-05 17:59:27 -04:00
TheWitness b76282bf3d Update tholds error_handler function 2021-09-05 10:37:47 -04:00
Mr. Smith 203afc7dba Resolving two issues
* Last process detected as hung continually
* Tholds calculating for cdef even though the thold does not call for it
* Minor log changes
2021-08-31 15:05:31 -04:00
TheWitness 5630daa0e1 Fixing Issue #525 Thold Daemon improperly adjust the thold by it's cdef twice
There are a few minor additional changes that do not impact the data.
2021-08-31 13:12:27 -04:00
TheWitness 1e60a78b1a Enhance logging, catch up CHANGELOG.md 2021-08-28 15:26:53 -04:00
TheWitness bded06c163 More logging, use json over serialize, new daemon debug option
This change should result in a functional daemon now.  It does the following:

* Uses JSON objects in the daemon data table instead of serialized objects
* Implements a new daemon debug option
* Renaming come functions to support daemon debug
* Instrumenting the thold_process.php script to add logging
* Correct some timestamp issues that were preventing processing
2021-08-28 15:13:43 -04:00
TheWitness 6ba01d1136 Change the sleep interval for a database down situation to 60 seconds make process heartbeats regularily 2021-08-28 10:04:01 -04:00
TheWitness 447345aa0e Thold daemon enhancement for testing by others
This patch requires that you restart your thold daemon.  There are table changes, and the minimum cacti release is 1.2.17.  Also, there is a lib/poller.php fix that needs to be implemented.
2021-08-27 23:49:46 -04:00
browniebraun 945f81aa0f small clean up 2021-01-13 23:49:42 +01:00
Manuel MauszandTheWitness 11063b63b6 Add support for upper + lower bytes calculation (#443)
Using this data type manipulation allows to calculate the total value
from upper and lower 4 bytes of data. e.g. OID dskAvailLow + dskAvailHigh

See also http://www.muonics.com/Docs/MIBSmithy/UserGuide/commonerrors.php#counter64

Co-authored-by: TheWitness <thewitness@cacti.net>
2020-09-13 12:25:27 -04:00
netniV 2db7e7f61c Update copyright year and tweak service documentation 2020-02-15 09:56:40 +00:00
netniV a07df0846c Thresholds are not applying CDEF's correctly
Closes #361
2019-09-11 13:55:58 +00:00
netniV 2ea956d614 Correct two major issue with current threshold management
- issue#284: Threshold loses parent template if template propagation disabled
  during save

- issue#285: Threshold name cache is not being populated properly

Closes #284
Closes #285
2019-03-29 15:08:32 +00:00
TheWitness e9c5c6c878 Replace most sizeof calls for cacti_sizeof for php7 compatibility 2019-01-10 18:54:42 -05:00
TheWitness 7053e7e834 Fixing one bug and half implementing another feature. 2019-01-02 18:43:48 -05:00
TheWitness 8517b189ac Code context review 2018-12-28 14:48:11 -05:00
TheWitness 0ad5286c97 Major forked thold merge to Cacti core thold plugin
For those of you watching, you may have thought I was never going to commit again.  Well, you were wrong 👍  I'm alive and still working on Cacti and a number of plugins.  Just not in a very public way.  You may be aware of my other work, and if so, that's great!

My current employer is allowing me to port a number of thold changes back to the Cacti community from our forked version.
 It was a necessity since the Cacti core version of the plugin has advanced so much since our forked version was first created.

Of the commits that will be forthcoming, this is the first.  In this first commit, I'm adding the following:

* Adding some additional hooks to the Notification List functionality for one of my non-public plubins.
* Adding Acknowledgment functionality to thold
* Allowing optional per threshold and per template specific email body support for both warnings and alerts
* Allow optional command line execution as the result of a threshold alert breach or restoral event.
* Allow threshold actions to be hooked by other plugins.  These hooks will be used by some of my non-public plugins for performing specific thold actions
* Add hooks for threshold and and template editing and saving
* Some faily significant code cleanup and code reduction
* In addition, I've found a number of smaller bugs that I will remediate via the GitHub issues list.

There are additional changes that I will be making subsequent to this commit, that include:

* Using a Threshold template to drive the creation of Graphs.  It's kind of backwards, but it works pretty well.
* Possible additional changes to the various hooks as I modify my gridalarms plugin to work with this version of thold.
* Any bug fixes that I discover from this major change.

Note, that you must be using Cacti 1.2 in order to leverage this new version of thold.  You are free to test it prior to it's release formal release of course.  I'm pretty sure all the core functionality is there as of right now.  However be warned, that I may for example pull or add a column or two, but you can rest assured that forcing another upgrade should fix things automatically.

Finally, let's not all get too excited.  I'm doing this work for my employer who has been kind enough to allow this work to happen.  None-the-less, I'm glad to be back, if only for a short time.

TW
2018-12-28 14:15:20 -05:00
cigamit fb56380f3d Correcting a typo from earlier commit. 2018-02-20 22:04:49 -06:00
cigamit d2b58db563 Resolving Issue #132
Undefined indexes from time to time when storing current value
2018-02-20 21:32:44 -06:00
cigamit c581fbe7a1 issue#153: Threshold Template "Operator Notes" Variable not Recognized 2018-02-20 20:07:53 -06:00
cigamit ebe2c91bd1 Correct eol characters in thold_process.php 2018-02-20 18:56:31 -06:00
rgt0078 e19b86d264 Apply sql optimization for thold_daemon 2017-10-13 09:21:01 +02:00
cigamit a18b7440b8 Solution for Issue #112
This update should be fully functional, and you should find that the
performance is much better.
2017-09-14 20:28:24 -05:00
cigamit b31f37c6ba One last optimization 2017-09-13 20:17:37 -05:00
cigamit 2e74815722 Fix issue with thold daemon and most recent optimizations 2017-09-13 20:02:29 -05:00
cigamit a968e864e1 feature: Report Thold Daemon Runtime with Millisecond Precision
- Also resolving remain thold deamon issues
- Catching some missed i18n.

Note that if you have been testing 1.0.4, you will need to alter the
plugin_thold_daemon_processes star and end columns by hand.  See the
includes/database.php for syntax.
2017-09-13 17:11:08 -05:00
cigamit f9577a9f0a Note that this is a version change. It is primarily for testing the thold daemon
Note, that if you have multiple data collectors, you must run the daemon
on each data collector presently.  This means that notification must
work from every data collector.  I'm considering a setting to have the
thold daemon only runon the main data collector, but that has not be
finalized yet.  Lastly, this is experimental.  If you want something
production grade, stick with everything upto the last commit.
2017-09-10 09:04:14 -05:00
cigamit 615c16ddf8 simplify the use of microtime in thold 2017-09-04 08:28:05 -05:00
Arjan Oosting 3b69b73b00 Fix undefined index and undefined function errors
The index thold_server_rrd_reindexed is rrd_reindexed and the index thold_server_rrd_time_reindexed is rrd_time_reindexed
lib/rrd.php should be included to use rrdtool functions
Also fix SQL error in second query and use the _prepared database functions
2017-05-11 10:51:11 +02:00
cigamit 29060a20e0 Help and Version Audit 2017-02-09 19:53:02 -06:00
cigamit db1f1ea347 Resolving Issue #32 2017-01-15 12:02:03 -06:00
cigamit 511a405a5f Update Copyright Years plus some missed updates 2017-01-02 08:11:29 -06:00
cigamit aabf089894 Remove deprecated calls 2016-12-31 12:45:57 -06:00