Add a boolean web config option to control whether server statistics
(load, CPU, DB connections, storage, RAM) are rendered in the navbar.
When disabled, the HTML is not output at all, saving the polling overhead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a boolean web config option to control visibility of the Back and
Refresh navigation buttons shown at the top of most views. Uses a body
class and CSS rule so no individual view files need changes.
Also remove the ZM_WEB_BUTTON_STYLE INSERT from the migration SQL since
zmupdate.pl handles Config table inserts from ConfigData.pm.in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a web config option to control toolbar button display:
- icons+text (default): show both icon and label
- icons: show only the icon, hide text labels
- text: show only the label, hide icons on buttons that have labels
Body class (btn-icons-only / btn-text-only) is set in getBodyTopHTML() and
CSS rules in skin.css toggle visibility of .text spans and icon elements.
Add title tooltips to console.php buttons so they remain usable in icon-only
mode. Migration appended to zm_update-1.39.4.sql.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change ZM_OPT_USE_REMEMBER_ME from a boolean to a tri-state string:
- None: checkbox hidden, sessions persist for ZM_COOKIE_LIFETIME (old disabled)
- Yes: checkbox shown and pre-checked by default
- No: checkbox shown and unchecked by default (old enabled behavior)
Update ConfigData.pm.in with new type definition, login.php to honor the
checked state, and session/action handlers to recognize the new values.
Migration in zm_update-1.39.4.sql maps old '1' to 'No' and '0' to 'None'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new config option (requires ZM_WEB_NAVBAR_TYPE=left) that controls
whether monitor filter settings are embedded in the left sidebar extruder
(default) or displayed inline at the top of the page. When set to inline,
the sidebar CSS no longer hides the filter elements, insertControlModuleMenu()
skips moving them, and the toggle icons are omitted. A `filter-inline` body
class drives the CSS scoping via :not(.filter-inline) selectors in sidebar.css.
Affects Console, Watch, Montage, and Montage Review views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Device field from the Monitors table was interpolated directly into
shell commands (qx(), backticks, exec()) without sanitization, allowing
authenticated users with monitor-edit permissions to execute arbitrary
commands as www-data via the Device Path field.
Defense in depth:
- Input validation: reject Device values not matching /^\/dev\/[\w\/.\-]+$/
at save time in both web UI and REST API
- Output sanitization: use escapeshellarg() in PHP and quote validated
values in Perl at every shell execution point
Affected locations:
- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm (control, zmcControl)
- scripts/zmpkg.pl.in (system startup)
- web/includes/Monitor.php (zmcControl)
- web/includes/functions.php (zmcStatus, zmcCheck, validDevicePath)
- web/includes/actions/monitor.php (save action)
- web/api/app/Model/Monitor.php (daemonControl, validation rules)
- web/api/app/Controller/MonitorsController.php (daemonStatus)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When ControlAddress does not contain authentication info, fall back
to Monitor->ONVIF_Username/ONVIF_Password, then Monitor->User/Pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ZM_OPT_USE_REMEMBER_ME config option (auth section, requires
ZM_OPT_USE_AUTH) that controls whether a Remember Me checkbox appears
on the login form. When enabled and unchecked, the session cookie
lifetime is set to 0 so the browser discards it on close, logging the
user out. When checked, the session persists for ZM_COOKIE_LIFETIME.
When the option is disabled, behavior is unchanged.
- ConfigData.pm.in: new ZM_OPT_USE_REMEMBER_ME boolean option
- login.php: checkbox between password field and reCAPTCHA/submit
- session.php: use lifetime=0 when remember me is off
- actions/login.php: set/clear ZM_REMEMBER_ME cookie on login, also
update $_COOKIE so zm_session_start sees it in the same request
- auth.php: clear ZM_REMEMBER_ME cookie on logout
- en_gb.php: add RememberMe translation string
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When cameras use HTTPS with self-signed or invalid certificates, LWP's
default SSL verification causes connection failures. Add automatic
fallback to all three HTTP methods (get, put, post) in the base Control
class: if a request fails with an SSL/certificate error, disable
verification and retry. Once disabled, the relaxed setting persists for
the rest of the control session via the ssl_verify_disabled flag.
This follows the pattern already used in Dahua.pm and
TapoC520WS_ONVIF.pm but applies it universally so all control modules
benefit without needing individual SSL handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change default training data directory from @ZM_CACHEDIR@/training to
@ZM_CONTENTDIR@/training so it lives alongside events storage instead
of inside the web cache folder. Update PHP fallback to use
dirname(ZM_DIR_EVENTS) accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix path traversal via directory prefix matching: append
DIRECTORY_SEPARATOR to base path before strpos containment check
(2 locations in training.php)
- Fix temp file leak: rename tempnam() base file to .jpg instead of
creating a second file, leaving the original orphaned
- Remove raw_output from detect response (information disclosure)
- Fix double _pushUndo on Delete key (keydown handler + deleteAnnotation)
- Fix help text: "alongside events storage" → "inside the ZoneMinder
cache directory" to match actual ZM_DIR_CACHE default (2 locations)
- Add missing .frame-total span element in event.php
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ZM_TRAINING_DETECT_SCRIPT config option for specifying the path
to an external detection script (e.g. zm_detect.py). When configured,
a Detect button appears in the annotation editor that:
- Runs the script with -f <image> -m <monitor_id>
- Parses the --SPLIT-- JSON output for labels, boxes, confidences
- Displays results as orange (pending) bounding boxes
- Users can accept (checkmark) or reject (x) each detection
- Only accepted annotations are saved to the training set
Also adds accept/reject UI in the sidebar object list, with orange
color for pending detections and normal colors for accepted ones.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add canvas-based bounding box annotation editor to the event view for
correcting object detection results and building YOLO training datasets.
- Two new config options: ZM_OPT_TRAINING (toggle) and ZM_TRAINING_DATA_DIR
- AJAX backend (training.php) with load/save/delete/status actions
- Canvas annotation editor (training.js) with draw/resize/relabel/undo
- Frame navigation (alarm/snapshot/objdetect/numbered frames)
- Roboflow-compatible YOLO output (images/all/, labels/all/, data.yaml)
- Training data statistics with per-class image counts and guidance
- Full i18n support via en_gb.php SLANG/OLANG entries
- Label validation, YOLO coordinate clamping, audit logging
- DB migration for existing installs (zm_update-1.39.2.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The error path was missing the /g flag on the regex replacing ? with %s,
so only the first placeholder was replaced. It also dumped bind values as
a space-separated string instead of using sprintf to substitute them into
the query. Now matches the existing debug path pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new AUDIT logging level (-5) between PANIC (-4) and NOLOG (shifted
to -6) across C++, PHP, and Perl loggers. AUDIT entries use code 'AUD'
and syslog priority LOG_NOTICE. They record who changed what, from where,
for monitors, filters, users, config, roles, groups, zones, states,
servers, storage, events, snapshots, control caps, and login/logout.
AUDIT entries have their own retention period (ZM_LOG_AUDIT_DATABASE_LIMIT,
default 1 year) separate from regular log pruning. The log pruning in
zmstats.pl and zmaudit.pl now excludes AUDIT rows from regular pruning
and prunes them independently.
Critical safety: the C++ termination logic is changed from
'if (level <= FATAL)' to 'if (level == FATAL || level == PANIC)' to
prevent AUDIT-level log calls from killing the process.
Includes db migration zm_update-1.39.1.sql to shift any stored NOLOG
config values from -5 to -6.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add config type entries for Hostname, DNS, NTP, NetworkInterfaces,
Capabilities, Scopes, Services, VideoSources, VideoSourceConfigurations,
AudioSources, AudioSourceConfigurations, AudioEncoderConfigurations,
StreamUri, SnapshotUri, ImagingOptions, PTZConfigurations, PTZNodes,
PTZPresets, and PTZStatus.
- Support __PROFILE_TOKEN__ substitution in request bodies
- Add writable flag to config types and enforce it in set_config
- Add NTP set handler for SetNTP ONVIF command
- Organize config_types by ONVIF service (device, media, imaging, PTZ)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MakeMaker was only used to copy .pm files — no XS compilation, no binary
linking, no dependency resolution. Its hardcoded "Makefile" output name
conflicts with cmake's generated Makefile for in-source builds, and using
FIRST_MAKEFILE=MakefilePerl causes thousands of "uninitialized value"
warnings because MM.pm stats the wrong file.
Replace with native CMake install(DIRECTORY ... FILES_MATCHING PATTERN
"*.pm") directives. Perl module install path is auto-detected at configure
time via `perl -MConfig` (vendorlib on Linux, sitelib on FreeBSD),
overridable with -DZM_PERL_INSTALL_PATH=<path>.
What's removed:
- ExtUtils::MakeMaker as build dependency
- Three perl+make subprocesses at build time (zmperlmodules,
zmonvifmodules, zmonvifproxy build targets)
- ~6000 auto-generated man3 pages from WSDL stubs
- MakeMaker scaffolding: Makefile.PL, MANIFEST, META.yml, Changes,
README, and t/ZoneMinder.t test stub
What's preserved:
- configure_file() for .pm.in templates (same behavior)
- ZM_PERL_SEARCH_PATH (independent mechanism, unchanged)
- Section 8 man pages for .pl scripts (Pod2Man.cmake, unaffected)
- DESTDIR support (CMake install() handles natively)
- Installed file paths (perl -MConfig returns same paths MakeMaker used)
Verified: 3102 .pm files installed, 0 .pm.in files, 0 .3pm man pages,
no @VERSION@ markers in generated files, DESTDIR and user override work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add configuration read/write support following the Uniview.pm pattern.
get_config() queries five ONVIF categories (DeviceInformation, DateTime,
ImagingSettings, VideoEncoderConfiguration, Profiles) via SOAP and returns
parsed XML as hashes. set_config() supports writing ImagingSettings
(Brightness/Contrast/Saturation/Sharpness/ColorSaturation) and DateTime,
treating other categories as read-only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- onvif.pm: irisAbsClose checked lowercase 'brightness' so getCamParams
was never called on decrease, sending stale/zero values
- Reolink.pm: same brightness case bug, plus whiteAbsIn and whiteAbsOut
built the SOAP message but never called sendCmd — contrast changes
were silently discarded
- Netcat.pm: same brightness case bug, plus debug() call should be Debug()
- TapoC520WS_ONVIF.pm: same brightness case bug
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single ONVIF SOAP/PTZ implementation that replaces onvif.pm, Reolink.pm,
Netcat.pm, and TapoC520WS_ONVIF.pm. All state is kept in instance variables
(no package globals), SSL verification falls back automatically, and
sendCmd routes to the correct ONVIF service endpoint per command type.
Bug fixes from the originals:
- Brightness decrease checked lowercase 'brightness' (never matched)
- whiteAbsIn/whiteAbsOut in Reolink/Netcat/TapoC520WS never called sendCmd
- Imaging commands sent to PTZ endpoint instead of /onvif/imaging
- Reboot sent to PTZ endpoint instead of /onvif/device_service
- Package globals leaked state between camera instances
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix critical crash in open() where guess_credentials() boolean return was
treated as URI object. Rewrite open() to use base class get_realm() for
auth discovery, matching the Uniview/HikVision pattern.
Bug fixes:
- focusRelNear: remove double-$self in recursive call, use instance var
- focusRelFar: fix copy-paste error calling focusRelNear instead of self,
remove double-$self, use instance var
- focusAbs: fix .cgi.cgi typo in opticssetup URL
- Replace package-global $use_optics with $$self{use_optics} to prevent
state leaking between camera instances
- Remove unused our $uri, use ZoneMinder::Config, use URI imports
- Move use LWP::UserAgent to module-level imports
New features:
- get_config/set_config via Axis param.cgi API
- probe() for network camera discovery
- rtsp_url() returning standard Axis RTSP path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace empty get_config/set_config stubs with working implementations
using Uniview's native LAPI JSON REST API. Modernize open() to use base
class guess_credentials() and get_realm() instead of duplicated auth
logic. Rename put() to PutCmd() to fix broken PTZ methods. Remove
package-level globals in favor of instance variables. Add lapi_get/
lapi_put helpers, probe via LAPI with ISAPI fallback, and rtsp_url().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 4 hardcoded `perl` invocations with `${PERL_EXECUTABLE}` from
find_package(Perl) so builds work when perl isn't in PATH or the wrong
perl would be found (NixOS, custom installs, Homebrew vs system).
Also: fix ZMCONFGEN_RESULT variable case mismatch in error message,
remove deprecated IMMEDIATE keyword from configure_file(), bump
cmake_minimum_required from 3.5 to 3.12 (C++17 requires 3.9+), and
remove dead C++11 fallback for CMake < 3.8 in ConfigureBaseTargets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 'events' command to check if an ONVIF camera supports the events
service. Returns "Events: yes" or "Events: no" for easy parsing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MariaDB is deprecating mysql-prefixed utility names (mysqldump,
mysql) in favour of its own (mariadb-dump, mariadb). Add
findDbCommand() to ZoneMinder::General that probes for the
MariaDB-native binary first and falls back to the legacy name.
Results are cached per-process. Update the three call sites in
zmupdate.pl.in and zmcamtool.pl.in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add IS NOT operator check alongside != in PHP FilterTerm.php
(was already handled in Perl but missing from PHP)
- Add defined() guard on $term->{val} in Perl Filter.pm to avoid
uninitialized value warnings with malformed/legacy saved filters
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The filter system ignored the operator (= vs !=) when generating SQL
for the special tag values "No Tag" (0) and "Any Tag" (-1).
In PHP (FilterTerm.php), "Tag != Any Tag" produced EXISTS instead of
NOT EXISTS, returning events WITH tags instead of events WITHOUT tags.
In Perl (Filter.pm), != was not handled as a special case and fell
through to generic SQL (T.Id != -1), which excluded events with no
tags because LEFT JOIN produces NULL and NULL != -1 evaluates to
UNKNOWN in SQL. Additionally, T.Id was unconditionally prepended for
all tag values, producing invalid SQL (T.IdEXISTS) for the special
cases that use EXISTS/NOT EXISTS subqueries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- zmDbDo: fix mismatched parentheses in Error() call
- zmDbGetMonitorAndControl: pass $id parameter to zmDbFetchOne
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add SSL verification state tracking to Dahua.pm to ensure retry logic works for all requests
- Add SSL retry logic to Dahua.pm sendCmd method
- Remove redundant 'use IO::Socket::SSL' statements from TapoC520WS_ONVIF.pm retry blocks
refs #TBD
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
Enable TLS peer certificate verification by default in all components that communicate over HTTPS. If SSL verification fails, log a warning and retry without verification to maintain backward compatibility with cameras using self-signed certificates.
Changes:
- C++ (zm_monitor_go2rtc.cpp): Enable SSL verification for all curl operations (3 locations)
- C++ (zm_monitor_rtsp2web.cpp): Enable SSL verification for all curl operations (3 locations)
- PHP (monitor_probe.php): Enable SSL verification with fallback logic
- Perl (Dahua.pm): Enable SSL verification with LWP::UserAgent
- Perl (TapoC520WS_ONVIF.pm): Enable SSL verification with retry logic in request methods
refs #TBD
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
User.pm declares `package ZoneMinder::Frame` instead of
`package ZoneMinder::User`, clobbering the Frame package namespace
and making the User ORM class unusable via `ZoneMinder::User->find_one()`.
FixesZoneMinder/zoneminder#4581
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Rename Janus-specific restream fields to be more generic since they are
now used by Go2RTC and RTSP2Web as well:
- Janus_Use_RTSP_Restream → Restream
- Janus_RTSP_User → RTSP_User
Update visibility logic so the Restream checkbox appears when RTSPServer
is enabled AND any streaming service (Janus, Go2RTC, or RTSP2Web) is
selected, rather than only when Janus is enabled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>