Files
zoneminder/web/includes/config.php.in
T
Isaac ConnorandClaude Opus 5.5 7603a7458f fix: read yes/no, true/false and on/off in config files as booleans in PHP and Perl
A boolean option set in zm.conf or conf.d wins over the Config table, and a
person editing a file writes yes or off rather than 1 or 0. The C++ daemons
now read those words (ConfigFileValue, 4d7609d36), but PHP and Perl took the
value as written, and a non-empty string is true in both. So
ZM_STRICT_VIDEO_CONFIG=off in conf.d left the option on in the web UI and the
Perl scripts while zmc turned it off.

Read a file value for a boolean option the same way in all three: yes, true
and on are 1; no, false and off are 0; anything else stays as written. Only
booleans are read this way, so a string option such as a title keeps "Yes".

PHP defines the file values as constants before it reads the Config table,
since it needs the database settings to connect, and only the table says
which options are booleans. So a word now waits until the table has been
read, apart from ZM_DB_* settings, whose words database.php reads itself.
Perl reads the type with each row.

Tested on 1.38's zm138 database with a conf.d file setting
ZM_OPT_FAST_DELETE=yes (0 in the table), ZM_STRICT_VIDEO_CONFIG=off (1)
and ZM_WEB_TITLE=Yes. The installed PHP and Perl gave 'yes', 'off' and
'Yes'; these give '1', '0' and 'Yes'. tests/php/test_config_file_booleans.php
checks the rule.

refs #5289

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:37:07 -04:00

379 lines
14 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
);
// A boolean written as a word in zm.conf or conf.d: '1' or '0', or null if $value isn't one.
function configFileWord($value) {
$word = strtolower(trim($value));
if (in_array($word, array('yes', 'true', 'on'), true)) return '1';
if (in_array($word, array('no', 'false', 'off'), true)) return '0';
return null;
}
// A value from zm.conf or conf.d as the Config table would hold it for an option of $type:
// a boolean may be written yes/no, true/false or on/off. Same rule as ConfigFileValue() in
// src/zm_config.cpp and ZoneMinder::Config.
function configFileValue($value, $type) {
if ($type != 'boolean') return $value;
$word = configFileWord($value);
return $word === null ? $value : $word;
}
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
# A boolean option may be written as a word in a file (yes/no, true/false, on/off), and
# its type is only known once the Config table has been read, so a word waits until
# then. Database settings can't wait, and database.php reads their words itself.
$deferred = array();
if ($defineConsts) {
foreach ( $initial_config as $key => $config ) {
if (configFileWord($config['Value']) !== null and strpos($key, 'ZM_DB_') !== 0) {
$deferred[$key] = $config['Value'];
continue;
}
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])) {
# Set by the owner of the box, so it wins over the database value.
$config[$name]['Value'] = configFileValue($initial_config[$name]['Value'], $row['Type']);
unset($initial_config[$name]);
if (isset($deferred[$name])) {
define($name, $config[$name]['Value']);
unset($deferred[$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);
}
}
}
# A word for an option the Config table doesn't have stays as written.
foreach ($deferred as $name => $value) define($name, $value);
# What is left exists only in zm.conf / conf.d: database credentials, paths and the
# like. Those have no Private column, and consumers such as skin.js.php export every
# entry that is not marked Private to the browser, so mark them Private unless they
# are known to be needed client side. refs GHSA-jv9c-pg2m-5m6m
$public_file_config = array('ZM_DIR_SOUNDS');
foreach ($initial_config as $name => $c) {
if (!isset($initial_config[$name]['Private']))
$initial_config[$name]['Private'] = in_array($name, $public_file_config) ? 0 : 1;
}
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', $s->Id());
}
}
if (!$thisServer->Id()) {
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->Id()) {
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;
}
?>