database.php called dbConnect() at file scope, so including it opened a socket,
and on failure rendered views/no_database_connection.php and exit()ed from
inside a library include. Every model in web/includes requires this file, so
merely loading a class did both.
Connect on first use instead. $dbConn becomes tri-state - false for "not
attempted", null for "attempt failed", a PDO for connected - and two accessors
sit on top of it:
zmDbConn() opens if needed; on failure renders the error view and
stops, which is what the include used to do, just at the
point a query is actually attempted.
zmDbConnOrNull() opens if needed but returns null instead of ending the
request, for callers with a fallback.
dbQuery() is the funnel every fetch helper goes through, so routing it plus
dbEscape(), dbError() and dbInsertId() through the accessors covers the library.
The five callers that reached for the raw global are updated: config.php.in,
Event.php and ajax/console.php need a connection and take zmDbConn(); logger.php
takes zmDbConnOrNull() and falls through to its error_log target, so a logging
call can no longer end the request or open a connection by itself.
ZMSessionHandler captured $dbConn in its constructor. It is constructed while
session.php is being included, before anything has needed the database, so with
a lazy connection that captured false. It now resolves per call and its methods
return "no session" rather than dereferencing a bool.
Two smaller fixes fall out. The error view was included by a relative path that
only resolved when the cwd was web/, so it never worked for requests served out
of web/api/; it is now anchored with __DIR__. And dbDisconnect() set $dbConn to
null, which in the new tri-state means "connecting failed" and would send the
next query to the error page; it sets false so a later query can reconnect.
Nothing calls dbDisconnect() today.
This does NOT make database.php includable without a database. It requires
logger.php, which requires config.php, which reads ZoneMinder's configuration
out of the Config table at include time. Until that cycle is broken the
connection still happens during bootstrap, just from config.php rather than from
here.
Tests: tests/php/test_database_lazy_connect.php, 7 assertions, all pass. It
tokenises database.php and asserts nothing runs at include time, that dbQuery()
goes through the accessor, and that only the connection plumbing touches the
global. Verified it reports the pre-refactor file's `if ( !dbConnect() )` - an
earlier version of the check skipped tokens inside parentheses and so passed on
exactly the code it exists to reject.
Not covered by tests: behaviour when the database is genuinely unreachable, and
the session handler against a live database. Needs manual testing on an
installed tree, including stopping mysql to confirm the error view still renders
for both a web request and an API request.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01477mR97vfnK6zczbHgzq6T
Deleted monitors are excluded from every monitor listing, so once a monitor
is deleted there is no way to find it again from the ui - which matters
because deleting is reversible, the monitor edit form has an undelete
checkbox for exactly that.
Add Deleted as a pseudo status in the Status filter. Selected on its own it
lists only the deleted monitors; selected alongside real statuses it adds
them to that selection rather than intersecting with it, which would always
be empty; not selected, listings stay restricted to live monitors as before.
Deleted is deliberately not matched against Monitor_Status. Whatever row a
deleted monitor left behind is stale - its daemons were stopped when it was
deleted - so filtering on it would drop the monitors we are trying to find.
For the same reason a deleted monitor is reported as Deleted rather than the
status on that row, is drawn with the error dot, is labelled in the list,
and does not get a link to a stream that is not running.
The three queries that hardcoded Deleted=false now share one function, so
the console page, the console ajax endpoint and getFilteredMonitorIds()
cannot disagree about what the filter means. Each passes its own status
column expression, which differ: the ajax endpoint coalesces a WebSite
monitor to Running.
tests/php/test_monitor_status_filter.php covers the sql and the bind value
ordering for all four cases, including a bare string from a cookie written
before the filter became a multi-select. Verified against a live install:
13 deleted and 19 live monitors return 19 with no filter, 13 for Deleted,
and 20 for Deleted plus NotRunning.
web/ajax/console.php was returning ONVIF_Alarm_Text under the
ONVIF_Event_Listener key whenever the column existed (always), so
console.js displayed "Use ONVIF 'MotionAlarm'" for every monitor
regardless of whether the listener was actually enabled.
Gate the alarm text on the actual ONVIF_Event_Listener boolean,
returning 0 otherwise so the JS falsy check works as intended.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove getFilterFromRequestOrCookie(); require _monitor_filters.php and
call getFilterSelection() directly instead (addresses duplicate logic)
- Replace filter_var(FILTER_VALIDATE_INT) with validCardinal() for
consistency with existing codebase utility functions
- Guard MonitorName and Source against getFilterSelection() returning an
array when the cookie value happens to be valid JSON
refs #4745
Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/3d5e3926-51cc-4da8-8707-eb82c7d5db29
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Add getFilterFromRequestOrCookie() helper that reads filter values from
$_REQUEST first, then falls back to zmFilter_* cookies, mirroring the
getFilterSelection() behavior on the PHP page-render side
- All monitor filters now use this helper so they persist after F5/navigation
- Replace plain SQL subquery for GroupId with Group::get_group_sql() which
correctly includes monitors in child groups, consistent with the PHP side
- Add integer validation for ID-based filters (GroupId, ServerId, StorageId,
MonitorId) to guard against tampered cookie values
- Add require_once for Group.php in queryRequest()
fixes#4745
Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/4f4372ca-129e-4845-93a2-75ee9c7ecede
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
Remove the bootstrap-table HTML export which included raw span tags
in output. Replace with a proper JSON export containing all monitor
fields, zones, and group names suitable for re-import. Use a
temporary anchor element for download to avoid blanking the page.
Also move Source column status class from span to td via
bootstrap-table _class attribute.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add data-monitor-width and data-monitor-height attributes to thumbnail
img elements in console.php, events.php, watch.php, and monitor.php.
Use these to calculate the scale parameter as a ratio of overlay size to
monitor native resolution, clamped to 5-100%, instead of hardcoding
scale=75 (overlay) and scale=32 (fallback).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename applies to Go2RTC, Janus, and RTSP2Web streaming options.
Update enum values from Primary/Secondary to Restream/CameraDirectPrimary/CameraDirectSecondary.
- Add db migration zm_update-1.37.79.sql to rename column and migrate data
- Update C++ enum StreamChannelOption and member stream_channel
- Update PHP getStreamChannelOptions() method
- Update all JavaScript references
- Auto-select CameraDirectPrimary when Restream option becomes disabled
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add status bar below video with LIVE indicator for live streams
- Show pulsing red dot animation for live streams
- Display wall clock time that updates as recorded video plays
- Add event start time data attribute to console and events pages
- Hide status bar when no content to display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Support go2rtc, RTSP2Web, and Janus streaming in thumbnail popups
- Separate still thumbnail logic from hover overlay streaming
- Still thumbnails use event snapshot when not decoding
- Hover overlay prioritizes live streaming over recorded video
- Refactor thumbnail JS into focused helper functions
- Fix video-stream.js path for dynamic import
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Go2rtc, rtsp2web, and janus work independently of ZM's decode/analyze
processes. Check for these external streaming methods before checking
Analysing/Decoding state to ensure live streaming is used when available.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a monitor is not analyzing or decoding, the console thumbnail now
shows the most recent event's snapshot instead of attempting a live
stream. The hover overlay plays the event replay (with mp4 support if
available).
For active monitors with go2rtc enabled, the hover overlay now uses
go2rtc WebRTC streaming via the video-stream custom element, with a
3-second fallback to MJPEG if the connection fails.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Modified web/ajax/console.php to generate filter querystrings for each event period (Hour, Day, Week, Month, Archived, Total)
- Modified web/skins/classic/views/js/console.js to use filter querystrings in event links
- Links now include proper date filters matching old console.php implementation
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Added Sequence field to row data in ajax/console.php
- Sequence is now available for bootstrap-table to sort by (already configured as default)
- Sequence column remains hidden (not defined in table headers)
- Ensures table sorts by monitor sequence order by default
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Removed session access and manipulation code
- Changed from session_filters to request_filters using $_REQUEST directly
- Removed zm_session_start() and session_write_close() calls
- AJAX handler now stateless, getting all filter values from request parameters
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Added MonitorId to session_filters array
- Implemented MonitorId filtering logic to filter monitors by selected IDs
- Handles both single value and array of monitor IDs
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Initialize params.data to empty object if undefined to prevent errors
- Improve validation logic to consistently handle arrays vs strings
- Make type checking more explicit and readable
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Fixed array field detection in ajaxRequest using endsWith('[]')
- Improved multi-value field detection in monitorFilterOnChange
- Fixed empty value check to properly handle '0' as valid value
- Added proper initialization of array fields in params.data
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Fixed cookie naming to use consistent names without array brackets
- Updated JavaScript to separate field names from cookie names
- Simplified empty value check in AJAX handler using empty()
- Consolidated cookie restoration logic in _monitor_filters.php
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Added cookie restoration in _monitor_filters.php as fallback when session is empty
- Fixed empty array comparison in AJAX console handler
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
- Updated Group::get_group_dropdown() to accept view parameter and use monitorFilterOnChange for console view
- Updated ajaxRequest in console.js to include filter form data in params.data
- Added session update logic in AJAX console handler to persist filter selections
- Added cookie storage in monitorFilterOnChange for client-side persistence
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
Merged commit 52ea542 from SteveGilvarry/ZoneMinder.
Bandwidth was showing as a raw number without units, making it
difficult to interpret. Now formats bandwidth with appropriate
units (B/s, kB/s, MB/s, GB/s) using human_filesize() function
with 1024 as the conversion step.
Server-side formatting is consistent with event disk space
formatting and reduces client-side processing.
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
* experiment with namespaces on the Server class
* experiment with namespaces on the Server class
* Implement the ZM namespace on objects
* Implement the ZM namespace on objects
* Implement the ZM namespace on objects