20 Commits
Author SHA1 Message Date
Thomas Vincent 36190b211c fix: bind SQL values in setup.php as prepared statements (#282)
Closes #207

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-24 09:48:11 -04:00
Thomas Vincent ec903267ee fix: require POST for monitor state changes and check dashboard ownership on save (#280)
* fix: require POST for state-changing monitor actions

csrf-magic only validates POST, so save, saveDb, remove, the mute toggles
and the card-order save now fall through to the normal page draw when sent
by GET, and monitor.js sends remove and the mute toggles as POST with the
CSRF token.

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

* fix: save dashboards only as their owner

saveSettings() took the owner from an unvalidated user request variable and
let sql_save() overwrite any dashboard id. It now always uses the session
user and refuses to update a dashboard the user does not own, matching
removeDashboard() and saveCardOrder().

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-23 14:47:29 -04:00
TheWitnessandCopilot bef1ad80a0 Fix alert audio not rendering: resolve sounds dir from plugin root (#278)
getMonitorSound() used __DIR__ . '/sounds/' after the controller moved to includes/, so the sound file was never found and the audio element was never printed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-16 15:20:41 -04:00
TheWitness 487f9b3e09 Fix fatal PRIMARY KEY (Array) on fresh install from array primary key (#276) 2026-10-15 09:37:48 -04:00
TheWitness 5f96b8b7e2 Monitor 3.0 board: full-width grouping cards, Tree ordering, and the Status->Events filter model (#274)
* Monitor: flex grouping cards to full width, order Tree cards by device count

* Monitor: drop inaccurate Site/Template ordering comparison from changelog

* CI: install the declared thold dependency so Monitor can install under Cacti's new install-failure reporting

* Monitor: remove the filter-status/criticality footer and prompt from the board

* Monitor: give the busiest grouping cards (span 3-4) a full row so their tiles span the page

* Monitor: changelog for full-width busy cards and footer removal

* Monitor: drop Site grouping header, tighten tile uptime spacing, add List column tips

* Monitor: add clickable List-view thold/servcheck/ping/criticality pills with filter toggles

* Monitor: unit tests for new filter branches and thold/servcheck count helpers

* Monitor: selectable status pill, fallback/empty pills, hide absent plugin columns, tile sizing, dialog UX

* Monitor: center and keep clickable the tile hostname, remove 'Names only' view option (backend retained)

* Monitor: multi-select Events filter + Events column (additive), Status becomes scope-only Show

* Monitor: apply the Events multi-select on dropdown close (and on change for native selects)

* Add All criticality, Device Template multi-select, servcheck scope

- Add an "All" option to the Criticality filter. Its value (-1) matches the
  default and the pill "clear to default" action, which previously set the
  select to a non-existent option and submitted crit=null, tripping
  validate_store_request_vars.
- Convert the Device Template filter to a checkbox multi-select (like Events)
  and actually apply it: renderWhereJoin now adds
  h.host_template_id IN (...) for the template grouping (0 = Non-Templated).
  Previously the template selection was never applied, so all hosts returned.
- Persist the template selection as a user setting and serialize the
  multi-value for request/save payloads.
- Add a "Devices without Service Checks" scope to the Show filter, shown only
  when the servcheck plugin is installed and permitted.
- Tests + regenerated .pot.

* Update template escaping assertions for the multiselect markup

* Cover empty-template branch in renderWhereJoin

* Left-align Actions glyph and harden Device Detail Panels dialog layout

- Left-align the List-view Actions column header and fa-layer-group glyph.
- Stack the detail panels as plain blocks and force the jQuery UI dialog
  content box to always fill the dialog width, so the panels can no longer
  collapse into a narrow right-hand strip.

* Size the detail dialog to its content instead of forcing overflow

The #monitorPanelDialog overflow override (higher specificity than the
theme's .ui-dialog .ui-dialog-content) turned the content into a fixed-height
scroll box that fought the theme's overflow:visible and squashed the panels.
Use height:auto (capped by maxHeight) and stop overriding overflow so the
dialog sizes to its content and the theme governs scrolling.

* Anchor the detail dialog 300px from the top instead of centering

* Make the detail panels dialog non-modal

* Move Notes out of the List view into the detail dialog

The Notes column consumed a lot of horizontal space in List view. Drop it
from the table and surface the notes in the Host Information panel of the
Device Detail Panels dialog instead (shown only when the device has notes).

* Add device-edit action icon and Devices filter button (realm-gated)

List view: add a pen-square action icon linking to host.php?action=edit for
users with the host.php (device edit) realm, beside the detail-panels icon.
Filter bar: add a Devices button (right of Acknowledge) that opens the Cacti
Devices page, shown only to users with the same realm. Also move the detail
dialog anchor from top+300 to top+150.

* Anchor the detail dialog at top+80

* List view: replace Hostname column with a Graphs action glyph

Remove the Hostname column and add an orange fas fa-chart-line Graphs glyph
(.monitorGraph, matching HMIB's colour) to the Actions column, linking to the
device's graphs where the hostname hyperlink used to point.

* List columns + filter layout refinements

- List columns reordered to Description, Location, Site, ID; add a Location
  column; Averages, Length in Status, Last Fail are now the last three
  columns, all right-aligned.
- rfilter Search now also matches the device location.
- Filter: move the Events multiselect to the second row (right of Search) and
  the Rows dropdown to the second row (left of Refresh); rename that dropdown
  from Devices to Rows.
- Add a renderWhereJoin test for the description/location rfilter search.

* Monitor: realm-gate service-check scope/column, persist events+template, legacy status reset, failed_svc independent, defer list counts, pill keyboard a11y, PHPDoc/changelog fixes
2026-10-12 06:26:35 -04:00
TheWitness 11c7e46706 Monitor: Rows filter, servcheck status, Tiles sizing, device-detail panels, dashboard properties & grouping cards (#272)
* feat: servcheck hover integration, list-only Rows filter, fix Tiles & Time tile box

Rows filter now only paginates/shows on the List view; non-list views show all devices. Add plugin_servcheck hover status + glyph alongside thold/syslog. Fix Tiles & Time tiles overflowing their bounding box via min-height.

* fix(review): servcheck schema compat, realm-gated status, POT, tests

- getHostTriggeredServchecks adapts to servcheck v0.3 (lastcheck/failures) and v0.4 (last_check/last_result) schemas and guards a missing table

- Gate the hover status promotion behind the servcheck_test.php realm, matching the glyph link

- Regenerate locales/po/cacti.pot for 'View Failing Service Check'

- Add unit coverage for the servcheck helper (disabled/empty/missing-table exits, bound params, v0.3 & v0.4 predicates)

* feat: distinct Service Check Failed status + List-view Actions detail panels

- Add status 10 (Service Check Failed) with legend chip, tile/list colour and status-filter coverage via a realm-gated, schema-aware checkServchecks() host set

- Add a non-sortable Actions column to the List view opening a modal of intropage-style cards: host info, data collection timing, service checks, thresholds, last-hour syslog, and device links

- Regenerate cacti.pot; add unit coverage for checkServchecks() and the servcheck status filter

* feat: dashboard properties JSON + colorized draggable grouping card panels

- Restructure plugin_monitor_dashboards to a JSON properties column; migrate the legacy url into it and drop the url column on upgrade; load/save dashboards via the structure

- Render Site and Device Template groupings as a responsive grid of draggable card panels, titles coloured by each group's worst device status, with per-dashboard/per-user persisted ordering

- Add unit coverage for the url->properties migration and db stubs

* fix(review): secure device panels, schema-aware not-tested, IPv6 link, thold realm

- Enforce is_device_allowed() on ajax_hostpanel and ajax_status so a user cannot pull details for a device off their board

- Gate the Thresholds card behind thold's thold_graph.php realm

- Select the schema-specific servcheck check time (lastcheck/last_check) and use it so never-run v0.3 checks report Not tested

- Bracket IPv6 literals in the Open Actual Device link

* fix(review): ownership-aware card order + request helper for order array

- Save/read grouping card order via the dashboard only when the user owns it; shared/read-only dashboards and no-dashboard fall back to the per-user monitor_cardorder setting so reorders persist

- Read the posted order through get_nfilter_request_var() instead of \ directly
2026-10-10 19:12:57 -04:00
TheWitness 0409ecb532 Fix select2 change recursion from double-wired Criticality device filter (#270)
* Fix select2 change recursion from double-wired Criticality device filter

Gate the client-side Criticality filter injection on whether core already rendered the field (device_filters/CactiTableFilter) instead of a CACTI_VERSION string. When core renders it natively the handler now bails, avoiding the second widget/handler that drove the select2 change -> selection:update recursion. Legacy releases still inject once, matching core's widget engine and binding a single namespaced change handler.

* test: cover monitor_device_table_bottom() filter injection

Exercise the device_table_bottom hook that injects the legacy Criticality filter, locking in the select2 change-recursion fix: the bail guard when core already rendered the field, a single namespaced change.monitor handler, a single widget engine (never both), theme gating, and the selected-option build. Covers the previously untested changed lines (setup.php patch-coverage gate).
2026-10-08 14:58:19 -04:00
TheWitness 38bdabf90f monitor: use CSS Grid for truly equal-width legend chips (#263)
* monitor: use CSS Grid for truly equal-width legend chips

The .monitorLegend flex: 1 1 0 + flex-wrap layout distributes free space per wrapped line, so a partial last row stretched its chips wider than full rows. Switch to a grid (repeat(auto-fill, minmax(var(--monitor-chip-min), 1fr))) so every chip shares an equal column across all rows. The longest-label seed (--monitor-chip-min from includes/controller.php) still drives the column minimum.

* monitor: use auto-fit so chips fill the legend width when they fit on one row

Review follow-up (#263): auto-fill retains empty implicit columns on wide viewports, leaving unused slots so the chips do not fill the legend width. auto-fit collapses the empty tracks while keeping equal shared columns across wrapped rows.

* monitor: extract monitor_legend() and add unit coverage

The status legend was rendered inline in includes/controller.php, so it had no unit coverage like the thold/servcheck/mactrack/syslog legends. Extract it into monitor_legend(array \, array \) in includes/functions.php (in the phpunit coverage source set) and add MonitorLegendTest.php asserting the exact calc(Nch + 1.5rem) longest-label value. No behaviour change.

* monitor: escape status-legend chip labels at output

Defence-in-depth: wrap the legend chip display text in html_escape() so markup or HTML entities in a translated label cannot alter the legend DOM. Applied consistently across the thold/monitor/servcheck/mactrack/syslog fleet legends. The chip-min sizing still measures the unescaped label length, so layout is unchanged.
2026-10-07 13:20:26 -04:00
TheWitness 10c3fa0cf8 Monitor: match tile icons + List pill to legend palette; fix chip overflow (#261)
Three follow-ups to the legend-chip redesign (PR #260): (1) legend chips kept overflowing their rounded bounds when space was tight - added min-width:max-content (keeping white-space:nowrap) so each chip is at least as wide as its label and wraps to a new row instead of clipping. (2) device-tile status icons were coloured by the core .deviceXxx classes, which did not match the legend; monitor.css now colours .mon_icon.deviceXxx from the same solid legend palette (with !important to beat the core themes' !important rules), including the Deepness emerald Up. (3) List view now adopts the Cacti deviceStatus pill design: the status cell is wrapped in a .monitorStatus.deviceStatus pill coloured from the legend palette, and the whole-row tint was removed so only the pill is coloured. includes/render.php added to the patch-coverage allowlist (auth-gated render library, not unit-loadable, same as controller.php).
2026-10-07 08:08:32 -04:00
TheWitness 1ac177ef81 ci: compare i18n strings via tokenizer instead of diff lines (#258)
Replace the diff-line scan in tests/bin/check-i18n-pot.php, which had
false-positive and false-negative cases, with a tokenizer-based
comparison. For the merge-base and for HEAD it tokenizes every
template-feeding PHP file (the find . -maxdepth 2 set build_gettext.sh
scans), finds the Cacti i18n helper calls, and collects the literal
string arguments that actually form each pot entry (msgctxt/msgid/plural
per the xgettext -k spec). A call only contributes when those positions
are constant strings, mirroring xgettext. If the extracted set differs
between base and HEAD, locales/po/cacti.pot must be part of the PR.

Reading whole files rather than diff lines means surrounding-code edits
and quote-style-only changes no longer trigger it, and multi-line calls
are handled correctly.

Same fix validated in plugin_reportit #150.
2026-10-05 13:03:09 -05:00
TheWitness 9e2e7ae650 ci: gate cacti.pot updates on i18n diff instead of regenerating (#257)
The "Verify translation template is up to date" step regenerated
locales/po/cacti.pot with locales/build_gettext.sh and compared it
(ignoring POT-Creation-Date). That couples the check to an exact
gettext/xgettext toolchain and fails on unrelated version drift.

Replace it with tests/bin/check-i18n-pot.php, which inspects the pull
request diff: if any changed line adds, removes, or edits a Cacti i18n
call (__(), __n(), __esc(), ... the same keywords build_gettext.sh
feeds xgettext) in a file that feeds the template, then
locales/po/cacti.pot must also be part of the pull request. The check
fails only when that pot update is missing.

The step now runs only on pull_request events and diffs against the
PR base SHA, mirroring the existing patch-coverage.php approach.
2026-10-05 12:11:06 -05:00
TheWitnessandCopilot 66358e0998 Adopt plugin table API and relocate schema to includes/database.php (#254)
* Relocate schema to includes/database.php via plugin table API + db_update_table upgrade

* CI: allowlist unmeasured schema bootstrap and web entry points

* CI: keep include_once for the settings-page-only lib/reports.php load (uncoverable)

* Add manifest.json + upgrade-time file pruning; consolidate themes/ into css/

* database: keep user-owned dashboards table across uninstall

api_plugin_db_table_create() records plugin_monitor_dashboards in
plugin_db_changes, so api_plugin_db_changes_remove() (run by
api_plugin_uninstall() after plugin_monitor_uninstall()) would drop it
even though monitor_drop_tables() intentionally leaves it in place. Remove
the 'create' ownership record after provisioning (install and upgrade
paths) so user dashboards survive an uninstall. Adds a regression test.

* 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 libraries into includes/, flatten css, drop sonar config

db_functions.php, monitor_render.php and monitor_controller.php move into
includes/ (the latter two lose the monitor_ prefix, becoming render.php and
controller.php); monitor.php and the tests load them from there. Each per-theme
stylesheet css/<theme>/monitor.css becomes css/<theme>.css so users can drop
their own css/<theme>.css into the css base, and plugin_monitor_page_head()
looks there, falling back to css/monitor.css. The unreferenced
sonar-project.properties is removed. Old paths are tombstoned so existing
installs drop the stale copies on upgrade.

* Rename includes/db_functions.php to includes/functions.php

Follows the fleet convention for the plugin's shared library filename; monitor.php,
the measured-source list and the unit test load it from the new name.

* 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 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.

* Bump plugin version to 3.0 so the upgrade-time cleanup migration runs

Add a 3.0 CHANGELOG section for this cycle's schema/manifest work and set
INFO version = 3.0 so existing 2.9 installs detect the version change and
run the schema refresh and manifest-based file prune.

---------

Co-authored-by: Copilot <copilot@users.noreply.github.com>
2026-10-01 01:39:53 -05:00
TheWitnessandTheWitness ccb1f821ee ci: enforce patch coverage of changed lines (#252)
* ci: enforce patch coverage of changed lines; drop COMPOSER_ROOT_VERSION

Adds tests/bin/patch-coverage.php and an "Enforce coverage of changed
lines" CI step (pull_request only) that fails when production PHP lines a
PR changes are left uncovered, mirroring plugin_thold. Whole-file
coverage is not meaningful for a Cacti plugin (most code only runs inside
a live Cacti), so the gate measures only the lines a change adds.

Also sets fetch-depth: 0 on the plugin checkout so the gate can diff
against the PR base, and removes the inert COMPOSER_ROOT_VERSION env from
the Pest step (it only affects composer root-version detection, which does
not run in that step).

* chore: ignore compiled locale files (locales/po/*.po, locales/LC_MESSAGES/*.mo)

---------

Co-authored-by: TheWitness <copilot@users.noreply.github.com>
2026-09-29 16:53:52 -04:00
TheWitness 8f961badb7 security(csp): version-safe inline-script nonce wrapper + strengthen PO/MO instructions (#249)
* security(csp): add version-safe inline-script nonce wrapper; strengthen PO/MO instructions

* test(csp): exercise nonce delegation in isolated process
2026-09-28 08:49:50 -04:00
TheWitness 2e5f294c1f test: add Security baseline and expand Unit/Integration coverage for lifecycle (#246)
This plugin had no tests/Security category at all, and setup.php's
lifecycle/device-hook functions had zero test coverage (only db_functions.php
and the request-output escaping/wiring were tested). Closed both gaps:

- Security: added the standard PHP compatibility scan (8.0-8.4
  removed/added syntax) and a static structure check (required
  functions, INFO keys), matching the baseline used across the rest of
  the plugin fleet.
- Unit: plugin_monitor_version(), the lifecycle contract functions
  (plugin_monitor_uninstall - verifies it drops every table it owns,
  plugin_monitor_check_config - both the out-of-range and valid
  monitor_refresh branches, plugin_monitor_upgrade,
  monitor_check_upgrade()'s page-guard and version-drift branches),
  the device-list hooks (monitor_device_filters, monitor_device_sql_where,
  monitor_device_action_array, monitor_device_remove), monitor_draw_navigation_text(),
  monitor_get_default(), monitor_setup_table() (verifies every table it
  provisions), and monitor_poller_bottom() (primary-poller and
  non-primary-poller branches).
- Integration: plugin_monitor_install() (verifies every hook, the
  realm, the tables it provisions, and the config defaults it sets,
  together, in one pass) - this plugin had no tests/Integration
  coverage of setup.php before (the existing Integration tests cover
  request-output wiring, not the plugin lifecycle).

No new unserialize() hardening was needed: setup.php's one
unserialize() call already goes through
sanitize_unserialize_selected_items().

Also fixed tests/.cacti-version, which was pinned to a stale "1.2.31"
and failed the bootstrap's version check against any other real Cacti
checkout (e.g. a local 1.2.32 checkout); "1.2.x" matches the
moving-branch convention used elsewhere in this fleet.

Intentionally left uncovered: monitor_device_table_bottom()/monitor_show_tab()/
monitor_top_graph_refresh()/plugin_monitor_page_head() (print-heavy UI
rendering), monitor_config_settings()/monitor_config_arrays()/monitor_config_form()
(very large), monitor_device_action_execute()/monitor_device_action_prepare()
(large, print/DB-heavy confirmation dialogs), and monitor_api_device_save()
(moderate complexity, comparatively low value relative to the effort of
safely mocking its dependencies) - consistent with similarly-scoped
functions skipped elsewhere in this coverage-expansion pass.

tests/bootstrap-unit.php gained a fixture-mock DB API
(monitor_test_mock_db/monitor_test_reset_db_mocks/monitor_test_db_result,
matching the convention used elsewhere in the plugin fleet),
api_plugin_register_hook/api_plugin_register_realm call-logging stubs,
db_table_exists, and exec_background (call-logging, safe here since
setup.php's poller include always goes through $config['library_path'],
which points at a throwaway test stub, never Cacti core's real
lib/poller.php); read_config_option/set_config_option were rewired from
hardcoded no-ops to a real backing store - all needed by the new tests.

Verified via a real WSL Cacti install (composer-managed Pest, not a
plugin-local vendor tree): 63 passed (303 assertions), up from 31.
2026-09-23 10:04:23 -04:00
TheWitness 4b999d66b9 chore: harmonize CI workflow, templates, and test structure (#244)
* chore: harmonize CI workflow, templates, and test structure

Part of the fleet-wide plugin_* harmonization effort.

- Pin actions/checkout, shivammathur/setup-php, actions/upload-artifact to
  current release commit SHAs (was floating @v4/@v2).
- Stop logging the MySQL root password in CI output (cat ~/.my.cnf ->
  chmod 600).
- Harden MySQL bootstrap: quoted --defaults-file, grants added for both
  'cactiuser'@'localhost' and 'cactiuser'@'127.0.0.1'.
- Add "Restore vendor ownership for Pest" step after Composer install.
- Switch the PHP syntax-check step to the vendor-excluding, null-delimited
  find/xargs pattern used in plugin_audit.
- Remove the unused "Configure Apache" step (apache2/libapache2-mod-php are
  not installed and not needed).
- Simplify the Pest invocation to rely solely on phpunit.xml's <testsuites>.
- No CodeQL workflow added: this repo has no JavaScript/Python/Ruby content
  (PHP only), so there is nothing for CodeQL to scan.
- Consolidate tests/Security/Php74CompatibilityTest.php into
  tests/Security/PhpCompatibilityTest.php, now checking for PHP 8.3/8.4-only
  syntax (this plugin's floor is PHP 8.2 per the CI matrix).
- Remove tests/TestCase.php and its require in tests/bootstrap-unit.php: not
  referenced by any uses(TestCase::class) call in this plugin's tests.
- Add .github/ISSUE_TEMPLATE/{bug_report,feature_request}.md and
  .github/PULL_REQUEST_TEMPLATE.md, styled after Cacti/cacti's own templates.
- Document CI/dependency baselines in copilot-instructions.md.

* chore: add i18n build/verify workflow and bump MariaDB floor to 11.8

- Document the locales/build_gettext.sh + cacti.pot workflow in
  copilot-instructions.md (Weblate owns per-language .po/.mo sync).
- Add a CI step that regenerates cacti.pot and fails if it is stale,
  ignoring the POT-Creation-Date timestamp.
- Mark locales/build_gettext.sh executable.
- Bump the CI MariaDB service image floor to 11.8 (from mariadb:10.6 /
  mysql:8.0).

* fix: mariadb:11.8 healthcheck and i18n step permissions

- mariadb:11.8 no longer ships mysqladmin; use mariadb-admin for the
  service container healthcheck.
- Run chmod/build_gettext.sh with sudo in the i18n verification step,
  since locales/ is owned by www-data by the time it runs.

* chore: regenerate locales/po/cacti.pot with current source strings

Ran the real locales/build_gettext.sh (xgettext/msgmerge/msgfmt) to
refresh the translation template against current source. Per the i18n
workflow documented in copilot-instructions.md, only the regenerated
cacti.pot is committed here; Weblate owns syncing the per-language
.po/.mo files from it.

* fix: make locales/build_gettext.sh string extraction order deterministic

`find` without `sort` returns filesystem/readdir-order results, which
can differ between machines (e.g. a local regen vs. a GitHub Actions
runner), producing a spurious reordering diff in cacti.pot even when
no strings actually changed. Pipe through `sort` and regenerate
cacti.pot with the now-deterministic order.

* fix: Address Copilot review findings on the harmonization PR

- tests/Security/PhpCompatibilityTest.php: fixed the #[Override]/#[Deprecated]
  attribute regexes to also match the fully qualified (`#[\Override]`,
  `#[\Deprecated]`) form, added a realpath() false-guard for the plugin root,
  included the relative file path in both RuntimeException messages, added
  typed-class-constant and dynamic-class-constant-fetch checks (PHP 8.3), and
  restored each()/create_function() removed-in-PHP-8.0 guards that the
  consolidation had dropped. Also excludes include/vendor/ (not just vendor/)
  from the recursive source scan.
- .github/workflows/plugin-ci-workflow.yml: the "Create MySQL Config" step's
  root password is now masked via `::add-mask::` before it's echoed into
  ~/.my.cnf, so it no longer appears in plaintext in the Actions log (chmod
  600 alone only protected the file after creation, not the command's own
  echoed source in the log).
2026-09-21 12:44:08 -07:00
TheWitness f1eef20a25 ci: fix bootstrap redeclare fatal, add coverage driver, bump PHP floor to 8.2 (#243)
- tests/bootstrap-unit.php: stop stubbing validateRequestVars() globally; monitor_controller.php declares it unconditionally, so the stub caused a Cannot redeclare fatal once a test loaded plugin source via loadPluginSource()/monitor_test_load().
- .github/workflows/plugin-ci-workflow.yml: add coverage: pcov to the setup-php step so the Pest --coverage-clover report has a driver to run against.
- Document the PHP 8.2 floor (matrix is already 8.2-8.4) in copilot-instructions.md, the php-developer agent, and the hexagonal-monitor ADR, replacing stale PHP 8.1 references now that PHP 8.1 is no longer tested.
- Verified apache2/libapache2-mod-php do not need to be re-added to the Install System Dependencies step: apache2 ships preinstalled on the ubuntu-latest runner image and shivammathur/setup-php configures the matching Apache PHP module for the selected matrix version.
2026-09-19 14:56:07 -04:00
TheWitness b349b8f306 ci: add CodeQL workflow and adopt DbFunctionsTest/OutputEscapingTest from #229 (#238)
Cherry-picked from Cacti/plugin_monitor#229 (somethingwithproof:hardening/testing-and-workflows) and adapted to this repo's existing Pest setup:

- .github/workflows/codeql.yml: added as-is (JS/TS CodeQL scanning).
- tests/Integration/OutputEscapingTest.php: added as-is, references tests/Unit instead of tests/unit in its docblock.
- tests/Unit/DbFunctionsTest.php: added, placed under tests/Unit (PascalCase) instead of tests/unit to match this repo's layout.
- tests/bootstrap-unit.php: get_request_var()/get_nfilter_request_var()/get_filter_request_var()/set_request_var() now read/write a stubbed \['__test_request'] store, and a new test_set_request() helper lets tests control request vars, mirroring #229's tests/bootstrap.php.
- tests/Pest.php: added a beforeEach() hook resetting __test_db_calls and the request var store between tests.
2026-09-15 15:37:04 -04:00
e2fc5aed5f ci: rebuild Monitor CI workflow from plugin_evidence template with Pest tests (#236)
* ci: modernize Monitor workflow

* ci: pin integration tests to Cacti 1.2.31

* ci(security): pin GitHub Actions to commit SHAs

Pin actions/checkout (v5), shivammathur/setup-php (v2) and actions/upload-artifact (v4) to full commit SHAs with a trailing # vX comment. Mutable tags let a compromised or force-pushed tag run arbitrary code in CI; pinning to an immutable SHA is the supply-chain-hardening best practice and matches the SHA-pinned checkout used in the core security workflow.

* Allow Cacti dependency validation warnings

* Align Monitor matrix with Cacti dependencies

* Fix Monitor PHPStan working directory

* Fix duplicate Monitor workflow key

* Guard optional PHPStan configuration

* Guard optional Composer vendor binaries

* Skip unavailable Composer quality scripts

* ci: restore full PHP 8.1-8.4 integration matrix

Keep 1.2.31 pin while covering mid-stream PHP releases.

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

* ci: grant the Cacti database user the host pattern the service uses

The service container publishes a port, so Cacti reaches MariaDB from the
Docker gateway address and matches cactiuser@'%', not cactiuser@localhost.

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

* ci: rebuild plugin CI workflow from plugin_evidence template

Replace the Monitor CI workflow wholesale with the plugin_evidence CI workflow, renamed for plugin_monitor, adding Pest test support (phpunit.xml, tests/Pest.php, tests/TestCase.php, tests/bootstrap-unit.php, tests/.cacti-version). Convert the existing raw-script sanity checks in tests/unit, tests/e2e, and tests/Integration into Pest tests, renaming tests/unit to tests/Unit and using PascalCase *Test.php file names.

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Thomas Vincent <thomas@vincent.dev>
2026-09-15 15:24:17 -04:00
Thomas VincentandTheWitness 11fa109548 refactor: safe PHP 7.4 modernization (#212)
* refactor: add strict typing and clean up standalone infra

* refactor: safe PHP 7.4 modernization (arrays, null coalescing)

* fix: restore corrupted function calls from refactor tool

Revert bulk array()->[] rewrite damage affecting:
- is_array, in_array, xml2array
- call_user_func_array, filter_var_array
- Function declarations with _array suffix

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

* fix: restore corrupted syntax from refactor tool

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

* fix: remove duplicate declare(strict_types) statements

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

* fix(security): escape request reuse in monitor views

* fix:Run php-cs-fixit to resolve workflows

* fix: Remove strict types for now

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-07-09 09:20:25 -04:00