mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-21 02:04:26 -04:00
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
337 lines
11 KiB
Plaintext
337 lines
11 KiB
Plaintext
<?php
|
|
//
|
|
// ZoneMinder web configuration file, $Date$, $Revision$
|
|
// Copyright (C) 2001-2008 Philip Coombes
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
//
|
|
// This section contains options substituted by the zmconfig.pl utility, do not edit these directly
|
|
//
|
|
define( 'ZM_CONFIG', '@ZM_CONFIG@' ); // Path to config file
|
|
define( 'ZM_CONFIG_SUBDIR', '@ZM_CONFIG_SUBDIR@' ); // Path to config subfolder
|
|
// Define, and override any given in config file
|
|
define( 'ZM_VERSION', '@VERSION@' ); // Version
|
|
define( 'ZM_DIR_TEMP', '@ZM_TMPDIR@' );
|
|
define( 'ZM_DIR_CACHE', '@ZM_CACHEDIR@' );
|
|
|
|
//
|
|
// If setup supports Video 4 Linux v2
|
|
//
|
|
define('ZM_HAS_V4L2', '@ZM_HAS_V4L2@'); // V4L2 support enabled
|
|
|
|
//
|
|
// If ONVIF support has been built in
|
|
//
|
|
define('ZM_HAS_ONVIF', '@ZM_HAS_ONVIF@'); // ONVIF support enabled
|
|
|
|
//
|
|
// If PCRE dev libraries are installed
|
|
//
|
|
define('ZM_PCRE', '@ZM_PCRE@'); // PCRE support enabled
|
|
|
|
//
|
|
// Alarm states
|
|
//
|
|
define('STATE_UNKNOWN', 0);
|
|
define('STATE_IDLE', 1);
|
|
define('STATE_PREALARM', 2);
|
|
define('STATE_ALARM', 3);
|
|
define('STATE_ALERT', 4);
|
|
|
|
define('TRIGGER_ACTION_GET', 1);
|
|
define('TRIGGER_ACTION_SET', 2);
|
|
define('TRIGGER_ACTION_RELOAD', 4);
|
|
define('TRIGGER_ACTION_SUSPEND', 16);
|
|
define('TRIGGER_ACTION_RESUME', 32);
|
|
|
|
define('TRIGGER_CANCEL', 0);
|
|
define('TRIGGER_ON', 1);
|
|
define('TRIGGER_OFF', 2);
|
|
|
|
define('ANALYSING_NONE', 1);
|
|
define('ANALYSING_ALWAYS', 2);
|
|
|
|
//
|
|
// DVR Control Commands
|
|
//
|
|
|
|
define('MSG_CMD', 1);
|
|
define('MSG_DATA_WATCH', 2);
|
|
define('MSG_DATA_EVENT', 3);
|
|
|
|
define('CMD_NONE', 0);
|
|
define('CMD_PAUSE', 1);
|
|
define('CMD_PLAY', 2);
|
|
define('CMD_STOP', 3);
|
|
define('CMD_FASTFWD', 4);
|
|
define('CMD_SLOWFWD', 5);
|
|
define('CMD_SLOWREV', 6);
|
|
define('CMD_FASTREV', 7);
|
|
define('CMD_ZOOMIN', 8);
|
|
define('CMD_ZOOMOUT', 9);
|
|
define('CMD_PAN', 10);
|
|
define('CMD_SCALE', 11);
|
|
define('CMD_PREV', 12);
|
|
define('CMD_NEXT', 13);
|
|
define('CMD_SEEK', 14 );
|
|
define('CMD_VARPLAY', 15);
|
|
define('CMD_GET_IMAGE', 16);
|
|
define('CMD_QUIT', 17);
|
|
define('CMD_MAXFPS', 18);
|
|
define('CMD_ANALYZE_ON', 19);
|
|
define('CMD_ANALYZE_OFF', 20);
|
|
define('CMD_ZOOMSTOP', 21);
|
|
define('CMD_QUERY', 99);
|
|
|
|
//
|
|
// These are miscellaneous options you won't normally need to change
|
|
//
|
|
define('MAX_EVENTS', 10); // The maximum number of events to show in the monitor event listing
|
|
define('RATE_BASE', 100); // The additional scaling factor used to help get fractional rates in integer format
|
|
define('SCALE_BASE', 100); // The additional scaling factor used to help get fractional scales in integer format
|
|
|
|
//
|
|
// Date and time formats, not to be modified by language files
|
|
//
|
|
define('STRF_FMT_DATETIME_DB', 'Y-m-d H:i:s'); // Strftime format for database queries, don't change
|
|
define('MYSQL_FMT_DATETIME_SHORT', 'Y/m/d H:i:s'); // MySQL date_format shorter format for dates with time
|
|
|
|
global $dateFormatter;
|
|
global $dateTimeFormatter;
|
|
global $timeFormatter;
|
|
$dateFormatter = new IntlDateFormatter(null, IntlDateFormatter::SHORT, IntlDateFormatter::NONE);
|
|
$dateTimeFormatter = new IntlDateFormatter(null, IntlDateFormatter::SHORT, IntlDateFormatter::LONG);
|
|
$timeFormatter = new IntlDateFormatter(null, IntlDateFormatter::NONE, IntlDateFormatter::LONG);
|
|
|
|
global $zm_config;
|
|
$zm_config = loadConfig();
|
|
ZM\Logger::fetch()->initialise();
|
|
//
|
|
// This section is options normally derived from other options or configuration
|
|
//
|
|
define('ZMU_PATH', ZM_PATH_BIN.'/zmu'); // Local path to the ZoneMinder Utility
|
|
|
|
$locale = ZM_LOCALE_DEFAULT;
|
|
$locales = ResourceBundle::getLocales('');
|
|
if ($locale) {
|
|
if (!array_search($locale, $locales)) {
|
|
ZM\Warning("Locale $locale does not seem to be valid.");
|
|
$locale = locale_get_default();
|
|
}
|
|
} else {
|
|
#ZM\Debug('Using system locale');
|
|
$locale = locale_get_default();
|
|
}
|
|
|
|
try {
|
|
if (ZM_TIMEZONE) {
|
|
$dateFormatter = new IntlDateFormatter($locale, IntlDateFormatter::SHORT, IntlDateFormatter::NONE, ZM_TIMEZONE);
|
|
$dateTimeFormatter = new IntlDateFormatter($locale, IntlDateFormatter::SHORT, IntlDateFormatter::LONG, ZM_TIMEZONE);
|
|
$timeFormatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, IntlDateFormatter::LONG, ZM_TIMEZONE);
|
|
} else {
|
|
$dateFormatter = new IntlDateFormatter($locale, IntlDateFormatter::SHORT, IntlDateFormatter::NONE);
|
|
$dateTimeFormatter = new IntlDateFormatter($locale, IntlDateFormatter::SHORT, IntlDateFormatter::LONG);
|
|
$timeFormatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, IntlDateFormatter::LONG);
|
|
}
|
|
} catch(Exception $e) {
|
|
error_log($e->getMessage());
|
|
$dateFormatter = new IntlDateFormatter(null, IntlDateFormatter::SHORT, IntlDateFormatter::NONE);
|
|
$dateTimeFormatter = new IntlDateFormatter(null, IntlDateFormatter::SHORT, IntlDateFormatter::LONG);
|
|
$timeFormatter = new IntlDateFormatter(null, IntlDateFormatter::NONE, IntlDateFormatter::LONG);
|
|
}
|
|
|
|
try {
|
|
# PHP 8.1.26 made these throw an exception if locale is invalid so have to try
|
|
if (ZM_DATE_FORMAT_PATTERN) {
|
|
$dateFormatter->setPattern(ZM_DATE_FORMAT_PATTERN);
|
|
}
|
|
if (ZM_DATETIME_FORMAT_PATTERN) {
|
|
$dateTimeFormatter->setPattern(ZM_DATETIME_FORMAT_PATTERN);
|
|
}
|
|
if (ZM_TIME_FORMAT_PATTERN) {
|
|
$timeFormatter->setPattern(ZM_TIME_FORMAT_PATTERN);
|
|
}
|
|
} catch(\Error $e) {
|
|
error_log($e->getMessage());
|
|
}
|
|
|
|
$GLOBALS['defaultUser'] = array(
|
|
'Id' => 0,
|
|
'Username' => 'admin',
|
|
'Password' => '',
|
|
'Language' => '',
|
|
'Enabled' => 1,
|
|
'Stream' => 'View',
|
|
'Events' => 'Edit',
|
|
'Control' => 'Edit',
|
|
'Monitors' => 'Create',
|
|
'Groups' => 'Edit',
|
|
'Devices' => 'Edit',
|
|
'Snapshots' => 'Edit',
|
|
'System' => 'Edit',
|
|
'MaxBandwidth' => '',
|
|
'HomeView' => '',
|
|
'MonitorIds' => false
|
|
);
|
|
|
|
function loadConfig( $defineConsts=true ) {
|
|
$configFile = ZM_CONFIG;
|
|
$localConfigFile = basename($configFile);
|
|
if ( file_exists($localConfigFile) && filesize($localConfigFile) > 0 ) {
|
|
if ( php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']) )
|
|
print("Warning, overriding installed $localConfigFile file with local copy\n");
|
|
else
|
|
error_log("Warning, overriding installed $localConfigFile file with local copy");
|
|
$configFile = $localConfigFile;
|
|
}
|
|
|
|
# Process name, value pairs from the main config file first
|
|
$initial_config = process_configfile($configFile);
|
|
if (!$initial_config) $initial_config = [];
|
|
|
|
# Search for user created config files. If one or more are found then
|
|
# update our config value array with those values
|
|
if ( is_dir(ZM_CONFIG_SUBDIR) ) {
|
|
if ( is_readable(ZM_CONFIG_SUBDIR) ) {
|
|
foreach ( glob(ZM_CONFIG_SUBDIR.'/*.conf') as $filename ) {
|
|
$newconfig = process_configfile($filename);
|
|
if ($newconfig) $initial_config = array_merge($initial_config, $newconfig);
|
|
}
|
|
} else {
|
|
error_log('WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on '.$configSubFolder);
|
|
}
|
|
} else {
|
|
error_log('WARNING: ZoneMinder configuration subfolder found but is not a directory. Check '.$configSubFolder);
|
|
}
|
|
|
|
# Need initial config defines in order to connect to db
|
|
# Now that our array our finalized, define each key => value
|
|
# pair in the array as a constant
|
|
if ($defineConsts) {
|
|
foreach ( $initial_config as $key => $config ) {
|
|
define($key, $config['Value']);
|
|
}
|
|
}
|
|
|
|
# Must have DB defines in place before including database.php
|
|
require_once('database.php');
|
|
require_once('logger.php');
|
|
|
|
$config = [];
|
|
|
|
$dbConn = zmDbConn();
|
|
$result = $dbConn->query('SELECT Name,Value,Type,Private FROM Config');
|
|
if ( !$result )
|
|
echo mysql_error();
|
|
while( $row = dbFetchNext($result) ) {
|
|
$name = $row['Name'];
|
|
|
|
$config[$name] = $row;
|
|
if (isset($initial_config[$name])) {
|
|
$config[$name]['Value'] = $initial_config[$name]['Value'];
|
|
unset($initial_config[$name]);
|
|
}
|
|
|
|
if ( $defineConsts ) {
|
|
# Values in conf.d files override db so check if already defined and update value
|
|
if (!defined($name)) {
|
|
define($name, $row['Value']);
|
|
} else {
|
|
$config[$name]['Value'] = constant($name);
|
|
}
|
|
}
|
|
}
|
|
return array_merge($config, $initial_config); # Merge any remaining config not in db
|
|
} # end function loadConfig
|
|
|
|
require_once('Server.php');
|
|
global $Servers; # Ensure that it is global, api doesn't specifically make it so
|
|
$Servers = ZM\Server::find([], ['order'=>'lower(Name)']);
|
|
$thisServer = new ZM\Server();
|
|
|
|
// For Human-readability, use ZM_SERVER_HOST or ZM_SERVER_NAME in zm.conf, and convert it here to a ZM_SERVER_ID
|
|
if (!defined('ZM_SERVER_ID')) {
|
|
if (defined('ZM_SERVER_NAME') and ZM_SERVER_NAME) {
|
|
foreach ($Servers as $s) {
|
|
if ($s->Name() == ZM_SERVER_NAME) {
|
|
$thisServer = $s;
|
|
define('ZM_SERVER_ID', $Server->Id());
|
|
}
|
|
}
|
|
if (!$thisServer) {
|
|
error_log('Invalid Multi-Server configration detected. ZM_SERVER_NAME set to ' . ZM_SERVER_NAME . ' in zm.conf, but no corresponding entry found in Servers table.');
|
|
}
|
|
} else if (defined('ZM_SERVER_HOST') and ZM_SERVER_HOST) {
|
|
foreach ($Servers as $s) {
|
|
if ($s->Name() == ZM_SERVER_HOST) {
|
|
$thisServer = $s;
|
|
define('ZM_SERVER_ID', $s->Id());
|
|
}
|
|
}
|
|
if (!$thisServer) {
|
|
error_log('Invalid Multi-Server configration detected. ZM_SERVER_HOST set to ' . ZM_SERVER_HOST . ' in zm.conf, but no corresponding entry found in Servers table.');
|
|
}
|
|
}
|
|
} else {
|
|
$thisServer = ZM\Server::find_one(['Id'=>ZM_SERVER_ID]); // Should be cached
|
|
if(!$thisServer) {
|
|
error_log('No server found for ZM_SERVER_ID '+ZM_SERVER_ID);
|
|
}
|
|
}
|
|
|
|
if ( defined('ZM_TIMEZONE') and ZM_TIMEZONE )
|
|
ini_set('date.timezone', ZM_TIMEZONE);
|
|
|
|
function process_configfile($configFile) {
|
|
$config = array();
|
|
if (is_readable($configFile)) {
|
|
$cfg = fopen($configFile, 'r') or error_log('Could not open config file: '.$configFile);
|
|
$logical = '';
|
|
while ( !feof($cfg) ) {
|
|
$str = fgets($cfg);
|
|
if ($str === false) break;
|
|
$str = preg_replace('/\r?\n\z/', '', $str);
|
|
// Trailing backslash (with optional whitespace) continues the value on
|
|
// the next physical line. Leading whitespace on continuation lines is
|
|
// stripped so users can indent for readability.
|
|
if ($logical !== '') {
|
|
$str = ltrim($str, " \t");
|
|
}
|
|
if (preg_match('/^(.*?)\\\\[ \t]*$/', $str, $m)) {
|
|
$logical .= $m[1];
|
|
continue;
|
|
}
|
|
$logical .= $str;
|
|
$line = $logical;
|
|
$logical = '';
|
|
if ( preg_match('/^\s*(#.*)?$/', $line) ) {
|
|
continue;
|
|
} else if ( preg_match('/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $line, $matches) ) {
|
|
$config[$matches[1]] = ['Name'=>$matches[1], 'Value'=>$matches[2]];
|
|
} else {
|
|
error_log("Malformed line in config $configFile\n$line");
|
|
}
|
|
}
|
|
fclose($cfg);
|
|
} else {
|
|
error_log('WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on '.$configFile);
|
|
}
|
|
return $config;
|
|
}
|
|
|
|
?>
|