Merge branch 'master' into patch-435415

This commit is contained in:
IgorA100
2026-04-11 14:51:53 +03:00
committed by GitHub
26 changed files with 170 additions and 78 deletions

View File

@@ -116,7 +116,11 @@ jobs:
run: |
set -eux
mkdir -p artifacts/deb
mv ../*.deb ../*.buildinfo ../*.changes ../*.dsc ../*.tar.xz ../*.tar.gz artifacts/deb/ || true
# Only collect binary artifacts. Source files (.dsc, .orig.tar.*,
# .debian.tar.*) are not needed for binary uploads and the orig
# tarball has the same filename across distros which confuses
# mini-dinstall.
mv ../*.deb ../*.buildinfo ../*.changes artifacts/deb/ || true
# quick verify signatures (non-fatal)
gpg --verify artifacts/deb/*.changes || true
gpg --verify artifacts/deb/*.buildinfo || true

View File

@@ -112,7 +112,11 @@ jobs:
set -eux
mkdir -p artifacts/deb
ls -l ../
mv ../*.deb ../*.buildinfo ../*.changes ../*.dsc ../*.tar.xz ../*.tar.gz artifacts/deb/ || true
# Only collect binary artifacts. Source files (.dsc, .orig.tar.*,
# .debian.tar.*) are not needed for binary uploads and the orig
# tarball has the same filename across distros which confuses
# mini-dinstall.
mv ../*.deb ../*.buildinfo ../*.changes artifacts/deb/ || true
# quick verify signatures (non-fatal)
gpg --verify artifacts/deb/*.changes || true
gpg --verify artifacts/deb/*.buildinfo || true

View File

@@ -98,22 +98,24 @@ sub Execute {
$sql =~ s/zmSystemLoad/$load/g;
}
Debug("Filter::Execute SQL ($sql)");
my $sth = $ZoneMinder::Database::dbh->prepare($sql)
or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
my $sth = $ZoneMinder::Database::dbh->prepare($sql);
if (!$sth) {
Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
return;
}
my $res = $sth->execute();
if ( !$res ) {
Error("Can't execute filter '$sql', ignoring: ".$sth->errstr());
return;
}
Debug("Filter::Execute SQL ($sql)");
my @results;
while ( my $event = $sth->fetchrow_hashref() ) {
push @results, $event;
}
$sth->finish();
Debug('Loaded ' . @results . ' events for filter '.$$self{Name}.' using query ('.$sql.')"');
if ( $self->{PostSQLConditions} ) {
if ($self->{PostSQLConditions} and @{$self->{PostSQLConditions}}) {
my @filtered_events;
foreach my $term ( @{$$self{PostSQLConditions}} ) {
if ( $$term{attr} eq 'ExistsInFileSystem' ) {

View File

@@ -76,6 +76,7 @@ our %EXPORT_TAGS = (
Dump
Debug
Info
Warn
Warning
Error
Fatal
@@ -741,6 +742,7 @@ sub info {
$log->logPrint(INFO, @_, caller);
}
sub Warn { fetch()->logPrint(WARNING, @_, caller); }
sub Warning { fetch()->logPrint(WARNING, @_, caller); }
sub warn {
my $log = shift;

View File

@@ -347,12 +347,11 @@ sub control {
my $command = shift;
my $process = shift;
my $valid_device = (defined $monitor->{Device} and $monitor->{Device} =~ /^\/dev\/[\w\/.\-]+$/);
if ($monitor->{Type} eq 'Local' and !$valid_device) {
Error("Invalid device path rejected: $monitor->{Device}");
return;
} elsif (!$valid_device and defined $monitor->{Device} and length($monitor->{Device})) {
Warning("Monitor $$monitor{Id} has invalid device path: $monitor->{Device}");
if ($monitor->{Type} eq 'Local') {
if (!defined $monitor->{Device} or $monitor->{Device} !~ /^\/dev\/[\w\/.\-]+$/) {
Error("Invalid device path rejected: $monitor->{Device}");
return;
}
}
if ($command eq 'stop') {

View File

@@ -127,7 +127,15 @@ sub CpuUsage {
} else {
# Get CPU utilization percentages
my $top_output = `top -b -n 1 | grep -i "^%Cpu(s)" | awk '{print \$2, \$4, \$6, \$8}'`;
my $top_output = '';
my $uname_output = lc(qx($ZoneMinder::Config{ZM_PATH_UNAME} -s));
chomp($uname_output);
## FreeBSD
if ($uname_output eq "freebsd") {
$top_output = `top -b -n 1 | grep "^CPU" | sed 's/%//g' | awk '{print \$2, \$6, \$4, \$10}'`;
} else {
$top_output = `top -b -n 1 | grep -i "^%Cpu(s)" | awk '{print \$2, \$4, \$6, \$8}'`;
}
my ($user, $system, $nice, $idle) = split(/ /, $top_output);
$user =~ s/[^\d\.]//g;
$system =~ s/[^\d\.]//g;

View File

@@ -277,9 +277,16 @@ sub run {
die 'Can\'t open pid file at '.ZM_PID."\n";
}
my $fd = 0;
# Redirect stdin/stdout/stderr to /dev/null rather than closing them.
# Closing them causes the FDs to be reused, which led to memory corruption
# when libx264 wrote to a reused stderr FD (66f11435b). Redirecting to
# /dev/null keeps valid FDs that children inherit safely.
open(STDIN, '<', '/dev/null') or die "Can't redirect STDIN: $!";
open(STDOUT, '>', '/dev/null') or die "Can't redirect STDOUT: $!";
open(STDERR, '>', '/dev/null') or die "Can't redirect STDERR: $!";
# This also closes dbh and CLIENT and SERVER
# Close all remaining FDs (dbh, CLIENT, SERVER, etc.)
my $fd = 3;
while ( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) {
POSIX::close($fd++);
}

View File

@@ -161,28 +161,19 @@ if ( ! ( $filter_name or $filter_id ) ) {
}
my @filters;
my $last_action = 0;
my $last_reload = 0;
while (!$zm_terminate) {
my $delay;
my $now = time;
if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) {
Debug('Reloading filters');
$last_action = $now;
if (($now - $last_reload) > $Config{ZM_FILTER_RELOAD_DELAY}) {
$last_reload = $now;
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
}
foreach my $filter (@filters) {
last if $zm_terminate;
my $elapsed = ($now - ($$filter{last_ran} ? $$filter{last_ran} : 0));
my $filter_delay = $$filter{ExecuteInterval} - $elapsed;
Warn("Filter $$filter{Name} is taking ".(-$filter_delay)." seconds longer than execute interval.") if $filter_delay < 0;
if (!defined($delay) or $filter_delay < $delay) {
$delay = $filter_delay > 0 ? $filter_delay : 0;
Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed for $$filter{Name}");
}
if ($$filter{Concurrent} and !($filter_id or $filter_name)) {
my ( $proc ) = $0 =~ /(\S+)/;
my ( $id ) = $$filter{Id} =~ /(\d+)/;
@@ -193,6 +184,15 @@ while (!$zm_terminate) {
checkFilter($filter);
$$filter{last_ran} = $now;
}
$now = time;
my $elapsed = ($now - ($$filter{last_ran} ? $$filter{last_ran} : $now));
my $filter_delay = $$filter{ExecuteInterval} - $elapsed;
Warning("Filter $$filter{Name} is taking ".(-$filter_delay)." seconds longer than execute interval $$filter{ExecuteInterval}.") if $filter_delay < 0;
if (!defined($delay) or $filter_delay < $delay) {
$delay = $filter_delay > 0 ? $filter_delay : 0;
Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed for $$filter{Name}");
}
} # end foreach filter
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;

View File

@@ -2720,9 +2720,12 @@ void Image::Fill(Rgb colour, int density, const Polygon &polygon) {
std::sort(active_edges.begin(), active_edges.end(), PolygonFill::Edge::CompareX);
if (!(scan_line % density)) {
for (auto it = active_edges.begin(); it < active_edges.end() - 1; ++it) {
// Fill between pairs of active edges (parity rule). Stepping one
// edge at a time would incorrectly fill the gaps between arms of
// a non-convex polygon (e.g. a banana shape).
for (auto it = active_edges.begin(); it + 1 < active_edges.end(); it += 2) {
int32 lo_x = static_cast<int32>(it->min_x);
int32 hi_x = static_cast<int32>(std::next(it)->min_x);
int32 hi_x = static_cast<int32>((it + 1)->min_x);
if (colours == ZM_COLOUR_GRAY8) {
uint8 *p = &buffer[(scan_line * width) + lo_x];

View File

@@ -676,8 +676,8 @@ int LocalCamera::Initialise() {
if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) {
v4l2_jpegcompression jpeg_comp;
if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) {
if (errno == EINVAL) {
Debug(2, "JPEG compression options are not available");
if (errno == EINVAL || errno == ENOTTY) {
Debug(2, "JPEG compression options are not available: %s", strerror(errno));
} else {
Warning("Failed to get JPEG compression options: %s", strerror(errno));
}

View File

@@ -3708,6 +3708,7 @@ int Monitor::Pause() {
convert_context = nullptr;
}
decoding_image_count = 0;
if (shared_data) shared_data->last_write_index = image_buffer_count;
}
if (analysis_thread) {
Debug(1, "Joining analysis");

View File

@@ -85,6 +85,9 @@ bool Monitor::MonitorLink::connect() {
if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(1)) {
last_connect_time = std::chrono::system_clock::to_time_t(now);
// Clean up any existing resources before reconnecting to avoid fd leaks
disconnect();
mem_size = sizeof(SharedData) + sizeof(TriggerData);
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
@@ -160,27 +163,25 @@ bool Monitor::MonitorLink::connect() {
} // end bool Monitor::MonitorLink::connect()
bool Monitor::MonitorLink::disconnect() {
if (connected) {
connected = false;
connected = false;
#if ZM_MEM_MAPPED
if (mem_ptr > (void *)0) {
msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size);
}
if (map_fd >= 0)
close(map_fd);
if (mem_ptr != nullptr && mem_ptr != MAP_FAILED) {
msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size);
}
if (map_fd >= 0)
close(map_fd);
map_fd = -1;
map_fd = -1;
#else // ZM_MEM_MAPPED
if (mem_ptr != nullptr) {
struct shmid_ds shm_data;
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
shm_id = 0;
if (shm_data.shm_nattch <= 1) {
if (shmctl(shm_id, IPC_RMID, 0) < 0) {
Debug(3, "Can't shmctl: %s", strerror(errno));
@@ -192,10 +193,15 @@ bool Monitor::MonitorLink::disconnect() {
Debug(3, "Can't shmdt: %s", strerror(errno));
return false;
}
#endif // ZM_MEM_MAPPED
mem_size = 0;
mem_ptr = nullptr;
}
shm_id = 0;
#endif // ZM_MEM_MAPPED
mem_size = 0;
mem_ptr = nullptr;
shared_data = nullptr;
trigger_data = nullptr;
zone_scores = nullptr;
return true;
}

View File

@@ -57,6 +57,7 @@ VideoStore::VideoStore(
audio_out_ctx(nullptr),
packets_written(0),
frame_count(0),
video_encoded(false),
hw_device_ctx(nullptr),
resample_ctx(nullptr),
fifo(nullptr),
@@ -239,12 +240,21 @@ bool VideoStore::open() {
if ((ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0) {
Warning("Can't open video codec (%s) %s", video_out_codec->name, av_make_error_string(ret).c_str());
video_out_codec = nullptr;
avcodec_free_context(&video_out_ctx);
}
} // end if video_out_codec
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
if (ret < 0) {
Error("Could not initialize stream parameters");
if (video_out_ctx) {
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
if (ret < 0) {
Error("Could not initialize stream parameters");
}
// Free the codec context now — it was only opened to generate new
// extradata for the stream parameters and is not used for encoding
// in PASSTHROUGH mode. Leaving it alive causes flush_codecs() to
// attempt flushing a codec that never received any frames, which
// crashes in avcodec_send_frame with newer FFmpeg.
avcodec_free_context(&video_out_ctx);
}
av_dict_free(&opts);
// Reload it for next attempt and/or avformat open
@@ -345,6 +355,11 @@ bool VideoStore::open() {
}
if (setup_hwaccel(video_out_ctx,
chosen_codec_data, hw_device_ctx, monitor->EncoderHWAccelDevice(), monitor->Width(), monitor->Height())) {
avcodec_free_context(&video_out_ctx);
av_dict_free(&opts);
if (hw_device_ctx) {
av_buffer_unref(&hw_device_ctx);
}
continue;
}
@@ -566,7 +581,9 @@ void VideoStore::flush_codecs() {
}
// I got crashes if the codec didn't do DELAY, so let's test for it.
if (video_out_ctx && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
// Also skip if no frames were ever sent — some encoders crash on flush
// when their internal state was never initialized by a real frame.
if (video_out_ctx && video_encoded && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
// First drain any pending packets before entering flush mode
// This prevents hangs when the encoder's internal buffer is full
Debug(1, "Draining pending packets before flush");
@@ -1297,6 +1314,7 @@ int VideoStore::writeVideoFramePacket(const std::shared_ptr<ZMPacket> zm_packet)
Debug(3, "Got EAGAIN");
}
} else {
video_encoded = true;
break;
}
} while (!zm_terminate);

View File

@@ -54,6 +54,7 @@ class VideoStore {
SWScale swscale;
unsigned int packets_written;
unsigned int frame_count;
bool video_encoded; // true once at least one frame has been sent to the video encoder
AVBufferRef *hw_device_ctx;

View File

@@ -303,7 +303,11 @@ int main(int argc, char *argv[]) {
time_t last_viewed = monitors[i]->getLastViewed();
int64 since_last_view = static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()) - last_viewed;
Debug(1, "Last view %jd= %" PRId64 " seconds since last view", last_viewed, since_last_view);
if (((!last_viewed) or (since_last_view > 10)) and (monitors[i]->GetLastWriteIndex() != -1)) {
if (!last_viewed or (since_last_view > 10)) {
// Nobody is watching — pause if running, otherwise stay paused.
// The previous GetLastWriteIndex() != -1 guard caused a
// Pause/Play cycle because Pause() resets the write index,
// making the guard false and falling through to Play().
if (monitors[i]->getCamera()->isPrimed()) {
monitors[i]->Pause();
}

View File

@@ -315,7 +315,7 @@ EOF
sudo apt-get install devscripts equivs
sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?"
DEBUILD=debuild -b -uc -us
DEBUILD="debuild -b -uc -us"
else
if [ $TYPE == "local" ]; then
# Auto-install all ZoneMinder's dependencies using the Debian control file

View File

@@ -63,7 +63,7 @@ class Monitor extends AppModel {
),
'Device' => array(
'validPath' => array(
'rule' => array('custom', '#^(/dev/[\w/.\-]+)?$#'),
'rule' => array('validDevicePath'),
'message' => 'Invalid device path. Must be a valid /dev/ path (e.g. /dev/video0).',
'allowEmpty' => true,
'required' => false,
@@ -72,6 +72,23 @@ class Monitor extends AppModel {
);
/**
* Validate the Device field. Only Local monitors pass Device to a shell,
* so the /dev/ restriction only applies when Type == 'Local'. Other Types
* may legitimately hold legacy values (e.g. an RTSP URL) in this column.
*/
public function validDevicePath($check) {
$value = reset($check);
if ($value === null || $value === '') {
return true;
}
$type = isset($this->data['Monitor']['Type']) ? $this->data['Monitor']['Type'] : null;
if ($type !== 'Local') {
return true;
}
return (bool)preg_match('#^/dev/[\w/.\-]+$#', $value);
}
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**

View File

@@ -58,8 +58,10 @@ if ($action == 'save') {
# For convenience
$newMonitor = $_REQUEST['newMonitor'];
# Validate Device path to prevent command injection (CVE-worthy)
if (!empty($newMonitor['Device'])) {
# Validate Device path to prevent command injection (CVE-worthy).
# Only Local monitors pass Device to a shell; for other Types the field
# is unused and may legitimately hold legacy values (e.g. an RTSP URL).
if (!empty($newMonitor['Device']) and isset($newMonitor['Type']) and $newMonitor['Type'] == 'Local') {
$newMonitor['Device'] = validDevicePath($newMonitor['Device']);
if ($newMonitor['Device'] === '') {
$error_message .= 'Invalid device path. Must be a valid /dev/ path (e.g. /dev/video0).</br>';

View File

@@ -2378,7 +2378,7 @@ function output_file($path, $chunkSize=1024) {
$new_length = $size - $range;
header('HTTP/1.1 206 Partial Content');
header("Content-Length: $new_length");
header("Content-Range: bytes $range$size2/$size");
header("Content-Range: bytes $range-$size2/$size");
} else {
$size2 = $size - 1;
header("Content-Range: bytes 0-$size2/$size");

View File

@@ -189,7 +189,7 @@ function MonitorStream(monitorData) {
this.show = function() {
const stream = this.getElement();
if (!stream.src) {
stream.src = this.url_to_zms+"&mode=single&scale="+this.scale+"&connkey="+this.connKey+'&'+auth_relay;
stream.src = this.url_to_zms+"&mode=single&scale="+this.scale+"&connkey="+this.connKey+(auth_relay?'&'+auth_relay:'');
}
};
@@ -607,9 +607,9 @@ function MonitorStream(monitorData) {
this.streamCommand(CMD_PLAY);
} else {
let src = this.url_to_zms.replace(/mode=single/i, 'mode=jpeg');
if (-1 == src.search('auth')) {
if (-1 == src.search('auth') && auth_relay) {
src += '&'+auth_relay;
} else {
} else if (-1 != src.search('auth')) {
src = src.replace(/auth=\w+/i, 'auth='+auth_hash);
}
if (-1 == src.search('connkey')) {
@@ -650,9 +650,9 @@ function MonitorStream(monitorData) {
if (!imgInfoBlock) return null;
let src = this.url_to_zms.replace(/mode=jpeg/i, 'mode=single');
if (-1 == src.search('auth')) {
if (-1 == src.search('auth') && auth_relay) {
src += '&'+auth_relay;
} else {
} else if (-1 != src.search('auth')) {
src = src.replace(/auth=\w+/i, 'auth='+auth_hash);
}
if (-1 == src.search('scale=')) {
@@ -1511,7 +1511,7 @@ function MonitorStream(monitorData) {
}; // this.getStatusCmdResponse
this.statusCmdQuery = function() {
$j.getJSON(this.url + '?view=request&request=status&entity=monitor&element[]=Status&element[]=CaptureFPS&element[]=AnalysisFPS&element[]=Analysing&element[]=Recording&id='+this.id+'&'+auth_relay)
$j.getJSON(this.url + '?view=request&request=status&entity=monitor&element[]=Status&element[]=CaptureFPS&element[]=AnalysisFPS&element[]=Analysing&element[]=Recording&id='+this.id+(auth_relay?'&'+auth_relay:''))
.done(this.getStatusCmdResponse.bind(this))
.fail(logAjaxFail);

View File

@@ -121,6 +121,11 @@ input[name="newStorage[Url]"] {
margin-bottom: 20px;
margin-right: 5px;
border-bottom: 1px solid #e7e7e7;
padding-bottom: 0.75rem;
}
#options .form-group span.form-text {
margin-top: 0.5rem;
}
#options .col-md {
text-align: left;
@@ -131,32 +136,34 @@ form {
/* flex-direction: column;*/
width: 100%;
height: 100%;
margin-left: 9px;
/* padding-top: 2rem; */
}
@media screen and (max-width:767px) {
#options label,
label.col-form-label {
label.col-form-label {
text-align: left;
padding-bottom: 5px;
}
}
div.dnsmasq,
.dnsmasq .config {
text-align: left;
text-align: left;
}
.dnsmasq .container {
margin-left: 0;
margin-left: 0;
}
.dnsmasq .config .row > label {
width: 150px;
text-align: right;
width: 150px;
text-align: right;
}
.dnsmasq .config .row {
min-height:36px;
text-align: left;
display: flex;
align-content: space-around;
min-height:36px;
text-align: left;
display: flex;
align-content: space-around;
}
#leasesTable td,
#leasesTable th {
@@ -197,7 +204,7 @@ body.sticky .fixed-table-container {
}
body.sticky #controlTable thead {
position: sticky;
top: 0;
box-shadow: 0 0px 0, 0 -3px 0 #dfe4ea;
position: sticky;
top: 0;
box-shadow: 0 0px 0, 0 -3px 0 #dfe4ea;
}

View File

@@ -172,7 +172,7 @@ if ( (null !== $filter->Concurrent()) and $filter->Concurrent() )
<?php } ?>
<p class="Name">
<label for="filter[Name]"><?php echo translate('Name') ?></label>
<input type="text" id="filter[Name]" name="filter[Name]" value="<?php echo validHtmlStr($filter->Name()) ?>" data-on-input-this="updateButtons"/>
<input type="text" id="filter[Name]" name="filter[Name]" value="<?php echo validHtmlStr($filter->Name()) ?>" maxlength="64" data-on-input-this="updateButtons"/>
</p>
<?php
if (ZM_OPT_USE_AUTH) {

View File

@@ -368,7 +368,10 @@ function initPage() {
// Manage the SAVE Button
document.getElementById("saveBtn").addEventListener("click", function onSaveClick(evt) {
saveMonitorData();
const form = document.getElementById('contentForm');
if (validateForm(form)) {
saveMonitorData();
}
});
// Manage the SAVE AND CLOSE Button - use AJAX instead of native form

View File

@@ -63,6 +63,8 @@ function validateForm(form) {
errors[errors.length] = "<?php echo translate('BadPalette') ?>";
if ( !form.elements['newMonitor[Device]'].value )
errors[errors.length] = "<?php echo translate('BadDevice') ?>";
else if ( !form.elements['newMonitor[Device]'].value.match(/^\/dev\/[\w\/.\-]+$/) )
errors[errors.length] = "<?php echo translate('BadDevice') ?>";
if ( !form.elements['newMonitor[Channel]'] || !form.elements['newMonitor[Channel]'].value || !form.elements['newMonitor[Channel]'].value.match( /^\d+$/ ) )
errors[errors.length] = "<?php echo translate('BadChannel') ?>";
if ( !form.elements['newMonitor[Format]'] || !form.elements['newMonitor[Format]'].value || !form.elements['newMonitor[Format]'].value.match( /^\d+$/ ) )

View File

@@ -668,7 +668,9 @@ switch ($name) {
<label><?php echo translate('DevicePath') ?></label>
<?php echo count($devices) > 1 ? htmlSelect('newMonitor[Devices]', $devices, $monitor->Device()) : ''; ?>
<input type="text" name="newMonitor[Device]" value="<?php echo validHtmlStr($monitor->Device()) ?>"
<?php echo (count($devices) > 1) ? 'style="display: none;"' : '' ?> autocomplete="off"
<?php echo (count($devices) > 1 and $monitor->Device() != '') ? 'style="display: none;"' : '' ?> autocomplete="off"
pattern="/dev/[\w\/.\-]+"
title="<?php echo translate('BadDevice') ?> (e.g. /dev/video0)"
/>
</li>
<?php

View File

@@ -338,7 +338,7 @@ foreach (array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)) as $
echo '<p class="warning">Note: This value has been overriden via configuration files in '.ZM_CONFIG. ' or ' . ZM_CONFIG_SUBDIR.'.<br/>The overriden value is: '.constant($name).'</p>'.PHP_EOL;
}
?>
<span class="form-text form-control-sm"><?php echo validHtmlStr($optionPromptText); echo makeHelpLink($name) ?></span>
<span class="form-text"><?php echo validHtmlStr($optionPromptText); echo makeHelpLink($name) ?></span>
</div><!-- End .col-md -->
</div><!-- End .form-group -->
<?php