Files
zoneminder/web/includes/config.php.in
T
Nic Boet 656a94c9bd feat: make options set in zm.conf or conf.d read-only on the Options page refs #5289
zm.conf and conf.d are set by the system owner and win over the Config
table in PHP, Perl and, since 4d7609d36, the daemons. The Options page
still offered those options for editing and showed the database value,
with a note only when the two differed.

An option a file sets is now shown with the value in effect (a boolean
word read as in configFileValue()), its input disabled, and one line
naming the file ('OptionSetInConfigFile', through translate()). That
line replaces the old "overriden via configuration files" note. A row
whose option a file sets gets no data-requires, so the page script
doesn't enable its input again, and the Requires check of other options
reads the value in effect.

actions/options.php skips options a file sets. A disabled checkbox is
left out of the form, and the action saved a missing boolean as 0, so
without the skip a save of the tab would have reset the Config row; a
hand-made POST can't change it either. The Config row keeps whatever the
admin last saved, which applies again once the file line is removed.

loadConfigFiles() is split out of loadConfig() so the view and the
action can tell which names come from a file and which file. Each entry
carries that file; loadConfig() drops the key before returning, so
configs/viewByName answers as before.

docs/userguide/configfiles.rst describes setting an option in a file.
2026-10-16 09:24:48 -05:00

387 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;
}
// Reads zm.conf, then every conf.d/*.conf in order, later files winning. Each entry
// keeps the file it came from in 'File', so callers can tell what was set locally.
function loadConfigFiles() {
$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
$file_config = process_configfile($configFile);
if (!$file_config) $file_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) $file_config = array_merge($file_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);
}
return $file_config;
}
function loadConfig( $defineConsts=true ) {
$initial_config = loadConfigFiles();
# 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) {
unset($initial_config[$name]['File']); # the API returns these entries whole
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], 'File'=>$configFile];
} 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;
}
?>