Skip to content

chore: PHPStan level 8 typing pass + bugfixes - #30

Merged
bmfmancini merged 2 commits into
developfrom
chore/phpstan-level-8-typing
Sep 25, 2026
Merged

bmfmancini merged 2 commits into
developfrom
chore/phpstan-level-8-typing

Conversation

@TheWitness

Copy link
Copy Markdown
Member

Summary

PHPStan level 8 pass for plugin_wmi, following the same process as the prior repos in this rollout. This repo mixes a modern, already well-typed OOP transport layer (linux_wmi.php's Linux_WMI/Wmi_Transport classes, from a recent PowerShell/CIM transport feature) with older, untyped procedural page/poller code - the typing work focused on the latter.

(a) Error count

PHPStan level 8: 35 -> 0 errors (after adding ~25 missing Cacti-core function stubs to the scratch bootstrap).

(b) Genuine bugs fixed

  • functions.php (plugin_wmi_create_resource_xml()): the exported WMI query Resource XML referenced a nonexistent script_path (scripts/wmic-script.php) and script_function (wmic_script) - both typo'd with an extra "c". Verified against this plugin's own shipped example (templates/resource/script_server/exchange_messages.xml), which correctly uses scripts/wmi-script.php / wmi_script. Exporting and re-importing this query's resource file would have pointed the script server at a script/function that don't exist.
  • wmi_accounts.php / wmi_queries.php: $account_actions[get_request_var('drp_action')] / $ds_actions[...] were indexed by a value only validated as some integer (via get_filter_request_var/input_validate_input_number), not restricted to the single real bulk-action id (1). Added an array_key_exists()-checked reject-and-redirect guard before the lookup - the same bug shape found and fixed in plugin_tags/plugin_apcupsd earlier in this rollout.
  • poller_wmi.php: plugin_wmi_version()'s parse_ini_file() result was hardened (isset($info['info']) && is_array($info['info']) ? $info['info'] : []), and its caller (display_version()) now guards against a missing version key before dereferencing it - the established parse_ini_file hardening pattern from plugin_apcupsd/plugin_cycle.
  • Multiple db_fetch_row_prepared() results (functions.php, poller_wmi.php, setup.php, wmi_accounts.php, wmi_queries.php) were read via array offset without narrowing out the bool half of the real array|bool return type - added is_array($x) ? $x : [] narrowing at each site.
  • linux_wmi.php: explode() was called with a public, mutable $separator class property with no non-empty guard (a PHP 8 ValueError if it were ever set to '' at runtime); unserialize() was called on base64_decode()'s result without checking for false first; proc_open()'s constructed command array is now wrapped in array_values() to guarantee a list (behaviorally a no-op, satisfies PHPStan's stricter proc_open() stub).
  • Several html_start_box() call sites passing '', '3' instead of false, 3 for the $div/$cell_padding params (the now-familiar recurring pattern across this whole rollout).
  • poller_wmi.php: process_device($host_id) is now cast to int at its call site - CLI args are always strings, but the function's own docblock declares @param int $host_id.

(c) Stubs added/verified

~25 new scratch PHPStan bootstrap stubs sourced from Cacti/cacti's develop branch (generate_hash, get_hash_version, get_hash_data_input, data_input_method_to_xml, cacti_escapeshellarg, cacti_escapeshellcmd, array_to_sql_or, api_graph_remove_multi, api_plugin_hook_function, api_data_source_remove_multi, html_header, form_dropdown, set_request_var, __x, html_header_checkbox, number_format_i18n, get_request_var_post), plus the established reusable base set.

(d) Test result

Full Pest suite: 34 passed (270 assertions), run against the real 1.2.x host via bind-mount, unchanged before and after this pass. php-cs-fixer (Cacti develop config) and php -l both clean on all touched files (only docblock-alignment diffs from the style pass).

(e) Unresolved / flagged for maintainer judgement

  • Added 3 narrowly-scoped, documented PHPStan ignores: variable.undefined for wmi_accounts.php/wmi_queries.php/wmi_script.php - all 3 are top-level dispatcher scripts that read $config['base_path'] immediately after include('./include/auth.php'), which is what actually defines $config at runtime (every Cacti plugin page does this); PHPStan can't trace variable definitions across a plain include() call, so this is the same structural false-positive category as the already-established include.fileNotFound ignores.

Files changed

functions.php, linux_wmi.php, poller_wmi.php, setup.php, wmi_accounts.php, wmi_queries.php, wmi_script.php, wmi_tools.php, script/wmi-script.php, CHANGELOG.md, locales/po/cacti.pot (translation template refresh only; per-language .po/.mo files reverted to avoid Weblate churn).

Brings plugin_wmi to PHPStan level 8 with 0 errors (35 -> 0, after
adding missing Cacti-core function stubs).

Real bugs fixed:
- functions.php (plugin_wmi_create_resource_xml()): the exported WMI
  query Resource XML referenced a nonexistent script_path
  (scripts/wmic-script.php) and script_function (wmic_script) - both
  typo'd with an extra "c". Confirmed against this plugin's own shipped
  example resource XML (templates/resource/script_server/exchange_messages.xml),
  which correctly uses scripts/wmi-script.php / wmi_script. Anyone who
  exported and re-imported this query's resource file would get a
  script server error, since the referenced script/function don't exist.
- wmi_accounts.php / wmi_queries.php (actions_accounts()/actions_queries()):
  $account_actions[get_request_var('drp_action')] / $ds_actions[...] were
  indexed by a value only validated as *some* integer, not restricted to
  the single real action id (1). Added an early
  array_key_exists()-checked reject-and-redirect guard before the lookup
  (same bug shape fixed in plugin_tags/plugin_apcupsd earlier in this
  rollout).
- poller_wmi.php (plugin_wmi_version() caller / display_version()):
  hardened plugin_wmi_version()'s parse_ini_file() result and added a
  guard in its caller before dereferencing $info['version'] (same
  parse_ini_file hardening pattern established in plugin_apcupsd/plugin_cycle).
- Several `db_fetch_row_prepared()` results (functions.php, poller_wmi.php,
  setup.php, wmi_accounts.php, wmi_queries.php) were read via array offset
  without narrowing out the `bool` half of their `array|bool` return type -
  added `is_array($x) ? $x : []` narrowing at each site.
- linux_wmi.php: `explode()` on a public, mutable `$separator` property
  with no non-empty guard (would throw a PHP 8 ValueError if ever set to
  '' at runtime); `unserialize()` called on `base64_decode()`'s result
  without checking for `false` first; `proc_open()`'s command array
  wrapped in `array_values()` to guarantee a list type.
- Several `html_start_box()` call sites passing `'', '3'` instead of
  `false, 3` for the `$div`/`$cell_padding` params (recurring pattern
  across this whole rollout).
- poller_wmi.php: `process_device($host_id)` cast to int at its call site
  (CLI args are always strings; the function's own docblock declares
  `@param int $host_id`).

Also ran php-cs-fixer in real fix mode (only docblock-alignment diffs)
and regenerated locales/po/cacti.pot (reverted per-language .po/.mo
churn, per this rollout's established Weblate-ownership convention).

Full Pest suite: 34 passed (270 assertions), unchanged before/after.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

An empty separator can still cause transport output and response parsing to use different delimiters, producing malformed query data.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

This PR completes a PHPStan level 8 typing pass and fixes WMI export, validation, and runtime-safety issues.

Changes:

  • Hardened database, configuration, decoding, and process handling.
  • Corrected exported WMI script references and bulk-action validation.
  • Updated documentation, changelog, and translation references.
File Summary
wmi_tools.php UI argument and result-handling fixes
wmi_script.php PHPDoc return-type updates
wmi_queries.php Action validation and database-result narrowing
wmi_accounts.php Action validation and database-result narrowing
setup.php Plugin setup and result-handling hardening
script/​wmi-script.php PHPDoc updates
poller_wmi.php Version handling and host ID typing
locales/​po/​cacti.pot Refreshed translation references
linux_wmi.php Transport, parsing, and decoding safeguards; empty-separator handling remains inconsistent
functions.php Resource XML corrections and result handling
CHANGELOG.md Documented fixes

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread linux_wmi.php Resolved
…or both the transport request and result parsing

Addresses Copilot review feedback: the earlier fallback only guarded
explode() against an empty separator when parsing results, but still
passed the raw (possibly empty) ->separator to the transport via
Wmi_Request, so an empty separator would make the transport emit
unsplit rows while the parser split on the fallback |+| - malformed
data, not just a crash. Wmi_Linux::effective_separator() is now the
single source of truth for both call sites. Added regression coverage
(tests/WmiEmptySeparatorTest.php) asserting the transport and the
parser see the same effective separator, for both an empty and a
custom separator.
@bmfmancini
bmfmancini merged commit 75c579e into develop Sep 25, 2026
3 checks passed
@TheWitness
TheWitness deleted the chore/phpstan-level-8-typing branch October 20, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants