Merge branch 'master' into patch-865636

This commit is contained in:
IgorA100 authored and GitHub committed 2026-07-22 20:54:49 +03:00
commit e7f5b85bb7
278 files changed
+2915 -12041

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
name: CI Homoglyphs
on:
push:
branches:
- '*'
pull_request:
branches: [ master ]
permissions:
contents: read
jobs:
homoglyphs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Check for Cyrillic/Greek homoglyphs in first-party source
run: python3 utils/check-homoglyphs.py
@@ -13,6 +13,10 @@ ExecStart=@BINDIR@/zmpkg.pl start
ExecReload=@BINDIR@/zmpkg.pl restart
ExecStop=@BINDIR@/zmpkg.pl stop
PIDFile=@ZM_RUNDIR@/zm.pid
# ZoneMinder shuts down many capture/analysis daemons on stop; allow ample time.
# Without this, Fedora's short DefaultTimeoutStopSec combined with its global
# TimeoutStopFailureMode=abort drop-in SIGABRTs zmpkg.pl mid-shutdown.
TimeoutSec=60
Environment=TZ=/etc/localtime
RuntimeDirectory=zoneminder
RuntimeDirectoryMode=0755
@@ -32,5 +32,12 @@ Recording Tab
- **Output Container**: Leaving at Auto allows ZoneMinder to use mp4. Other choices are mkv and webm.
- **Optional Encoding Parameters**: Mostly useful when encoding as each encoder takes different parameters. Consult the `FFmpeg documentation <https://ffmpeg.org/ffmpeg-formats.html>`__ for available parameters for each encoder.
- **Recording Audio**: Check the box in order to save audio (if available) when events are recorded.
- **Event Start Command**: When a recording event starts, you can run a system command. The parameters to the command will be the event id and the monitor id.
- **Event End Command**: When a recording event ends, you can run a system command. The parameters to the command will be the event id and the monitor id.
- **Event Start Command**: When a recording event starts, you can run a system command.
- **Event End Command**: When a recording event ends, you can run a system command. It runs after the event has been finalized, so the event's video file is complete and its database records are in place.
How these commands are executed depends on whether the command string contains a ``%`` character:
- **Without** ``%``, the whole string is treated as the path of a single executable (no shell is involved), and two arguments are appended to it: the event id and the monitor id. You cannot pass your own arguments this way — a value like ``/usr/local/bin/notify.sh start`` is looked up as one file named ``notify.sh start`` and fails.
- **With** ``%``, the string is run through ``/bin/sh -c`` after token substitution, and no arguments are appended automatically. Available tokens are ``%EID%`` (event id), ``%MID%`` (monitor id) and, for the start command only, ``%EC%`` (the shell-escaped event cause). So to combine your own arguments with the event id, write e.g. ``/usr/local/bin/notify.sh start %EID%``.
The command runs in the background as the same user as the capture process (normally the web user), with all file descriptors closed — anything it prints goes nowhere, so redirect output inside your script if you need to see it. A failure to execute the command is logged by the capture process (zmc).
+11 -9
View File
@@ -861,7 +861,7 @@ sub recover_timestamps {
Debug('Have ' . @contents . ' files in '.$path);
closedir(DIR);
my @mp4_files = grep(/^\d+\-video\.\w+\.mp4$/, @contents);
my @mp4_files = grep(/^\d+\-video\.(?:\w+\.)?mp4$/, @contents);
if ( @mp4_files ) {
$$Event{DefaultVideo} = $mp4_files[0];
}
@@ -880,7 +880,6 @@ sub recover_timestamps {
my $first_file = "$path/$capture_jpgs[0]";
( $first_file ) = $first_file =~ /^(.*)$/;
my $first_timestamp = (stat($first_file))[9];
$starttime = $first_timestamp if $first_timestamp < $starttime;
my $last_file = $path.'/'.$capture_jpgs[@capture_jpgs-1];
( $last_file ) = $last_file =~ /^(.*)$/;
@@ -922,24 +921,27 @@ sub recover_timestamps {
my $file = $path.'/'.$mp4_files[0];
( $file ) = $file =~ /^(.*)$/; # de-taint
my $first_timestamp = (stat($file))[9];
$starttime = $first_timestamp if $first_timestamp < $starttime;
my $seconds = mp4_duration($file);
if ( !defined $seconds ) {
Warning("Unable to determine duration of $file from ffprobe. Defaulting Length to 0.");
$seconds = 0;
}
Debug("From mp4 have duration $seconds seconds, start: $first_timestamp");
# The mp4 is written as the event records, so its mtime is when recording
# finished. The event therefore started $seconds before that.
my $last_timestamp = (stat($file))[9];
my $first_timestamp = $last_timestamp - $seconds;
Debug("From mp4 have duration $seconds seconds, start: $first_timestamp end: $last_timestamp");
$Event->Length(sprintf('%.2f', $seconds));
$Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) );
$Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) );
$Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) );
} else {
# Nothing to derive the times from, so fall back to the directory's mtime.
$Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $starttime) );
}
if ( @mp4_files ) {
$Event->DefaultVideo($mp4_files[0]);
}
$Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $starttime) );
}
# Return the duration of a video file in seconds (float), or undef if it
@@ -988,7 +990,7 @@ sub fix_DefaultVideo {
Debug('Have ' . @contents . ' files in '.$path);
closedir(DIR);
my @mp4_files = grep(/^\d+\-video\.\w+\.mp4$/, @contents);
my @mp4_files = grep(/^\d+\-video\.(?:\w+\.)?mp4$/, @contents);
if ( @mp4_files ) {
$$event{DefaultVideo} = $mp4_files[0];
}
+13 -1
View File
@@ -214,7 +214,19 @@ sub Sql {
} elsif ( $term->{attr} eq 'CurrentDate' ) {
$self->{Sql} .= 'to_days(NOW())';
} elsif ( $term->{attr} eq 'DateTime' ) {
$self->{Sql} .= 'E.StartDateTime';
# Mirror web/includes/FilterTerm.php: DateTime is an "event overlaps
# this instant/window" idiom, not a plain StartDateTime comparison. A
# lower bound (>=/>) is satisfied by an event still running at that
# time, so compare against EndDateTime (NULL end = ongoing = never
# ends). An upper bound (<=/</=) is satisfied by an event that had
# already started, so compare against StartDateTime. Keep this in sync
# with the PHP so the web UI and the zmfilter.pl daemon select the
# same events. refs #4976
if ( ($term->{op}//'') eq '>=' or ($term->{op}//'') eq '>' ) {
$self->{Sql} .= "COALESCE(E.EndDateTime, '9999-12-31 23:59:59')";
} else {
$self->{Sql} .= 'E.StartDateTime';
}
} elsif ( $term->{attr} eq 'Date' ) {
# column emitted as part of range expression below
} elsif ( $term->{attr} eq 'StartDate' ) {
@@ -0,0 +1,36 @@
use strict;
use warnings;
use Test::More;
use File::Temp qw(tempdir);
use Date::Parse;
plan skip_all => 'ffmpeg not available' if system('ffmpeg -version >/dev/null 2>&1');
eval { require ZoneMinder::Event; 1 } or plan skip_all => "cannot load ZoneMinder::Event: $@";
plan tests => 6;
# An event directory holding only an mp4 (no capture jpgs), as produced when
# ZM_SAVE_JPEGS is off. Duration is 3 seconds.
my $dir = tempdir(CLEANUP => 1);
my $mp4 = "$dir/1-video.h264.mp4";
is(system("ffmpeg -v error -y -f lavfi -i testsrc=duration=3:size=64x64:rate=10 '$mp4' >/dev/null 2>&1"),
0, 'built a 3 second test mp4');
# mtime of the mp4 is when recording finished.
my $end = 1700000000;
utime($end, $end, $mp4) or die "utime: $!";
# Make the directory itself much older, so a fallback to its mtime is visible.
utime($end - 3600, $end - 3600, $dir) or die "utime: $!";
my $Event = new ZoneMinder::Event();
$Event->recover_timestamps($dir);
cmp_ok(abs($Event->Length() - 3), '<', 0.5, 'Length comes from the mp4 duration');
is($Event->DefaultVideo(), '1-video.h264.mp4', 'DefaultVideo is the mp4 we found');
my $start = Date::Parse::str2time($Event->StartDateTime());
my $stop = Date::Parse::str2time($Event->EndDateTime());
is($stop, $end, 'EndDateTime is the mp4 mtime, when recording finished');
cmp_ok(abs($start - ($end - 3)), '<=', 1,
'StartDateTime is duration before the end, not the dir mtime');
cmp_ok(abs(($stop - $start) - $Event->Length()), '<=', 1,
'Length agrees with EndDateTime - StartDateTime');
+4 -4
View File
@@ -22,6 +22,7 @@
#include "zm_signal.h"
#include <algorithm>
#include <cctype>
#include <cinttypes>
#include <cstdlib>
#include <unistd.h>
@@ -267,7 +268,7 @@ int zmDbDo(const std::string &query) {
return 1;
}
int zmDbDoInsert(const std::string &query) {
uint64_t zmDbDoInsert(const std::string &query) {
std::lock_guard<std::mutex> lck(db_mutex);
if (!zmDbConnected and !zmDbConnect())
return 0;
@@ -282,9 +283,8 @@ int zmDbDoInsert(const std::string &query) {
return 0;
}
}
// Might not be an int... FIXME
int id = mysql_insert_id(&dbconn);
Debug(2, "Success running sql insert %s. Resulting id is %d", query.c_str(), id);
uint64_t id = mysql_insert_id(&dbconn);
Debug(2, "Success running sql insert %s. Resulting id is %" PRIu64, query.c_str(), id);
return id;
}
+2 -1
View File
@@ -21,6 +21,7 @@
#define ZM_DB_H
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <mysql/mysql.h>
#include <mysql/mysqld_error.h>
@@ -70,7 +71,7 @@ extern bool zmDbConnected;
bool zmDbConnect();
void zmDbClose();
int zmDbDo(const std::string &query);
int zmDbDoInsert(const std::string &query);
uint64_t zmDbDoInsert(const std::string &query);
int zmDbDoUpdate(const std::string &query);
MYSQL_RES * zmDbFetch(const std::string &query);
+6 -6
View File
@@ -153,24 +153,24 @@ Event::Event(
/* None of these are crucial, and are simple when done as individual transactions */
sql = stringtf("INSERT INTO `Events_Hour` (EventId,MonitorId,StartDateTime,DiskSpace)"
" VALUES (%" PRId64 ",%u,'%s',NULL)",
" VALUES (%" PRIu64 ",%u,'%s',NULL)",
id, monitor->Id(), now_str.c_str());
dbQueue.push(std::move(sql));
sql = stringtf("INSERT INTO `Events_Day` (EventId,MonitorId,StartDateTime,DiskSpace)"
" VALUES (%" PRId64 ",%u,'%s',NULL)",
" VALUES (%" PRIu64 ",%u,'%s',NULL)",
id, monitor->Id(), now_str.c_str());
dbQueue.push(std::move(sql));
sql = stringtf("INSERT INTO `Events_Week` (EventId,MonitorId,StartDateTime,DiskSpace)"
" VALUES (%" PRId64 ",%u,'%s',NULL)",
" VALUES (%" PRIu64 ",%u,'%s',NULL)",
id, monitor->Id(), now_str.c_str());
dbQueue.push(std::move(sql));
sql = stringtf("INSERT INTO `Events_Month` (EventId,MonitorId,StartDateTime,DiskSpace)"
" VALUES (%" PRId64 ",%u,'%s',NULL)",
" VALUES (%" PRIu64 ",%u,'%s',NULL)",
id, monitor->Id(), now_str.c_str());
dbQueue.push(std::move(sql));
/*
sql = stringtf("INSERT INTO `Events_Year` (EventId,MonitorId,StartDateTime,DiskSpace)"
" VALUES (%" PRId64 ",%u,'%s',NULL)",
" VALUES (%" PRIu64 ",%u,'%s',NULL)",
id, monitor->Id(), now_str.c_str());
dbQueue.push(std::move(sql));
*/
@@ -469,7 +469,7 @@ void Event::AddPacket_(const std::shared_ptr<ZMPacket>packet) {
tag->save();
Debug(1, "Created new Tag %s", cls.c_str());
tags.emplace(std::make_pair(cls, *tag));
int tag_id = tag->Id();
uint64_t tag_id = tag->Id();
delete tag; // Delete after copying into map
tag = nullptr;
+2 -2
View File
@@ -78,7 +78,7 @@ Event_Tag::Event_Tag(uint64_t p_tag_id, uint64_t p_event_id, SystemTimePoint p_a
Event_Tag::~Event_Tag() {
}
int Event_Tag::save() {
uint64_t Event_Tag::save() {
std::string sql = stringtf("INSERT INTO `Events_Tags` (`TagId`, `EventId`, `AssignedDate`, `AssignedBy`)"
" VALUES (%" PRIu64 ", %" PRIu64 ", from_unixtime(%" PRId64 "), %d)",
tag_id,
@@ -86,7 +86,7 @@ int Event_Tag::save() {
static_cast<int64>(std::chrono::system_clock::to_time_t(assigned_on)),
assigned_by
);
int rc;
uint64_t rc;
//do {
rc = zmDbDoInsert(sql);
//} while (!rc and !zm_terminate);
+1 -1
View File
@@ -56,7 +56,7 @@ class Event_Tag {
unsigned int AssignedBy() { return assigned_by; };
unsigned int AssignedBy(unsigned int p_assigned_by) { return assigned_by = p_assigned_by; };
int save();
uint64_t save();
};
#endif // ZM_EVENT_TAG_H
+27
View File
@@ -149,6 +149,33 @@ std::list<const CodecData*> get_decoder_data(int wanted_codec, const std::string
return results;
}
AVCodecContext *open_fallback_decoder(const AVCodecParameters *codecpar, const AVCodec **codec_out) {
const AVCodec *codec = avcodec_find_decoder(codecpar->codec_id);
if (!codec) {
Debug(1, "No fallback decoder available for codec %s",
avcodec_get_name(codecpar->codec_id));
return nullptr;
}
Debug(1, "Trying fallback decoder %s for codec %s",
codec->name, avcodec_get_name(codecpar->codec_id));
AVCodecContext *ctx = avcodec_alloc_context3(codec);
if (!ctx) {
Error("Failed to allocate context for fallback decoder %s", codec->name);
return nullptr;
}
avcodec_parameters_to_context(ctx, codecpar);
zm_dump_codec(ctx);
int ret = avcodec_open2(ctx, codec, nullptr);
if (ret < 0) {
Error("Could not open fallback decoder %s (error '%s')",
codec->name, av_make_error_string(ret).c_str());
avcodec_free_context(&ctx);
return nullptr;
}
if (codec_out) *codec_out = codec;
return ctx;
}
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
+5
View File
@@ -332,6 +332,11 @@ struct CodecData {
};
std::list<const CodecData*> get_encoder_data(const std::string & wanted_codec, const std::string &wanted_coder) ;
std::list<const CodecData*> get_decoder_data(int wanted_codec, const std::string &wanted_coder) ;
// When none of the preferred decoders in dec_codecs are usable, fall back to
// whatever decoder this ffmpeg build actually provides for codecpar->codec_id.
// Returns an opened AVCodecContext (caller frees with avcodec_free_context) or
// nullptr. When non-null, *codec_out receives the chosen AVCodec.
AVCodecContext *open_fallback_decoder(const AVCodecParameters *codecpar, const AVCodec **codec_out = nullptr);
int setup_hwaccel(AVCodecContext *codec_ctx, const CodecData *codec_data,AVBufferRef * &hw_device_ctx, const std::string &device, int width, int height);
int libjpeg_to_ffmpeg_qv(int libjpeg_quality);
enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts);
+73 -55
View File
@@ -27,6 +27,7 @@
#include "url.hpp"
#include <thread>
#include <vector>
extern "C" {
#include <libavutil/time.h>
@@ -671,73 +672,85 @@ int FfmpegCamera::OpenFfmpeg() {
#if HAVE_LIBAVUTIL_HWCONTEXT_H
// 3.2 doesn't seem to have all the bits in place, so let's require 3.4 and up
#if LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0)
// Print out available types
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
Debug(1, "%s", av_hwdevice_get_type_name(type));
const char *hw_name = hwaccel_name.c_str();
type = av_hwdevice_find_type_by_name(hw_name);
if (type == AV_HWDEVICE_TYPE_NONE) {
Debug(1, "Device type %s is not supported.", hw_name);
} else {
Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type));
// Build the list of hw device types to try. A DecoderHWAccelName of
// "auto" probes every hwaccel libav offers and uses the first that both
// the decoder supports and whose device can be created; any other value
// is a comma-separated priority list of device-type names, tried in
// order (e.g. "cuda,vaapi"; a single name like "vaapi" is just the
// one-element case and behaves as before). If nothing usable is found
// we transparently fall back to software.
std::vector<enum AVHWDeviceType> candidate_types;
bool auto_detect = (hwaccel_name == "auto");
enum AVHWDeviceType it = AV_HWDEVICE_TYPE_NONE;
while ((it = av_hwdevice_iterate_types(it)) != AV_HWDEVICE_TYPE_NONE) {
Debug(1, "Available hwdevice type %s", av_hwdevice_get_type_name(it));
if (auto_detect) candidate_types.push_back(it);
}
if (!auto_detect) {
for (const std::string &token : Split(hwaccel_name, ',')) {
std::string name = TrimSpaces(token);
if (name.empty()) continue;
enum AVHWDeviceType named = av_hwdevice_find_type_by_name(name.c_str());
if (named == AV_HWDEVICE_TYPE_NONE)
Warning("Unknown hwaccel device type '%s', skipping.", name.c_str());
else
candidate_types.push_back(named);
}
}
for (enum AVHWDeviceType type : candidate_types) {
Debug(1, "Trying hwdevice %s", av_hwdevice_get_type_name(type));
hw_pix_fmt = AV_PIX_FMT_NONE;
#if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0)
// Get hw_pix_fmt
for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i);
if (!config) {
Debug(1, "Decoder %s does not support config %d.",
mVideoCodec->name, i);
break;
}
if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
&& (config->device_type == type)
) {
hw_pix_fmt = config->pix_fmt;
Debug(1, "Decoder %s does support our type %s.",
mVideoCodec->name, av_hwdevice_get_type_name(type));
//break;
} else {
Debug(1, "Decoder %s hwConfig doesn't match our type: %s != %s, pix_fmt %s.",
mVideoCodec->name,
av_hwdevice_get_type_name(type),
av_hwdevice_get_type_name(config->device_type),
zm_get_pix_fmt_name(config->pix_fmt)
);
}
} // end foreach hwconfig
// Does this decoder advertise a hw config for this device type?
for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i);
if (!config) break;
if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
&& (config->device_type == type)) {
hw_pix_fmt = config->pix_fmt;
Debug(1, "Decoder %s supports type %s (pix_fmt %s).",
mVideoCodec->name, av_hwdevice_get_type_name(type),
zm_get_pix_fmt_name(hw_pix_fmt));
}
} // end foreach hwconfig
#else
hw_pix_fmt = find_fmt_by_hw_type(type);
hw_pix_fmt = find_fmt_by_hw_type(type);
#endif
if (hw_pix_fmt != AV_PIX_FMT_NONE) {
Debug(1, "Selected hw_pix_fmt %d %s",
hw_pix_fmt, zm_get_pix_fmt_name(hw_pix_fmt));
mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
//if (!lavc_param->check_hw_profile)
mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
if (hw_pix_fmt == AV_PIX_FMT_NONE) {
Debug(1, "Decoder %s has no hw_pix_fmt for %s, skipping.",
mVideoCodec->name, av_hwdevice_get_type_name(type));
continue;
}
ret = av_hwdevice_ctx_create(&hw_device_ctx, type,
(hwaccel_device != "" ? hwaccel_device.c_str() : nullptr), nullptr, 0);
if (ret < 0 and hwaccel_device != "") {
if (ret < 0 and hwaccel_device != "")
ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0);
}
if (ret < 0) {
Error("Failed to create hwaccel device. %s", av_make_error_string(ret).c_str());
Warning("Failed to create %s hwaccel device: %s",
av_hwdevice_get_type_name(type), av_make_error_string(ret).c_str());
hw_pix_fmt = AV_PIX_FMT_NONE;
use_hwaccel = false;
} else {
Debug(1, "Created hwdevice for %s", hwaccel_device.c_str());
// Set opaque to point to our hw_pix_fmt so callback can access it
mVideoCodecContext->opaque = &hw_pix_fmt;
mVideoCodecContext->get_format = get_hw_format;
mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);
hw_device_ctx = nullptr;
continue; // try the next candidate
}
} else {
Debug(1, "Failed to find suitable hw_pix_fmt.");
// Success: wire up hardware decoding and stop searching.
Info("Using %s hardware decoding for %s",
av_hwdevice_get_type_name(type), mVideoCodec->name);
mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
//if (!lavc_param->check_hw_profile)
mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
// Set opaque to point to our hw_pix_fmt so callback can access it
mVideoCodecContext->opaque = &hw_pix_fmt;
mVideoCodecContext->get_format = get_hw_format;
mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);
break;
} // end foreach candidate type
if (hw_pix_fmt == AV_PIX_FMT_NONE) {
Debug(1, "No usable hardware decoder found; falling back to software decoding.");
use_hwaccel = false;
}
#else
Debug(1, "AVCodec not new enough for hwaccel");
@@ -766,6 +779,11 @@ int FfmpegCamera::OpenFfmpeg() {
break;
} // end foreach codec
if (!mVideoCodecContext) {
Debug(1, "Failed with known codecs, trying harder");
mVideoCodecContext = open_fallback_decoder(mVideoStream->codecpar, &mVideoCodec);
}
if (!mVideoCodecContext) {
Warning("Failed to open codec");
return -1;
+2 -14
View File
@@ -129,20 +129,8 @@ int FFmpeg_Input::Open(const char *filepath) {
if (!streams[i].context) {
Debug(1, "Failed with known codecs, trying harder");
if ((streams[i].codec = avcodec_find_decoder(input_format_context->streams[i]->codecpar->codec_id))) {
Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i);
streams[i].context = avcodec_alloc_context3(streams[i].codec);
avcodec_parameters_to_context(streams[i].context, input_format_context->streams[i]->codecpar);
zm_dump_codec(streams[i].context);
error = avcodec_open2(streams[i].context, streams[i].codec, nullptr);
if (error < 0) {
Error("Could not open input codec (error '%s')", av_make_error_string(error).c_str());
avcodec_free_context(&streams[i].context);
streams[i].context = nullptr;
}
}
streams[i].context = open_fallback_decoder(
input_format_context->streams[i]->codecpar, &streams[i].codec);
}
if (!streams[i].context) {
+9
View File
@@ -2392,6 +2392,15 @@ bool Image::Delta(const Image &image, Image* targetimage) const {
return false;
}
// DumpImgBuffer() nulls buffer while leaving width/height/colours intact, so
// the size check above is no guarantee the pixels are present. Without this a
// dumped reference image (e.g. dropped on resume) walks the delta helpers from
// a null base pointer and faults at address 0 on the first row (refs #4983).
if ( buffer == nullptr || image.buffer == nullptr ) {
Error("Attempt to get delta with a null buffer (this %p, other %p)", buffer, image.buffer);
return false;
}
uint8_t *pdiff = targetimage->WriteBuffer(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE);
if ( pdiff == nullptr ) {
Error("Failed requesting writeable buffer for storing the delta image");
+14 -2
View File
@@ -2004,7 +2004,9 @@ void Monitor::CheckAction() {
if ( Enabled() && !Active() ) {
Info("Received resume indication at count %d", shared_data->image_count);
shared_data->analysing = analysing;
ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread
// Analysis thread owns ref_image; ask it to drop the pre-suspend
// reference rather than freeing the buffer under it here (refs #4983).
ref_image_reset_ = true;
shared_data->alarm_x = shared_data->alarm_y = -1;
}
shared_data->action &= ~RESUME;
@@ -2017,7 +2019,8 @@ void Monitor::CheckAction() {
Info("Auto resuming at count %d", shared_data->image_count);
auto_resume_time = {};
shared_data->analysing = analysing;
ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread
// See RESUME above: defer the reference-image reset to the analysis thread.
ref_image_reset_ = true;
}
}
}
@@ -2138,6 +2141,15 @@ bool Monitor::Analyse() {
}
std::shared_ptr<ZMPacket> packet = packet_lock.packet_;
// The capture thread requested that we drop the pre-suspend reference image.
// Do it here, on the thread that owns ref_image, so the buffer is never freed
// out from under an in-flight Delta/Blend (refs #4983). The subsequent
// !ref_image.Buffer() checks re-seed it from the next frame.
if (ref_image_reset_.exchange(false)) {
Debug(1, "Resetting reference image on resume");
ref_image.DumpImgBuffer();
}
// Is it possible for packet->score to be ! -1 ? Not if everything is working correctly
if (packet->score != -1) {
Error("Packet score was %d at index %d, should always be -1!", packet->score, packet->image_index);
+7
View File
@@ -34,6 +34,7 @@
#include "zm_utils.h"
#include "zm_zone.h"
#include <atomic>
#include <list>
#include <memory>
#include <sys/time.h>
@@ -727,6 +728,12 @@ class Monitor : public std::enable_shared_from_this<Monitor> {
Image delta_image;
Image ref_image;
// ref_image is owned by the analysis thread (Analyse/DetectMotion). The
// capture thread (CheckAction) must not touch its buffer directly; on
// suspend-resume it sets this flag instead and the analysis thread drops the
// stale reference itself on its next pass. Prevents a use-after-free/null
// deref race in Image::Delta (refs #4983).
std::atomic<bool> ref_image_reset_{false};
// Analysis image ring: the annotated/analysis image is published into a ring
// of image_buffer_count slots living in the alarm_images SHM region. Readers
// pick up the newest via shared_data->last_analysis_index. Replaces the
+3 -3
View File
@@ -623,10 +623,10 @@ int main(int argc, char *argv[]) {
monitor->Id(), storage_id, event_id,
static_cast<int64>(std::chrono::duration_cast<Seconds>(fd.timestamp.time_since_epoch()).count()),
scheme_str.c_str(), monitor->Width(), monitor->Height());
int new_event_id = zmDbDoInsert(sql);
if (new_event_id > 0) {
uint64_t new_event_id = zmDbDoInsert(sql);
if (new_event_id) {
current_event->db_event_id = new_event_id;
Info("Created new event %d from re-analysis of event %" PRIu64, new_event_id, event_id);
Info("Created new event %" PRIu64 " from re-analysis of event %" PRIu64, new_event_id, event_id);
// Create directory and copy video files to new event
std::string new_event_path = BuildNewEventPath(
+3 -1
View File
@@ -29,8 +29,10 @@ set(TEST_SOURCES
zm_utils.cpp
zm_vector2.cpp
zm_zone.cpp
zm_zone_stride.cpp
zm_image_linesize.cpp
zm_ffmpeg_camera.cpp)
zm_ffmpeg_camera.cpp
zm_ffmpeg_fallback.cpp)
add_executable(tests main.cpp ${TEST_SOURCES})
@@ -0,0 +1,135 @@
<?php
// Security regression test: an Events=View user must not be able to execute
// arbitrary OS commands through a Filter's AutoExecute/AutoExecuteCmd.
//
// Exercises the REAL ZM\Filter::canEdit() decision table:
// * A view-only (Events=View) user must NOT be able to edit/execute a filter
// that has ANY auto side-effect enabled (the original bug used `and`, so it
// only blocked when ALL five were set at once -> RCE).
// * AutoExecute (arbitrary OS command via zmfilter.pl) requires System edit
// permission, not merely Events edit.
// * Ownership is enforced: a non-System user can only act on their own filter.
//
// Run as: php tests/php/test_filter_canedit_autoexecute.php
//
// The real Filter class pulls in database.php / FilterTerm.php / Monitor.php via
// require_once, but canEdit() itself only uses ZM_Object's magic accessors (the
// `defaults` array, no DB) plus the passed-in user. So we stub the require-only
// dependencies, keep the real Object.php + Filter.php, and drive canEdit()
// directly.
namespace ZM;
// ---- test harness (top-level namespace-free helpers) -----------------------
$failures = 0;
$passes = 0;
function check($name, $got, $want) {
global $failures, $passes;
if ($got === $want) {
$passes++;
echo "ok - $name\n";
} else {
$failures++;
echo "FAIL - $name (got ".var_export($got, true).", want ".var_export($want, true).")\n";
}
}
// ---- stub the require-only dependencies of Filter.php ----------------------
$stubdir = sys_get_temp_dir().'/zm_filter_canedit_stubs_'.getmypid();
@mkdir($stubdir, 0700, true);
foreach (array('database.php', 'FilterTerm.php', 'Monitor.php') as $stub) {
file_put_contents($stubdir.'/'.$stub, "<?php\n");
}
// Stub dir first so its empty database.php/FilterTerm.php/Monitor.php win, then
// the real web/includes for Object.php and Filter.php.
set_include_path($stubdir.PATH_SEPARATOR.__DIR__.'/../../web/includes'.PATH_SEPARATOR.get_include_path());
// Logging stubs (ZM namespace) in case any accessor path emits one.
function Warning($s) { /* noop */ }
function Error($s) { /* noop */ }
function Debug($s) { /* noop */ }
function Info($s) { /* noop */ }
require_once __DIR__.'/../../web/includes/Filter.php';
// ---- stub user -------------------------------------------------------------
class TestUser {
private $id;
private $perms; // area => level ('None'|'View'|'Edit')
public function __construct($id, $perms) { $this->id = $id; $this->perms = $perms; }
public function Id() { return $this->id; }
private function level($area) { return isset($this->perms[$area]) ? $this->perms[$area] : 'None'; }
public function canView($area) { $l = $this->level($area); return $l == 'View' || $l == 'Edit'; }
public function canEdit($area) { return $this->level($area) == 'Edit'; }
}
// Helper: build a Filter owned by $ownerId with the given auto-flags set.
function make_filter($ownerId, array $flags) {
$f = new Filter();
$f->UserId($ownerId);
foreach ($flags as $k => $v) {
$f->$k($v);
}
return $f;
}
$systemAdmin = new TestUser(1, array('System' => 'Edit'));
$eventsEditor = new TestUser(2, array('Events' => 'Edit'));
$eventsViewer = new TestUser(3, array('Events' => 'View'));
// ---- the reported vulnerability: view-only user + AutoExecute --------------
$f = make_filter(3, array('AutoExecute' => 1, 'AutoExecuteCmd' => '/bin/sh -c "id"'));
check('Events=View owner CANNOT edit AutoExecute filter', $f->canEdit($eventsViewer), false);
// Each side-effect alone must block a view-only user (the `and` -> `or` fix).
foreach (array('AutoExecute','AutoDelete','AutoUnarchive','AutoArchive',
'AutoMove','AutoCopy','AutoVideo','AutoUpload',
'AutoEmail','AutoMessage') as $flag) {
$f = make_filter(3, array($flag => 1));
check("Events=View owner blocked when only $flag set", $f->canEdit($eventsViewer), false);
}
// A pure query filter (no auto actions) is fine for a view-only owner.
$f = make_filter(3, array());
check('Events=View owner CAN edit plain query filter', $f->canEdit($eventsViewer), true);
// ---- AutoExecute requires System edit, not just Events edit ----------------
$f = make_filter(2, array('AutoExecute' => 1, 'AutoExecuteCmd' => '/bin/sh -c "id"'));
check('Events=Edit owner CANNOT edit AutoExecute filter', $f->canEdit($eventsEditor), false);
// Events editor may still run event-changing filters.
$f = make_filter(2, array('AutoDelete' => 1));
check('Events=Edit owner CAN edit AutoDelete filter', $f->canEdit($eventsEditor), true);
$f = make_filter(2, array('AutoMove' => 1, 'AutoMoveTo' => 5));
check('Events=Edit owner CAN edit AutoMove filter', $f->canEdit($eventsEditor), true);
// System editor can do anything, including AutoExecute.
$f = make_filter(999, array('AutoExecute' => 1, 'AutoExecuteCmd' => '/bin/sh -c "id"'));
check('System editor CAN edit AutoExecute filter (any owner)', $f->canEdit($systemAdmin), true);
// ---- ownership enforcement -------------------------------------------------
$f = make_filter(1, array()); // owned by someone else
check('Events=Edit user CANNOT edit filter owned by another user', $f->canEdit($eventsEditor), false);
check('Events=View user CANNOT edit filter owned by another user', $f->canEdit($eventsViewer), false);
// A user with no Events permission at all cannot edit even their own filter.
$noPerm = new TestUser(4, array());
$f = make_filter(4, array());
check('User with no Events perm CANNOT edit own plain filter', $f->canEdit($noPerm), false);
// ---- cleanup ---------------------------------------------------------------
@unlink($stubdir.'/database.php');
@unlink($stubdir.'/FilterTerm.php');
@unlink($stubdir.'/Monitor.php');
@rmdir($stubdir);
echo "\n$passes passed, $failures failed.\n";
exit($failures ? 1 : 0);
+83
View File
@@ -0,0 +1,83 @@
/*
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
#include "zm_catch2.h"
#include "zm_ffmpeg.h"
// open_fallback_decoder() is the "try whatever ffmpeg actually has" path used
// when none of the preferred decoders in dec_codecs are available. The
// hard-coded table is only a preference list; a codec present in the ffmpeg
// build but absent from the table must still open.
static AVCodecParameters *make_video_par(enum AVCodecID id) {
AVCodecParameters *par = avcodec_parameters_alloc();
par->codec_type = AVMEDIA_TYPE_VIDEO;
par->codec_id = id;
par->width = 1280;
par->height = 720;
par->format = AV_PIX_FMT_YUV420P;
return par;
}
TEST_CASE("open_fallback_decoder: opens a decoder not listed in dec_codecs") {
// MPEG2VIDEO has a native ffmpeg decoder that is always compiled in, and it is
// deliberately not in the preferred dec_codecs table, so it only resolves via
// the fallback. If this build somehow lacks it, skip rather than false-fail.
if (!avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO)) {
WARN("mpeg2video decoder not present in this ffmpeg build; skipping");
return;
}
AVCodecParameters *par = make_video_par(AV_CODEC_ID_MPEG2VIDEO);
const AVCodec *codec = nullptr;
AVCodecContext *ctx = open_fallback_decoder(par, &codec);
REQUIRE(ctx != nullptr);
REQUIRE(codec != nullptr);
REQUIRE(codec->id == AV_CODEC_ID_MPEG2VIDEO);
avcodec_free_context(&ctx);
avcodec_parameters_free(&par);
}
TEST_CASE("open_fallback_decoder: codec_out is optional") {
if (!avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO)) {
WARN("mpeg2video decoder not present in this ffmpeg build; skipping");
return;
}
AVCodecParameters *par = make_video_par(AV_CODEC_ID_MPEG2VIDEO);
AVCodecContext *ctx = open_fallback_decoder(par); // default nullptr codec_out
REQUIRE(ctx != nullptr);
avcodec_free_context(&ctx);
avcodec_parameters_free(&par);
}
TEST_CASE("open_fallback_decoder: returns nullptr when no decoder exists") {
AVCodecParameters *par = make_video_par(AV_CODEC_ID_NONE);
const AVCodec *codec = reinterpret_cast<const AVCodec *>(0x1);
AVCodecContext *ctx = open_fallback_decoder(par, &codec);
REQUIRE(ctx == nullptr);
// codec_out must be left untouched on failure.
REQUIRE(codec == reinterpret_cast<const AVCodec *>(0x1));
avcodec_parameters_free(&par);
}
+243
View File
@@ -0,0 +1,243 @@
/*
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "zm_catch2.h"
#include "zm_config.h"
#include "zm_image.h"
#include "zm_monitor.h"
#include "zm_rgb.h"
#include "zm_zone.h"
#include <memory>
// Motion analysis on a portrait monitor (refs #4983). ZM allocates images with
// linesize = FFALIGN(width, 32). Every width in the CaptureResolution dropdown
// (1920, 1280, 2560, 1440, 704, 640, ...) is already a multiple of 32, so
// linesize == width and any code that uses width as a row stride works by
// accident. A portrait monitor is 1080 wide; FFALIGN(1080,32) == 1088, so it is
// the first common config where linesize != width and those paths are exercised
// for real. Each case below runs the same assertion at a padded width and at an
// aligned width, so a failure tells us whether the stride is the culprit or
// whether the geometry is wrong independent of it.
namespace {
// No zm.conf is loaded under the test harness, so config strings are null.
// Image::Initialise() dereferences config.font_file_location and the Monitor
// constructor strcmp()s config.event_close_mode; both segfault on null.
void EnsureConfig() {
if (!config.font_file_location) config.font_file_location = "";
if (!config.event_close_mode) config.event_close_mode = "idle";
}
// Monitor's width/height are protected and normally come from the database.
// Expose them so a zone can be set up against arbitrary dimensions without a DB.
class TestMonitor : public Monitor {
public:
TestMonitor(unsigned int w, unsigned int h) : Monitor() {
width = w;
height = h;
colours = ZM_COLOUR_GRAY8;
// >1 is what gates the blob/filter masking path in Zone::CheckAlarms.
savejpegs = 2;
}
};
std::shared_ptr<Monitor> MakeMonitor(unsigned int w, unsigned int h) {
return std::static_pointer_cast<Monitor>(std::make_shared<TestMonitor>(w, h));
}
// Rectangle covering the whole frame, in pixel coordinates.
Polygon FullFramePolygon(unsigned int w, unsigned int h) {
std::vector<Vector2> vertices = {
Vector2(0, 0),
Vector2(w - 1, 0),
Vector2(w - 1, h - 1),
Vector2(0, h - 1),
};
return Polygon(vertices);
}
// A GRAY8 delta image where every pixel reads as maximum difference, written
// through Buffer(x, y) so the fill itself is stride-correct regardless of
// padding.
std::unique_ptr<Image> MakeSaturatedDelta(unsigned int w, unsigned int h) {
auto img = std::unique_ptr<Image>(new Image(w, h, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE));
for (unsigned int y = 0; y < h; y++)
for (unsigned int x = 0; x < w; x++)
*img->Buffer(x, y) = 255;
return img;
}
} // namespace
TEST_CASE("Image: a 1080-wide GRAY8 image is stride-padded, a 1920-wide one is not", "[Zone]") {
EnsureConfig();
// This is the premise the rest of the cases rest on. If FFALIGN ever stops
// padding 1080, these tests stop testing what they claim to.
Image portrait(1080, 1920, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE);
REQUIRE(portrait.Width() == 1080);
REQUIRE(portrait.LineSize() == 1088);
Image landscape(1920, 1080, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE);
REQUIRE(landscape.Width() == 1920);
REQUIRE(landscape.LineSize() == 1920);
}
// refs #4983. DumpImgBuffer() nulls buffer but leaves
// width/height/colours/subpixelorder/linesize intact, so Delta's "different
// sized images" guard still passes and, without a buffer check, it walks the
// delta helpers from a null base: buffer + 0*linesize is address 0 on the first
// row. That reproduced the reported "Fault address: (nil)" inside the gray8
// delta helper. Delta must reject a null buffer on either operand instead of
// dereferencing it. (The originating race — the capture thread dumping
// ref_image under the analysis thread — is fixed separately in Monitor; this is
// the defensive backstop, and the only half unit-testable without a live race.)
TEST_CASE("Image::Delta rejects an operand whose buffer has been dumped", "[Image]") {
EnsureConfig();
auto make = []() { return Image(1080, 1920, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); };
SECTION("source (this) buffer dumped") {
Image ref = make();
Image comp = make();
Image delta;
ref.DumpImgBuffer();
// Dimensions survive the dump, which is exactly why the size check is no
// protection here.
REQUIRE(ref.Buffer() == nullptr);
REQUIRE(ref.Width() == comp.Width());
REQUIRE(ref.Height() == comp.Height());
REQUIRE(ref.Colours() == comp.Colours());
REQUIRE(ref.Delta(comp, &delta) == false);
}
SECTION("comparison (other) buffer dumped") {
Image ref = make();
Image comp = make();
Image delta;
comp.DumpImgBuffer();
REQUIRE(ref.Delta(comp, &delta) == false);
}
}
TEST_CASE("Zone::Setup derives per-row ranges correctly at a padded width", "[Zone]") {
EnsureConfig();
auto check = [](unsigned int w, unsigned int h) {
auto monitor = MakeMonitor(w, h);
Zone zone(monitor, 1, "full", Zone::ACTIVE, FullFramePolygon(w, h), kRGBRed,
Zone::ALARMED_PIXELS,
/*min_pixel_threshold*/ 10, /*max_pixel_threshold*/ 0,
/*min_alarm_pixels*/ 1, /*max_alarm_pixels*/ static_cast<int>(w * h));
const Image *pg = zone.getPgImage();
REQUIRE(pg != nullptr);
REQUIRE(pg->Width() == w);
REQUIRE(pg->Height() == h);
// Setup() scans pg_image row by row to find each row's polygon extent. A
// full-frame polygon must mark every row from column 0 to the last column.
// A stride bug shows as a wrong extent on the rows past the padding
// boundary, so sample the first row, the last row, and a spread in between
// rather than asserting on all ~2000 (which bloats the suite and hides the
// failing row). Zone::Range is protected; getRanges() hands it out publicly,
// so let the type be deduced rather than naming it.
const auto *ranges = zone.getRanges();
REQUIRE(ranges != nullptr);
for (unsigned int y : {0u, 1u, h / 2, h - 2, h - 1}) {
INFO("row y=" << y << " at " << w << "x" << h);
REQUIRE(ranges[y].lo_x == 0);
REQUIRE(ranges[y].hi_x == static_cast<int>(w - 1));
}
};
SECTION("1080x1920 portrait (linesize 1088 != width 1080)") {
check(1080, 1920);
}
SECTION("1920x1080 landscape (linesize == width)") {
check(1920, 1080);
}
}
// ALARMED_PIXELS returns before the filter and blob stages. Real monitors
// commonly run BLOBS with SaveJPEGs>1, which additionally walks the diff buffer
// for filtering, runs blob detection, and builds an alarm highlight image.
TEST_CASE("Zone::CheckAlarms survives the blob path on a full-frame delta", "[Zone]") {
EnsureConfig();
auto check = [](unsigned int w, unsigned int h) {
const int frame_pixels = static_cast<int>(w * h);
auto monitor = MakeMonitor(w, h);
// The filter/blob maxima default to values far below a full frame (50000),
// which would reject this delta before the code under test runs. Size every
// limit to the frame so the alarm survives to the blob stage.
Zone zone(monitor, 1, "full", Zone::ACTIVE, FullFramePolygon(w, h), kRGBRed,
Zone::BLOBS,
/*min_pixel_threshold*/ 10, /*max_pixel_threshold*/ 0,
/*min_alarm_pixels*/ 1, /*max_alarm_pixels*/ frame_pixels,
/*filter_box*/ Vector2(3, 3),
/*min_filter_pixels*/ 1, /*max_filter_pixels*/ frame_pixels,
/*min_blob_pixels*/ 1, /*max_blob_pixels*/ 0,
/*min_blobs*/ 1, /*max_blobs*/ 0);
std::unique_ptr<Image> delta = MakeSaturatedDelta(w, h);
REQUIRE(zone.CheckAlarms(delta.get()) == true);
REQUIRE(zone.GetStats().alarm_pixels_ == w * h);
};
SECTION("1080x1920 portrait (linesize 1088 != width 1080)") {
check(1080, 1920);
}
SECTION("1920x1080 landscape (linesize == width)") {
check(1920, 1080);
}
}
TEST_CASE("Zone::CheckAlarms counts every pixel of a saturated full-frame delta", "[Zone]") {
EnsureConfig();
auto check = [](unsigned int w, unsigned int h) {
auto monitor = MakeMonitor(w, h);
Zone zone(monitor, 1, "full", Zone::ACTIVE, FullFramePolygon(w, h), kRGBRed,
Zone::ALARMED_PIXELS,
/*min_pixel_threshold*/ 10, /*max_pixel_threshold*/ 0,
/*min_alarm_pixels*/ 1, /*max_alarm_pixels*/ static_cast<int>(w * h));
std::unique_ptr<Image> delta = MakeSaturatedDelta(w, h);
REQUIRE(zone.CheckAlarms(delta.get()) == true);
// Every pixel is above threshold and every pixel is inside the polygon, so
// the alarmed count must be the whole frame. A stride mismatch shows up
// here as a short count.
REQUIRE(zone.GetStats().alarm_pixels_ == w * h);
};
SECTION("1080x1920 portrait (linesize 1088 != width 1080)") {
check(1080, 1920);
}
SECTION("1920x1080 landscape (linesize == width)") {
check(1920, 1080);
}
}
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Fail if first-party source contains Cyrillic/Greek homoglyphs.
Contributors occasionally paste characters that look like ASCII letters but are
not, e.g. Cyrillic Es (U+0421) instead of Latin C. These break identifier and
translation-key lookups, and the multi-byte UTF-8 sequences are corrupted by
some reverse proxies (ZoneMinder discussions #4993, #4678). This scans the
git-tracked source tree for characters in the Cyrillic (U+0400-U+04FF) and
Greek (U+0370-U+03FF) blocks and exits non-zero if any are found.
Translations (web/lang/) and vendored/minified assets legitimately contain
these characters and are excluded. Run from the repo root:
python3 utils/check-homoglyphs.py
"""
import re
import subprocess
import sys
# Only these extensions are scanned; everything else (images, fonts, binaries)
# is ignored.
SCANNED_EXT = (
'.php', '.js', '.cpp', '.h', '.hpp', '.c', '.cc',
'.pl', '.pm', '.pl.in', '.pm.in', '.in',
'.css', '.sql', '.py', '.sh',
)
# Paths where non-ASCII letters are legitimate (translations) or not ours to
# fix (vendored, minified, generated). Matched as a case-insensitive substring
# of the repo-relative path.
EXCLUDE = re.compile(
r'(?:'
r'/lang/' # translation catalogues
r'|\.min\.' # minified vendored bundles
r'|/assets/' # bundled third-party skin assets
r'|/vendor/|/dist/|/lib/' # vendored libraries
r'|node_modules/'
r'|jquery|bootstrap|chosen|moment|video\.js|audiomotion'
r'|pro-sidebar|dygraph|packery|flatpickr'
r')',
re.IGNORECASE,
)
# Cyrillic (U+0400-U+04FF) and Greek (U+0370-U+03FF) letter blocks. These are
# what homoglyph attacks and accidental paste-ins draw from; ZoneMinder code is
# otherwise ASCII. Written with \u escapes so this script is itself ASCII and
# passes its own check.
HOMOGLYPH = re.compile("[\u0400-\u04ff\u0370-\u03ff]")
def tracked_files():
out = subprocess.check_output(['git', 'ls-files'], text=True)
return out.splitlines()
def main():
violations = []
for path in tracked_files():
if EXCLUDE.search(path):
continue
if not path.endswith(SCANNED_EXT):
continue
try:
with open(path, encoding='utf-8') as handle:
lines = handle.readlines()
except (OSError, UnicodeDecodeError):
continue
for lineno, line in enumerate(lines, 1):
for match in HOMOGLYPH.finditer(line):
char = match.group()
violations.append(
(path, lineno, match.start() + 1, ord(char), line.rstrip('\n'))
)
if not violations:
print('OK: no Cyrillic/Greek homoglyphs in first-party source.')
return 0
print('Found {} homoglyph character(s) in first-party source:\n'.format(len(violations)))
for path, lineno, col, cp, text in violations:
print(' {}:{}:{} U+{:04X}'.format(path, lineno, col, cp))
print(' {}'.format(text.strip()))
print('\nReplace each with its ASCII equivalent (e.g. Cyrillic Es U+0421 -> Latin C).')
print('If a character is genuinely required, add its path to EXCLUDE in {}.'.format(sys.argv[0]))
return 1
if __name__ == '__main__':
sys.exit(main())
+69 -40
View File
@@ -144,51 +144,80 @@ if (canEdit('Monitors')) {
} // end case url_probe
case 'import':
{
if ( !isset($_FILES['import_file']) ) {
ajaxError('No file was uploaded.');
return;
}
$file = $_FILES['import_file'];
if ( $file['error'] > 0 ) {
ajaxError($file['error']);
ajaxError('File upload failed with error code '.$file['error']);
return;
} else {
$filename = $file['name'];
$available_streams = array();
$row = 1;
if ( ($handle = fopen($file['tmp_name'], 'r')) !== FALSE ) {
while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) {
$name = $data[0];
$url = $data[1];
$group = $data[2];
ZM\Info("Have the following line data $name $url $group");
$url_bits = null;
if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) {
$url_bits = array('host'=>$url, 'scheme'=>'http');
} else {
$url_bits = parse_url($url);
}
if ( ! $url_bits ) {
ZM\Info("Bad url, skipping line $name $url $group");
continue;
}
$available_streams += probe($url_bits);
//$url_bits['url'] = unparse_url( $url_bits );
//$url_bits['Monitor'] = $defaultMonitor;
//$url_bits['Monitor']->Name( $name );
//$url_bits['Monitor']->merge( $_POST['newMonitor'] );
//$available_streams[] = $url_bits;
} // end while rows
fclose($handle);
ajaxResponse(array('Streams'=>$available_streams));
} else {
ajaxError('Uploaded file does not exist');
return;
}
}
if ( !is_uploaded_file($file['tmp_name']) ) {
ajaxError('Uploaded file does not exist');
return;
}
if ( ($handle = fopen($file['tmp_name'], 'r')) === FALSE ) {
ajaxError('Unable to open uploaded file');
return;
}
$available_streams = array();
$row = 0;
while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) {
$row ++;
if ( count($data) < 2 ) {
ZM\Info("Skipping line $row, expected at least Name,URL columns");
continue;
}
$name = trim($data[0]);
$url = trim($data[1]);
$group = isset($data[2]) ? trim($data[2]) : '';
// Skip an optional header row (Name,URL,Group)
if ( $row == 1 and !strcasecmp($name, 'Name') and !strcasecmp($url, 'URL') ) {
ZM\Debug('Skipping header row');
continue;
}
if ( $url === '' ) {
ZM\Info("Skipping line $row, no URL");
continue;
}
ZM\Info("Importing line $row: $name $url $group");
$url_bits = parse_url($url);
$host = ($url_bits and isset($url_bits['host'])) ? $url_bits['host'] : '';
# If a monitor already exists for this url, offer to edit it instead of adding a duplicate.
$monitor = null;
$existing = ZM\Monitor::find(array('Path'=>$url));
if ( count($existing) ) $monitor = $existing[0];
$available_streams[] = array(
'mac' => '',
'description' => $name.' - '.$url,
'url' => $url,
'IP' => $host,
'Group' => $group,
'camera' => array(
'Name' => $name,
'ip' => $host,
'Manufacturer' => '',
'Model' => '',
'monitor' => array(
'Type' => 'Ffmpeg',
'Path' => $url,
'Name' => $name,
),
),
'Monitor' => $monitor,
);
} // end while rows
fclose($handle);
ajaxResponse(array('Streams'=>$available_streams));
break;
} // end case import
default:
+14 -4
View File
@@ -29,11 +29,19 @@ if ($message) {
}
require_once('includes/Filter.php');
require_once getSkinFile('views/_monitor_filters.php'); // getFilteredMonitorIds()
$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
if (count( $user->unviewableMonitorIds())) {
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user->viewableMonitorIds()));
// $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>'5'));
}
# Constrain to the monitors selected by the shared monitor-attribute filters
# (Status/Capturing/Server/Storage/Name/Source) which have no Events column and so
# can't be expressed as event terms. Null = no such filter active. refs #4976
$attr_monitor_ids = getFilteredMonitorIds();
if ($attr_monitor_ids !== null) {
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$attr_monitor_ids));
}
// TODO: Why is $user->viewableMonitorIds() returning $user->unviewableMonitorIds()
// Error('$user->viewableMonitorIds(): '.print_r($user->viewableMonitorIds()));
if (!empty($_REQUEST['StartDateTime'])) {
@@ -224,20 +232,22 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
// For events that never wrote EndDateTime (zmc killed/crashed mid-event),
// fall back to StartDateTime + Length. Length is flushed to the DB every
// few seconds during recording, so it reflects the actual recorded
// duration even when zmc died without closing the event. Falling back to
// NOW() would otherwise extend the event across all the down-time.
// duration even when zmc died without closing the event. When Length is 0
// too (an empty crash-orphaned event), fall back to StartDateTime so the
// event has no span; NOW() would otherwise extend it across all the
// down-time and overlap every later event.
$col_str = '
E.*,
UNIX_TIMESTAMP(E.StartDateTime) AS StartTimeSecs,
CASE
WHEN E.EndDateTime IS NOT NULL THEN E.EndDateTime
WHEN E.Length > 0 THEN DATE_ADD(E.StartDateTime, INTERVAL FLOOR(E.Length) SECOND)
ELSE NOW()
ELSE E.StartDateTime
END AS EndDateTime,
CASE
WHEN E.EndDateTime IS NOT NULL THEN UNIX_TIMESTAMP(E.EndDateTime)
WHEN E.Length > 0 THEN UNIX_TIMESTAMP(E.StartDateTime) + E.Length
ELSE UNIX_TIMESTAMP(NOW())
ELSE UNIX_TIMESTAMP(E.StartDateTime)
END AS EndTimeSecs,
M.Name AS Monitor,
GROUP_CONCAT(T.Name SEPARATOR ", ") AS Tags';
+1 -1
View File
@@ -20,7 +20,7 @@ if ( $sid and ! $Server->Id() ) return;
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><?php echo translate('Server') .' - '. $Server->Name() ?></h5>
<h5 class="modal-title"><?php echo translate('Server') .' - '. validHtmlStr($Server->Name()) ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
+1 -1
View File
@@ -38,5 +38,5 @@ if ( canView('Monitors') || (isset($_REQUEST['mid']) && $_REQUEST['mid'] !== ''
} // end switch action
} // end if canView('Monitors')
ajaxError(translate('UnrecognisedAction').' "'.validHtmlStr($_REQUEST['action'] ?? '').'" '.translate('СonjOr').' '.translate('insufficientPermissionsUser').' "'.validHtmlStr($user->Username()).'"');
ajaxError(translate('UnrecognisedAction').' "'.validHtmlStr($_REQUEST['action'] ?? '').'" '.translate('ConjOr').' '.translate('insufficientPermissionsUser').' "'.validHtmlStr($user->Username()).'"');
?>
+2 -2
View File
@@ -105,7 +105,7 @@ class ConfigsController extends AppController {
*/
public function edit($id = null) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
$canEdit = (!$user) || ($user->System() == 'Edit');
if (!$canEdit) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
@@ -143,7 +143,7 @@ class ConfigsController extends AppController {
*/
public function delete($id = null) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
$canEdit = (!$user) || ($user->System() == 'Edit');
if (!$canEdit) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
@@ -348,6 +348,18 @@ class EventsController extends AppController {
throw new NotFoundException(__('Invalid event'));
}
# Events=Edit is coarse. Enforce the per-monitor ACL too, otherwise a user
# denied a monitor can still mutate that monitor's events by direct Id.
$this->Event->recursive = -1;
$event = $this->Event->find('first', array(
'conditions' => array('Event.' . $this->Event->primaryKey => $id)
));
$EventObj = new ZM\Event($event['Event']);
if ( !$EventObj->canEdit() ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
if ( $this->Event->save($this->request->data) ) {
$message = 'Saved';
} else {
@@ -379,6 +391,19 @@ class EventsController extends AppController {
throw new NotFoundException(__('Invalid event'));
}
$this->request->allowMethod('post', 'delete');
# Events=Edit is coarse. Enforce the per-monitor ACL too, otherwise a user
# denied a monitor can still delete that monitor's events by direct Id.
$this->Event->recursive = -1;
$event = $this->Event->find('first', array(
'conditions' => array('Event.' . $this->Event->primaryKey => $id)
));
$EventObj = new ZM\Event($event['Event']);
if ( !$EventObj->canEdit() ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
if ( $this->Event->delete() ) {
//$this->loadModel('Frame');
//$this->Event->Frame->delete();
@@ -26,6 +26,40 @@ class FramesController extends AppController {
}
}
# Frames are addressed by their own Id, so the parent Event's per-monitor ACL
# has to be resolved explicitly. Without this a user denied a monitor can
# reach that monitor's frames by guessing frame Ids.
private function eventForFrame($id) {
$this->Frame->recursive = -1;
$frame = $this->Frame->find('first', array(
'conditions' => array('Frame.' . $this->Frame->primaryKey => $id)
));
if (!$frame) {
throw new NotFoundException(__('Invalid frame'));
}
$this->loadModel('Event');
$this->Event->recursive = -1;
$event = $this->Event->find('first', array(
'conditions' => array('Event.Id' => $frame['Frame']['EventId'])
));
if (!$event) {
throw new NotFoundException(__('Invalid event'));
}
return new ZM\Event($event['Event']);
}
# Frame mutation is an Event mutation, so require Events=Edit as well as the
# per-monitor ACL. beforeFilter() only guarantees Events != None.
private function requireFrameEdit($id) {
global $user;
if ($user and ($user->Events() != 'Edit')) {
throw new UnauthorizedException(__('Insufficient Privileges'));
}
if (!$this->eventForFrame($id)->canEdit()) {
throw new UnauthorizedException(__('Insufficient Privileges'));
}
}
/**
* index method
* @return void
@@ -67,6 +101,9 @@ class FramesController extends AppController {
if (!$this->Frame->exists($id)) {
throw new NotFoundException(__('Invalid frame'));
}
if (!$this->eventForFrame($id)->canView()) {
throw new UnauthorizedException(__('Insufficient Privileges'));
}
$options = array('conditions' => array('Frame.' . $this->Frame->primaryKey => $id));
$frame = $this->Frame->find('first', $options);
$this->set(array(
@@ -102,6 +139,7 @@ class FramesController extends AppController {
if (!$this->Frame->exists($id)) {
throw new NotFoundException(__('Invalid frame'));
}
$this->requireFrameEdit($id);
if ($this->request->is(array('post', 'put'))) {
if ($this->Frame->save($this->request->data)) {
return $this->flash(__('The frame has been saved.'), array('action' => 'index'));
@@ -127,6 +165,7 @@ class FramesController extends AppController {
throw new NotFoundException(__('Invalid frame'));
}
$this->request->allowMethod('post', 'delete');
$this->requireFrameEdit($id);
if ($this->Frame->delete()) {
return $this->flash(__('The frame has been deleted.'), array('action' => 'index'));
} else {
@@ -31,6 +31,19 @@ class ZonesController extends AppController {
if ( !$this->Monitor->exists($id) ) {
throw new NotFoundException(__('Invalid monitor'));
}
# Monitors=View is coarse. Enforce the per-monitor ACL so zones of a monitor
# the user is denied are not listed by direct monitor Id.
$this->Monitor->recursive = -1;
$monitor = $this->Monitor->find('first', array(
'conditions' => array('Monitor.Id' => $id)
));
$MonitorObj = new ZM\Monitor($monitor['Monitor']);
if ( !$MonitorObj->canView() ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
$this->Zone->recursive = -1;
$zones = $this->Zone->find('all', array(
'conditions' => array('MonitorId' => $id)
+1 -1
View File
@@ -39,7 +39,7 @@ class Event extends AppModel {
// from painting an event bar across hours/days of no real recording.
public $virtualFields = array(
'StartTimeSecs' => 'UNIX_TIMESTAMP(StartDateTime)',
'EndTimeSecs' => '(CASE WHEN Event.EndDateTime IS NOT NULL THEN UNIX_TIMESTAMP(Event.EndDateTime) WHEN Event.Length > 0 THEN UNIX_TIMESTAMP(Event.StartDateTime) + Event.Length ELSE UNIX_TIMESTAMP(NOW()) END)',
'EndTimeSecs' => '(CASE WHEN Event.EndDateTime IS NOT NULL THEN UNIX_TIMESTAMP(Event.EndDateTime) WHEN Event.Length > 0 THEN UNIX_TIMESTAMP(Event.StartDateTime) + Event.Length ELSE UNIX_TIMESTAMP(Event.StartDateTime) END)',
'StartTime' => 'StartDateTime',
'EndTime' => '(CASE WHEN Event.EndDateTime IS NOT NULL THEN Event.EndDateTime WHEN Event.Length > 0 THEN DATE_ADD(Event.StartDateTime, INTERVAL FLOOR(Event.Length) SECOND) ELSE NOW() END)'
);
+60 -29
View File
@@ -1142,6 +1142,22 @@ class Filter extends ZM_Object {
return $html;
} # end function widget()
// Decode a multi-value term value or cookie into an array of ids/strings.
// Cookies are written JSON-encoded by skin.js setCookie(), but legacy cookies
// (and pasted values) may be plain comma-separated, so fall back to explode.
static function decode_multi($raw) {
if ($raw === null or $raw === '')
return array();
if (is_array($raw))
return $raw;
$decoded = json_decode($raw, true);
if (is_array($decoded))
return $decoded;
if ($decoded !== null) # scalar JSON value like a bare number
return array($decoded);
return explode(',', $raw);
}
//
// This displays filters from the events page.
//
@@ -1269,11 +1285,17 @@ class Filter extends ZM_Object {
$html .= $this->addButtonForFilterSelect("filter[Query][terms][$i][val]");
$html .= '</span>';
} else if ( $term['attr'] == 'Group') {
$selected = self::decode_multi($term['val']);
$attrs = ['class'=>'term-value chosen chosen-auto-width',
'multiple'=>'multiple',
'data-placeholder'=>translate('All Groups')];
if (isset($term['cookie'])) {
$attrs['data-cookie'] = $term['cookie'];
if (!$selected and isset($_COOKIE[$term['cookie']]))
$selected = self::decode_multi($_COOKIE[$term['cookie']]);
}
$html .= '<span class="term-value-wrapper">';
$html .= htmlSelect("filter[Query][terms][$i][val]", Group::get_dropdown_options(), $term['val'],
['class'=>'term-value chosen chosen-auto-width',
'multiple'=>'multiple',
'data-placeholder'=>translate('All Groups')]).PHP_EOL;
$html .= htmlSelect("filter[Query][terms][$i][val]", Group::get_dropdown_options(), $selected, $attrs).PHP_EOL;
$html .= $this->addButtonForFilterSelect("filter[Query][terms][$i][val]");
$html .= '</span>';
} else if ( $term['attr'] == 'StateId' ) {
@@ -1293,17 +1315,14 @@ class Filter extends ZM_Object {
$monitors[$m->Id()] = $m->Id().' '.validHtmlStr($m->Name());
}
}
$selected = explode(',', $term['val']);
$selected = self::decode_multi($term['val']);
// echo '<pre>Monitor selected: '; print_r($selected); echo '</pre>';
if (count($selected) == 1 and !$selected[0]) {
$selected = null;
}
$options = ['class'=>'term-value chosen chosen-auto-width', 'multiple'=>'multiple', 'data-placeholder'=>translate('All Monitors')];
if (isset($term['cookie'])) {
$options['data-cookie'] = $term['cookie'];
if (!$selected and isset($_COOKIE[$term['cookie']]) and $_COOKIE[$term['cookie']])
$selected = explode(',', $_COOKIE[$term['cookie']]);
if (!$selected and isset($_COOKIE[$term['cookie']]))
$selected = self::decode_multi($_COOKIE[$term['cookie']]);
}
$html .= '<span class="term-value-wrapper">';
$html .= htmlSelect("filter[Query][terms][$i][val]", $monitors, $selected, $options).PHP_EOL;
@@ -1344,15 +1363,12 @@ class Filter extends ZM_Object {
$html .= $this->addButtonForFilterSelect("filter[Query][terms][$i][val]");
} else if ( $term['attr'] == 'Notes' ) {
$attrs = ['id'=>'filterNotes', 'class'=>'term-value chosen chosen-auto-width', 'multiple'=>'multiple', 'data-placeholder'=>translate('Event Type')];
$selected = explode(',', $term['val']);
if (count($selected) == 1 and !$selected[0]) {
$selected = null;
}
$selected = self::decode_multi($term['val']);
if (isset($term['cookie'])) {
$attrs['data-cookie'] = $term['cookie'];
if (!$selected and isset($_COOKIE[$term['cookie']]) and $_COOKIE[$term['cookie']])
$selected = explode(',', $_COOKIE[$term['cookie']]);
if (!$selected and isset($_COOKIE[$term['cookie']]))
$selected = self::decode_multi($_COOKIE[$term['cookie']]);
}
$options = [
'Motion' => 'Motion',
@@ -1440,19 +1456,34 @@ class Filter extends ZM_Object {
if (!$u) $u=$user;
if ($u->canEdit('System')) return true;
if ($this->UserId() == $u->Id()) {
if ($u->canEdit('Events')) return true;
if ($u->canView('Events')) {
// If we can only view events, then we can't perform filters that involve changing the event.
if (!(
$this->AutoExecute() and
$this->AutoDelete() and
$this->AutoUnarchive() and
$this->AutoMove() and
$this->AutoCopy()
)) {
return true;
}
// Below here the user is not a System editor, so may only act on filters
// that they own.
if ($this->UserId() != $u->Id()) return false;
// AutoExecuteCmd is run verbatim as an OS command by zmfilter.pl, which is
// equivalent to shell access on the server. Restrict it to System editors,
// who were already granted access above.
if ($this->AutoExecute()) return false;
// Users who can edit Events may run filters that change events
// (move/copy/delete/archive/etc).
if ($u->canEdit('Events')) return true;
// View-only users may only run filters that neither change events nor
// trigger side effects. Deny if ANY auto action is enabled.
if ($u->canView('Events')) {
if (!(
$this->AutoDelete() or
$this->AutoUnarchive() or
$this->AutoArchive() or
$this->AutoMove() or
$this->AutoCopy() or
$this->AutoVideo() or
$this->AutoUpload() or
$this->AutoEmail() or
$this->AutoMessage()
)) {
return true;
}
}
return false;
+7
View File
@@ -304,6 +304,13 @@ class FilterTerm {
case 'CurrentWeekday':
return 'weekday(NOW()';
case 'DateTime':
// DateTime is an "event overlaps this instant/window" idiom, not a plain
// StartDateTime comparison. A lower bound (>=/>) is satisfied by an event
// still running at that time, so compare against EndDateTime (NULL end =
// ongoing = never ends). An upper bound (<=/</=) is satisfied by an event
// that had already started, so compare against StartDateTime. refs #4976
if ($this->op == '>=' or $this->op == '>')
return "COALESCE(E.EndDateTime, '9999-12-31 23:59:59')";
return 'E.StartDateTime';
case 'Date':
return 'to_days(E.StartDateTime)';
+1 -1
View File
@@ -79,7 +79,7 @@ class Zone extends ZM_Object {
$areaCoords = preg_replace('/\s+/', ',', pointsToCoords($points));
}
}
return '<polygon points="'.$areaCoords.'" class="'.$this->Type().'" data-mid="'.$this->MonitorId().'" data-zid="'.$this->Id().'"><title>'.$this->Name().'</title></polygon>';
return '<polygon points="'.$areaCoords.'" class="'.$this->Type().'" data-mid="'.$this->MonitorId().'" data-zid="'.$this->Id().'"><title>'.validHtmlStr($this->Name()).'</title></polygon>';
}
} # end class Zone
?>
+9
View File
@@ -45,6 +45,15 @@ if ($action == 'delete') {
}
$path_parts = pathinfo($path);
# $is_ok_path was computed above but never consulted, so a path outside every
# Storage area still reached unlink(). detaintPathAllowAbsolute() permits
# absolute paths, so this is what keeps the delete inside a Storage area.
if (!$is_ok_path) {
$error_message .= 'Path is not valid. Path must be below a designated Storage area.<br/>';
ZM\Warning("Refusing to delete files under '$path': not below a Storage area");
return;
}
foreach ($_REQUEST['files'] as $file) {
$full_path = $path.'/'.detaintPath($file);
if (is_file($full_path)) {
+12 -4
View File
@@ -1823,8 +1823,12 @@ function generateConnKey() {
}
function detaintPathAllowAbsolute($path) {
// Strip out :// because php:// is a way to inject code apparently
$path = str_replace('://', '', $path);
// Strip out :// because php:// is a way to inject code apparently.
// This must loop: a single pass lets the removal re-form the sequence it
// just removed, so '::////' collapses back into '://'.
do {
$path = str_replace('://', '', $path, $count);
} while($count);
// Remove any absolute paths, or relative ones that want to go up
do {
$path = str_replace('../', '', $path, $count);
@@ -1834,8 +1838,12 @@ function detaintPathAllowAbsolute($path) {
function detaintPath($path) {
// Strip out :// because php:// is a way to inject code apparently
$path = str_replace('://', '', $path);
// Strip out :// because php:// is a way to inject code apparently.
// This must loop: a single pass lets the removal re-form the sequence it
// just removed, so '::////' collapses back into '://'.
do {
$path = str_replace('://', '', $path, $count);
} while($count);
// Remove any absolute paths, or relative ones that want to go up
do {
$path = str_replace('../', '', $path, $count);
+69 -73
View File
@@ -88,6 +88,17 @@ function EventStream(config) {
*/
this.start = function(eventId, options) {
options = options || {};
// An explicit start supersedes any pending recovery.
if (this.recoveryTimer) {
clearTimeout(this.recoveryTimer);
this.recoveryTimer = null;
}
// Tear down any existing connection first. Re-using a live <img> would
// abort its MJPEG stream behind zms's back and orphan the zms process.
if (this.started || this.img) this.teardown(this.started);
this.currentEventId = eventId;
this.rate = (options.rate !== undefined) ? options.rate : 100;
this.paused = false;
@@ -172,6 +183,37 @@ function EventStream(config) {
this.started = true;
};
// -------------------------------------------------------------------------
// teardown(quit) — Drop the connection, timers and draw loop.
// Pass quit=false when zms is already gone, so we don't ask a dead process
// to exit. Detaching the img handlers before clearing src keeps our own
// teardown from firing onerror and looking like a stream failure.
// -------------------------------------------------------------------------
this.teardown = function(quit) {
if (quit && this.started) this.streamCommand(CMD_QUIT);
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.img) {
this.img.onload = null;
this.img.onerror = null;
this.img.src = '';
if (this.img.parentNode) {
this.img.parentNode.removeChild(this.img);
}
this.img = null;
}
this.started = false;
this.connKey = null;
this.streamCmdParms.connkey = null;
};
// -------------------------------------------------------------------------
// stop() — Stop the current stream
// -------------------------------------------------------------------------
@@ -184,29 +226,9 @@ function EventStream(config) {
if (!this.started) return;
this.streamCommand(CMD_QUIT);
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.img) {
this.img.onload = null;
this.img.onerror = null;
this.img.src = '';
if (this.img.parentNode) {
this.img.parentNode.removeChild(this.img);
}
this.img = null;
}
this.started = false;
this.teardown(true);
this.paused = false;
this.stopped = false;
this.connKey = null;
this.streamCmdParms.connkey = null;
this.consecutiveErrors = 0;
this.recoveryDelay = 1000;
};
@@ -216,8 +238,24 @@ function EventStream(config) {
// -------------------------------------------------------------------------
this.recover = function() {
// A recovery is already scheduled. Without this, every error arriving
// while we wait to retry (the status poll keeps firing, and each reply
// is another Error) would queue another restart and inflate the attempt
// count until we give up on a stream that was never retried once.
if (this.recoveryTimer) return;
this.consecutiveErrors++;
var self = this;
var eventId = this.currentEventId;
var opts = Object.assign({}, this.lastOptions || {});
opts.rate = this.rate;
// Drop the dead connection before deciding whether to retry, so that
// giving up leaves nothing running. zms is already gone, so no CMD_QUIT.
this.teardown(false);
this.stopped = false;
if (this.consecutiveErrors > this.maxRecoveryAttempts) {
console.error('EventStream: max recovery attempts reached for monitor ' +
this.monitorId + ', giving up');
@@ -228,34 +266,14 @@ function EventStream(config) {
console.warn('EventStream: recovery attempt ' + this.consecutiveErrors +
'/' + this.maxRecoveryAttempts + ' for monitor ' + this.monitorId);
var self = this;
var eventId = this.currentEventId;
var opts = Object.assign({}, this.lastOptions || {});
opts.rate = this.rate;
// Clean up old state without sending CMD_QUIT (zms is already dead)
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.img) {
this.img.onload = null;
this.img.onerror = null;
this.img.src = '';
if (this.img.parentNode) {
this.img.parentNode.removeChild(this.img);
}
this.img = null;
}
this.started = false;
this.connKey = null;
this.stopped = false;
this.streamCmdParms.connkey = null;
// Delay before restarting — exponential backoff
this.recoveryTimer = setTimeout(function() {
self.recoveryTimer = null;
// teardown() cleared started, so a consumer polling for a live stream
// may have restarted us while we waited. Restarting again here would
// abort that healthy stream and orphan its zms, which fails the img
// and lands us straight back in recover().
if (self.started) return;
self.start(eventId, opts);
}, this.recoveryDelay);
@@ -340,37 +358,15 @@ function EventStream(config) {
this.recoveryTimer = null;
}
if (this.started) {
// Tell current zms to exit
this.streamCommand(CMD_QUIT);
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.img) {
this.img.onload = null;
this.img.onerror = null;
this.img.src = '';
if (this.img.parentNode) {
this.img.parentNode.removeChild(this.img);
}
this.img = null;
}
this.started = false;
this.connKey = null;
this.streamCmdParms.connkey = null;
}
// Reset recovery state for fresh event
this.consecutiveErrors = 0;
this.recoveryDelay = 1000;
// Brief delay to let the old zms process clean up, then start fresh
var self = this;
setTimeout(function() {
self.start(eventId, options);
}, 200);
// start() tears the old stream down (sending CMD_QUIT) and brings the new
// event up in one step. Doing the teardown here instead and starting from
// a timer would leave started=false in between, and a consumer polling for
// a live stream would start its own before the timer fired.
this.start(eventId, options);
};
// -------------------------------------------------------------------------
+4 -5
View File
@@ -745,9 +745,8 @@ function MonitorStream(monitorData) {
stream.srcObject = null;
this.webrtc = null;
}
if (this.hls) {
hlsDestroy(this.hls);
}
if (this.hls) hlsDestroy(this);
if (-1 !== this.activePlayer.indexOf('mse')) {
this.stopMse();
}
@@ -1839,7 +1838,7 @@ function MonitorStream(monitorData) {
// PCM, G.711A, G.711Mu, G.726, G.723 - no audio track
this.updateStreamInfo('', `Error. AAC codec "${data.audio.codec}" is not supported.`); //HLS
this.streamErrorRegistration();
hlsDestroy(this.hls);
hlsDestroy(this);
this.restart(this.currentChannelStream);
}
}, this);
@@ -1851,7 +1850,7 @@ function MonitorStream(monitorData) {
if (!data || !data.fatal) return;
this.updateStreamInfo('', 'Error'); //HLS
this.streamErrorRegistration();
hlsDestroy(this.hls);
hlsDestroy(this);
this.restart(this.currentChannelStream);
}, this);
this.hls.loadSource(hlsUrl.href);
+1 -1
View File
@@ -1 +1 @@
bootstrap-table-1.27.1/
bootstrap-table-1.27.3/
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -0,0 +1,88 @@
import { createElementBlock as e, openBlock as t } from "vue";
//#region \0plugin-vue:export-helper
var n = (e, t) => {
let n = e.__vccOpts || e;
for (let [e, r] of t) n[e] = r;
return n;
}, r = window.jQuery, i = (e) => e === void 0 ? e : r.fn.bootstrapTable.utils.extend(!0, Array.isArray(e) ? [] : {}, e), a = {
name: "BootstrapTable",
props: {
columns: {
type: Array,
require: !0
},
data: {
type: [Array, Object],
default() {}
},
options: {
type: Object,
default() {
return {};
}
}
},
data() {
return { optionsChangedIdx: 0 };
},
mounted() {
this.$table = r(this.$el), this.$table.on("all.bs.table", (e, t, n) => {
let i = r.fn.bootstrapTable.events[t];
i = i.replace(/([A-Z])/g, "-$1").toLowerCase(), this.$emit("on-all", ...n), this.$emit(i, ...n);
}), this._initTable();
},
beforeUnmount() {
this.$table.bootstrapTable("destroy");
},
methods: {
_initTable() {
this._bindMethods();
let e = {
...i(this.options),
columns: i(this.columns),
data: i(this.data)
};
this._hasInit ? this.refreshOptions(e) : (this.$table.bootstrapTable(e), this._hasInit = !0);
},
refreshOptions(...e) {
return this.$table.bootstrapTable("refreshOptions", ...e);
},
load(...e) {
return this.$table.bootstrapTable("load", ...e);
},
_bindMethods() {
for (let e of r.fn.bootstrapTable.methods || []) this[e] || (this[e] = (...t) => this.$table.bootstrapTable(e, ...t));
}
},
watch: {
options: {
handler() {
this.optionsChangedIdx++;
},
deep: !0
},
columns: {
handler() {
this.optionsChangedIdx++;
},
deep: !0
},
optionsChangedIdx() {
this._initTable();
},
data: {
handler() {
this.load(i(this.data));
},
deep: !0
}
}
};
function o(n, r, i, a, o, s) {
return t(), e("table");
}
//#endregion
//#region src/vue/index.js
var s = /* @__PURE__ */ n(a, [["render", o]]);
//#endregion
export { s as default };
@@ -0,0 +1 @@
(function(e,t){typeof exports==`object`&&typeof module<`u`?module.exports=t(require(`vue`)):typeof define==`function`&&define.amd?define([`vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,e.BootstrapTable=t(e.Vue))})(this,function(e){var t=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},n=window.jQuery,r=e=>e===void 0?e:n.fn.bootstrapTable.utils.extend(!0,Array.isArray(e)?[]:{},e),i={name:`BootstrapTable`,props:{columns:{type:Array,require:!0},data:{type:[Array,Object],default(){}},options:{type:Object,default(){return{}}}},data(){return{optionsChangedIdx:0}},mounted(){this.$table=n(this.$el),this.$table.on(`all.bs.table`,(e,t,r)=>{let i=n.fn.bootstrapTable.events[t];i=i.replace(/([A-Z])/g,`-$1`).toLowerCase(),this.$emit(`on-all`,...r),this.$emit(i,...r)}),this._initTable()},beforeUnmount(){this.$table.bootstrapTable(`destroy`)},methods:{_initTable(){this._bindMethods();let e={...r(this.options),columns:r(this.columns),data:r(this.data)};this._hasInit?this.refreshOptions(e):(this.$table.bootstrapTable(e),this._hasInit=!0)},refreshOptions(...e){return this.$table.bootstrapTable(`refreshOptions`,...e)},load(...e){return this.$table.bootstrapTable(`load`,...e)},_bindMethods(){for(let e of n.fn.bootstrapTable.methods||[])this[e]||(this[e]=(...t)=>this.$table.bootstrapTable(e,...t))}},watch:{options:{handler(){this.optionsChangedIdx++},deep:!0},columns:{handler(){this.optionsChangedIdx++},deep:!0},optionsChangedIdx(){this._initTable()},data:{handler(){this.load(r(this.data))},deep:!0}}};function a(t,n,r,i,a,o){return(0,e.openBlock)(),(0,e.createElementBlock)(`table`)}return t(i,[[`render`,a]])});
@@ -1,7 +1,7 @@
@charset "UTF-8";
/**
* @author zhixin wen <wenzhixin2010@gmail.com>
* version: 1.27.1
* version: 1.27.3
* https://github.com/wenzhixin/bootstrap-table/
*/
/* stylelint-disable annotation-no-unknown, max-line-length */
@@ -206,7 +206,7 @@ html[data-bs-theme=dark] {
opacity: 1;
}
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap {
align-items: baseline;
align-items: center;
display: flex;
justify-content: center;
}
@@ -228,10 +228,10 @@ html[data-bs-theme=dark] {
background: var(--bt-table-loading-color);
border-radius: 50%;
display: block;
height: 5px;
margin: 0 4px;
height: 0.3em;
margin: 0 0.2em;
opacity: 0;
width: 5px;
width: 0.3em;
}
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot {
animation-delay: 0.3s;
@@ -337,3 +337,11 @@ div.fixed-table-scroll-outer {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.bootstrap-table .fixed-table-loading .animation-dot,
.bootstrap-table .fixed-table-loading .animation-wrap::after,
.bootstrap-table .fixed-table-loading .animation-wrap::before {
animation: none !important;
opacity: 1 !important;
}
}
@@ -6657,6 +6657,9 @@
return col.fieldIndex === i;
});
var column = underColumns[underColumns.length - 1];
if (!column) {
return 1; // continue
}
if (underColumns.length > 1) {
for (var j = 0; j < underColumns.length - 1; j++) {
underColumns[j].visible = column.visible;
@@ -6667,7 +6670,7 @@
}
};
for (var i = r.colspanIndex; i < r.colspanIndex + r.colspanGroup; i++) {
_loop(i);
if (_loop(i)) continue;
}
r.colspan = colspan;
r.visible = colspan > 0;
@@ -6889,13 +6892,86 @@
return false;
}
/**
* Checks if any row in the data has rowspan cells with a span greater than 1.
*
* @param {Array.<Object.<string, *>>} data - The data array to check.
* @returns {boolean} True if any row has real rowspan merges, false otherwise.
*/
function hasRowspanCells(data) {
var _iterator1 = _createForOfIteratorHelper(data),
_step1;
try {
for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
var row = _step1.value;
for (var _i4 = 0, _Object$keys2 = Object.keys(row); _i4 < _Object$keys2.length; _i4++) {
var key = _Object$keys2[_i4];
if (key.startsWith('_') && key.endsWith('_rowspan') && (+row[key] || 0) > 1) {
return true;
}
}
}
} catch (err) {
_iterator1.e(err);
} finally {
_iterator1.f();
}
return false;
}
/**
* Flattens rowspan cells in the data by copying the cell value from the
* rowspan parent row into all spanned child rows, so each row becomes
* independent and can be sorted without breaking the table layout.
*
* @param {Array.<Object.<string, *>>} data - The data array to flatten.
*/
function flattenRowspanCells(data) {
for (var i = 0; i < data.length; i++) {
var row = data[i];
for (var _i5 = 0, _Object$keys3 = Object.keys(row); _i5 < _Object$keys3.length; _i5++) {
var key = _Object$keys3[_i5];
if (!key.startsWith('_') || !key.endsWith('_rowspan')) {
continue;
}
var field = key.replace(/^_/, '').replace(/_rowspan$/, '');
var rowspan = +row[key] || 1;
if (rowspan <= 1) {
continue;
}
var value = getItemField(row, field, false);
var useFlatKey = row.hasOwnProperty(field);
for (var j = 1; j < rowspan && i + j < data.length; j++) {
var childRow = data[i + j];
if (field.includes('.') && !useFlatKey) {
var props = field.split('.');
var target = childRow;
for (var k = 0; k < props.length - 1; k++) {
if (_typeof(target[props[k]]) !== 'object' || target[props[k]] === null) {
target[props[k]] = {};
}
target = target[props[k]];
}
target[props[props.length - 1]] = value;
} else {
childRow[field] = value;
}
delete childRow["_".concat(field, "_rowspan")];
}
delete row[key];
}
}
}
var tableData = /*#__PURE__*/Object.freeze({
__proto__: null,
checkAutoMergeCells: checkAutoMergeCells,
findIndex: findIndex,
flattenRowspanCells: flattenRowspanCells,
getFieldTitle: getFieldTitle,
getItemField: getItemField,
getRealDataAttr: getRealDataAttr,
hasRowspanCells: hasRowspanCells,
setFieldIndex: setFieldIndex,
trToData: trToData,
updateFieldGroup: updateFieldGroup
@@ -8452,7 +8528,7 @@
var Utils = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, framework), object), string), dom), tableData), searchSort), helper), checkbox);
var VERSION = '1.27.1';
var VERSION = '1.27.3';
var bootstrapVersion = Utils.getBootstrapVersion();
var CONSTANTS = {
3: {
@@ -9910,6 +9986,12 @@
fontSize = Math.min(32, fontSize);
fontSize = "".concat(fontSize, "px");
}
var $loadingWrap = this.$tableLoading.find('.loading-wrap');
if ($loadingWrap.length) {
$loadingWrap.css('font-size', fontSize);
} else {
this.$tableLoading.css('font-size', fontSize);
}
this.$tableLoading.find('.loading-text').css('font-size', fontSize);
},
hideLoading: function hideLoading() {
@@ -9991,24 +10073,19 @@
return rows;
},
showColumn: function showColumn(field) {
var _this4 = this;
var fields = Array.isArray(field) ? field : [field];
fields.forEach(function (field) {
_this4._toggleColumn(_this4.fieldsColumnsIndex[field], true, true);
});
this._toggleColumns(Array.isArray(field) ? field : [field], true, true);
},
hideColumn: function hideColumn(field) {
var _this5 = this;
var fields = Array.isArray(field) ? field : [field];
fields.forEach(function (field) {
_this5._toggleColumn(_this5.fieldsColumnsIndex[field], false, true);
});
this._toggleColumns(Array.isArray(field) ? field : [field], false, true);
},
_toggleColumn: function _toggleColumn(index, checked, needUpdate) {
_toggleColumnVisibility: function _toggleColumnVisibility(index, checked) {
if (index === undefined || this.columns[index].visible === checked) {
return;
return false;
}
this.columns[index].visible = checked;
return true;
},
_updateAfterColumnToggle: function _updateAfterColumnToggle(changedIndices, checked, needUpdate) {
this.initHeader();
this.initSearch();
this.initPagination();
@@ -10016,13 +10093,48 @@
if (this.options.showColumns) {
var $items = this.$toolbar.find('.keep-open input:not(".toggle-all")').prop('disabled', false);
if (needUpdate) {
$items.filter(Utils.sprintf('[value="%s"]', index)).prop('checked', checked);
var _iterator2 = _createForOfIteratorHelper(changedIndices),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var index = _step2.value;
$items.filter(Utils.sprintf('[value="%s"]', index)).prop('checked', checked);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
if ($items.filter(':checked').length <= this.options.minimumCountColumns) {
$items.filter(':checked').prop('disabled', true);
}
}
},
_toggleColumns: function _toggleColumns(fields, checked, needUpdate) {
if (!fields.length) {
return;
}
var changedIndices = [];
var _iterator3 = _createForOfIteratorHelper(fields),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var field = _step3.value;
var index = this.fieldsColumnsIndex[field];
if (this._toggleColumnVisibility(index, checked)) {
changedIndices.push(index);
}
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
if (changedIndices.length) {
this._updateAfterColumnToggle(changedIndices, checked, needUpdate);
}
},
showAllColumns: function showAllColumns() {
this._toggleAllColumns(true);
},
@@ -10030,12 +10142,12 @@
this._toggleAllColumns(false);
},
_toggleAllColumns: function _toggleAllColumns(visible) {
var _this6 = this;
var _iterator2 = _createForOfIteratorHelper(this.columns.slice().reverse()),
_step2;
var _this4 = this;
var _iterator4 = _createForOfIteratorHelper(this.columns.slice().reverse()),
_step4;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var column = _step2.value;
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var column = _step4.value;
if (column.switchable) {
if (!visible && this.options.showColumns && this.getVisibleColumns().filter(function (it) {
return it.switchable;
@@ -10046,9 +10158,9 @@
}
}
} catch (err) {
_iterator2.e(err);
_iterator4.e(err);
} finally {
_iterator2.f();
_iterator4.f();
}
this.initHeader();
this.initSearch();
@@ -10060,7 +10172,7 @@
$items.prop('checked', visible);
} else {
$items.get().reverse().forEach(function (item) {
if ($items.filter(':checked').length > _this6.options.minimumCountColumns) {
if ($items.filter(':checked').length > _this4.options.minimumCountColumns) {
$(item).prop('checked', visible);
}
});
@@ -10091,9 +10203,9 @@
$td.attr('rowspan', rowspan).attr('colspan', colspan).show();
},
getVisibleColumns: function getVisibleColumns() {
var _this7 = this;
var _this5 = this;
return this.columns.filter(function (column) {
return column.visible && !_this7.isSelectionColumn(column);
return column.visible && !_this5.isSelectionColumn(column);
});
},
getHiddenColumns: function getHiddenColumns() {
@@ -10716,6 +10828,9 @@
}
});
}
if (Utils.hasRowspanCells(this.data)) {
Utils.flattenRowspanCells(this.data);
}
if (this.options.customSort) {
Utils.calculateObjectValue(this.options, this.options.customSort, [this.options.sortName, this.options.sortOrder, this.data]);
} else {
@@ -11598,6 +11713,11 @@
opts.totalRows = data.length;
}
this.totalPages = 0;
if (opts.pageSize <= 0) {
console.warn('pageSize must be a positive number, falling back to show all rows.');
opts.pageSize = opts.totalRows || 1;
allSelected = true;
}
if (opts.totalRows) {
if (opts.pageSize === opts.formatAllRows()) {
opts.pageSize = opts.totalRows;
@@ -12042,6 +12162,44 @@
};
var ToolbarModule = {
renderButton: function renderButton(buttonName, buttonConfig) {
var opts = this.options;
var buttonHtml;
if (buttonConfig.hasOwnProperty('html')) {
if (typeof buttonConfig.html === 'function') {
buttonHtml = buttonConfig.html();
} else {
buttonHtml = buttonConfig.html;
}
} else {
var buttonClass = this.constants.buttonsClass;
if (buttonConfig.hasOwnProperty('attributes') && buttonConfig.attributes.class) {
buttonClass += " ".concat(buttonConfig.attributes.class);
}
buttonHtml = "<button class=\"".concat(buttonClass, "\" type=\"button\" name=\"").concat(buttonName, "\"");
if (buttonConfig.hasOwnProperty('attributes')) {
for (var _i = 0, _Object$entries = Object.entries(buttonConfig.attributes); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
attributeName = _Object$entries$_i[0],
value = _Object$entries$_i[1];
if (attributeName === 'class') {
continue;
}
var attribute = attributeName === 'title' ? opts.buttonsAttributeTitle : attributeName;
buttonHtml += " ".concat(attribute, "=\"").concat(value, "\"");
}
}
buttonHtml += '>';
if (opts.showButtonIcons && buttonConfig.hasOwnProperty('icon')) {
buttonHtml += "".concat(Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, buttonConfig.icon), " ");
}
if (opts.showButtonText && buttonConfig.hasOwnProperty('text')) {
buttonHtml += buttonConfig.text;
}
buttonHtml += '</button>';
}
return buttonHtml;
},
initToolbar: function initToolbar() {
var _this = this;
var opts = this.options;
@@ -12165,45 +12323,11 @@
}
});
var buttonsHtml = {};
for (var _i = 0, _Object$entries = Object.entries(this.buttons); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
buttonName = _Object$entries$_i[0],
buttonConfig = _Object$entries$_i[1];
var buttonHtml = void 0;
if (buttonConfig.hasOwnProperty('html')) {
if (typeof buttonConfig.html === 'function') {
buttonHtml = buttonConfig.html();
} else {
buttonHtml = buttonConfig.html;
}
} else {
var buttonClass = this.constants.buttonsClass;
if (buttonConfig.hasOwnProperty('attributes') && buttonConfig.attributes.class) {
buttonClass += " ".concat(buttonConfig.attributes.class);
}
buttonHtml = "<button class=\"".concat(buttonClass, "\" type=\"button\" name=\"").concat(buttonName, "\"");
if (buttonConfig.hasOwnProperty('attributes')) {
for (var _i2 = 0, _Object$entries2 = Object.entries(buttonConfig.attributes); _i2 < _Object$entries2.length; _i2++) {
var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
attributeName = _Object$entries2$_i[0],
value = _Object$entries2$_i[1];
if (attributeName === 'class') {
continue;
}
var attribute = attributeName === 'title' ? this.options.buttonsAttributeTitle : attributeName;
buttonHtml += " ".concat(attribute, "=\"").concat(value, "\"");
}
}
buttonHtml += '>';
if (opts.showButtonIcons && buttonConfig.hasOwnProperty('icon')) {
buttonHtml += "".concat(Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, buttonConfig.icon), " ");
}
if (opts.showButtonText && buttonConfig.hasOwnProperty('text')) {
buttonHtml += buttonConfig.text;
}
buttonHtml += '</button>';
}
buttonsHtml[buttonName] = buttonHtml;
for (var _i2 = 0, _Object$entries2 = Object.entries(this.buttons); _i2 < _Object$entries2.length; _i2++) {
var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
buttonName = _Object$entries2$_i[0],
buttonConfig = _Object$entries2$_i[1];
buttonsHtml[buttonName] = this.renderButton(buttonName, buttonConfig);
var optionName = "show".concat(buttonName.charAt(0).toUpperCase()).concat(buttonName.substring(1));
var showOption = opts[optionName];
if ((!buttonConfig.hasOwnProperty('render') || buttonConfig.hasOwnProperty('render') && buttonConfig.render) && (showOption === undefined || showOption === true)) {
@@ -12302,7 +12426,7 @@
$checkboxes.off('click').on('click', function (_ref) {
var currentTarget = _ref.currentTarget;
var $this = $(currentTarget);
_this._toggleColumn($this.val(), $this.prop('checked'), false);
_this._toggleColumns([$this.data('field')], $this.prop('checked'), false);
_this.trigger('column-switch', $this.data('field'), $this.prop('checked'));
$toggleAll.prop('checked', $checkboxes.filter(':checked').length === _this.columns.filter(function (column) {
return !_this.isSelectionColumn(column);
File diff suppressed because one or more lines are too long.
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -4189,12 +4189,12 @@
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber);
}
}, {
key: "_toggleColumn",
value: function _toggleColumn() {
key: "_toggleColumns",
value: function _toggleColumns() {
for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
args[_key9] = arguments[_key9];
}
_superPropGet(_class, "_toggleColumn", this)(args);
_superPropGet(_class, "_toggleColumns", this)(args);
if (!this.options.cookie) {
return;
}
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -3602,7 +3602,6 @@
}
$exportButtons.click(function (e) {
e.preventDefault();
_this.trigger('export-started');
_this.exportTable({
type: $(e.currentTarget).data('type')
});
@@ -3694,6 +3693,18 @@
}
}, o.exportOptions, options));
};
// Early return for selected data type when no rows are selected
var selectedData = null;
if (o.exportDataType === 'selected') {
selectedData = this.getSelections();
if (!selectedData.length) {
return;
}
}
// Trigger export-started after early-return checks and before any export operation
this.trigger('export-started');
if (o.exportDataType === 'all' && o.pagination) {
var eventName = o.sidePagination === 'server' ? 'post-body.bs.table' : 'page-change.bs.table';
var virtualScroll = this.options.virtualScroll;
@@ -3714,11 +3725,7 @@
includeHiddenRows: true,
unfiltered: true
});
var selectedData = this.getSelections();
var pagination = o.pagination;
if (!selectedData.length) {
return;
}
if (o.sidePagination === 'server') {
data = _defineProperty({
total: o.totalRows
@@ -6130,18 +6130,8 @@
keyCode = _ref5.keyCode;
var $selectControl = $(currentTarget);
var value = $selectControl.val();
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
if (value[i] && value[i].length > 0 && value[i].trim()) {
$selectControl.find("option[value=\"".concat(value[i], "\"]")).attr('selected', true);
}
}
} else if (value && value.length > 0 && value.trim()) {
$selectControl.find('option[selected]').removeAttr('selected');
$selectControl.find("option[value=\"".concat(value, "\"]")).attr('selected', true);
} else {
$selectControl.find('option[selected]').removeAttr('selected');
}
var normalizedValue = value === null ? $selectControl.prop('multiple') ? [] : null : value;
$selectControl.val(normalizedValue);
clearTimeout(currentTarget.timeoutId || 0);
currentTarget.timeoutId = setTimeout(function () {
that.onColumnSearch({
@@ -6842,10 +6832,12 @@
});
}
}, {
key: "_toggleColumn",
value: function _toggleColumn(index, checked, needUpdate) {
this._initialized = false;
_superPropGet(_class, "_toggleColumn", this)([index, checked, needUpdate]);
key: "_toggleColumns",
value: function _toggleColumns(fields, checked, needUpdate) {
if (fields.length) {
this._initialized = false;
}
_superPropGet(_class, "_toggleColumns", this)([fields, checked, needUpdate]);
syncHeaders(this);
}
}]);
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
File diff suppressed because one or more lines are too long.
@@ -4187,18 +4187,8 @@
keyCode = _ref5.keyCode;
var $selectControl = $(currentTarget);
var value = $selectControl.val();
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
if (value[i] && value[i].length > 0 && value[i].trim()) {
$selectControl.find("option[value=\"".concat(value[i], "\"]")).attr('selected', true);
}
}
} else if (value && value.length > 0 && value.trim()) {
$selectControl.find('option[selected]').removeAttr('selected');
$selectControl.find("option[value=\"".concat(value, "\"]")).attr('selected', true);
} else {
$selectControl.find('option[selected]').removeAttr('selected');
}
var normalizedValue = value === null ? $selectControl.prop('multiple') ? [] : null : value;
$selectControl.val(normalizedValue);
clearTimeout(currentTarget.timeoutId || 0);
currentTarget.timeoutId = setTimeout(function () {
that.onColumnSearch({
@@ -21,3 +21,9 @@
.fixed-columns-right .fixed-table-body {
overflow-x: hidden !important;
}
.fixed-columns .table-hover .hover-row,
.fixed-columns-right .table-hover .hover-row,
.fixed-table-container > .fixed-table-body .table-hover .hover-row {
background-color: var(--bt-table-hover-bg, var(--bs-table-hover-bg, rgba(0, 0, 0, 0.075)));
}
@@ -2592,20 +2592,27 @@
var _this4 = this;
var toggleHover = function toggleHover(e, toggle) {
var tr = "tr[data-index=\"".concat($(e.currentTarget).data('index'), "\"]");
var $trs = _this4.$tableBody.find(tr);
var isFromFixed = $(e.currentTarget).closest('.fixed-columns, .fixed-columns-right').length > 0;
var $trs = $();
if (isFromFixed) {
$trs = $trs.add(_this4.$tableBody.find(tr));
}
if (_this4.$fixedBody) {
$trs = $trs.add(_this4.$fixedBody.find(tr));
}
if (_this4.$fixedBodyRight) {
$trs = $trs.add(_this4.$fixedBodyRight.find(tr));
}
$trs.css('background-color', toggle ? $(e.currentTarget).css('background-color') : '');
$trs.toggleClass('hover-row', toggle);
};
this.$tableBody.find('tr').hover(function (e) {
toggleHover(e, true);
}, function (e) {
toggleHover(e, false);
});
var bindHover = function bindHover($el) {
$el.find('tr').hover(function (e) {
toggleHover(e, true);
}, function (e) {
toggleHover(e, false);
});
};
bindHover(this.$tableBody);
var isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
var mousewheel = isFirefox ? 'DOMMouseScroll' : 'mousewheel';
var updateScroll = function updateScroll(e, fixedBody) {
@@ -2624,21 +2631,13 @@
}
};
if (this.needFixedColumns && this.options.fixedNumber && this.$fixedBody) {
this.$fixedBody.find('tr').hover(function (e) {
toggleHover(e, true);
}, function (e) {
toggleHover(e, false);
});
bindHover(this.$fixedBody);
this.$fixedBody[0].addEventListener(mousewheel, function (e) {
updateScroll(e, _this4.$fixedBody[0]);
});
}
if (this.needFixedColumns && this.options.fixedRightNumber) {
this.$fixedBodyRight.find('tr').hover(function (e) {
toggleHover(e, true);
}, function (e) {
toggleHover(e, false);
});
bindHover(this.$fixedBodyRight);
this.$fixedBodyRight.off('scroll').on('scroll', function () {
var top = _this4.$fixedBodyRight.scrollTop();
_this4.$tableBody.scrollTop(top);
@@ -1,10 +1,10 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
*/
.fixed-columns,.fixed-columns-right{position:absolute;top:0;height:100%;background-color:#fff;box-sizing:border-box;z-index:1}.fixed-columns{left:0}.fixed-columns .fixed-table-body{overflow:hidden!important}.fixed-columns-right{right:0}.fixed-columns-right .fixed-table-body{overflow-x:hidden!important}
.fixed-columns,.fixed-columns-right{position:absolute;top:0;height:100%;background-color:#fff;box-sizing:border-box;z-index:1}.fixed-columns{left:0}.fixed-columns .fixed-table-body{overflow:hidden!important}.fixed-columns-right{right:0}.fixed-columns-right .fixed-table-body{overflow-x:hidden!important}.fixed-columns .table-hover .hover-row,.fixed-columns-right .table-hover .hover-row,.fixed-table-container>.fixed-table-body .table-hover .hover-row{background-color:var(--bt-table-hover-bg,var(--bs-table-hover-bg,rgba(0,0,0,.075)))}
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -96,7 +96,7 @@
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
var es_array_includes = {};
var es_array_filter = {};
var globalThis_1;
var hasRequiredGlobalThis;
@@ -1612,6 +1612,376 @@
return _export;
}
var functionUncurryThisClause;
var hasRequiredFunctionUncurryThisClause;
function requireFunctionUncurryThisClause () {
if (hasRequiredFunctionUncurryThisClause) return functionUncurryThisClause;
hasRequiredFunctionUncurryThisClause = 1;
var classofRaw = requireClassofRaw();
var uncurryThis = requireFunctionUncurryThis();
functionUncurryThisClause = function (fn) {
// Nashorn bug:
// https://github.com/zloirock/core-js/issues/1128
// https://github.com/zloirock/core-js/issues/1130
if (classofRaw(fn) === 'Function') return uncurryThis(fn);
};
return functionUncurryThisClause;
}
var functionBindContext;
var hasRequiredFunctionBindContext;
function requireFunctionBindContext () {
if (hasRequiredFunctionBindContext) return functionBindContext;
hasRequiredFunctionBindContext = 1;
var uncurryThis = requireFunctionUncurryThisClause();
var aCallable = requireACallable();
var NATIVE_BIND = requireFunctionBindNative();
var bind = uncurryThis(uncurryThis.bind);
// optional / simple context binding
functionBindContext = function (fn, that) {
aCallable(fn);
return that === undefined ? fn : NATIVE_BIND ? bind(fn, that) : function (/* ...args */) {
return fn.apply(that, arguments);
};
};
return functionBindContext;
}
var isArray;
var hasRequiredIsArray;
function requireIsArray () {
if (hasRequiredIsArray) return isArray;
hasRequiredIsArray = 1;
var classof = requireClassofRaw();
// `IsArray` abstract operation
// https://tc39.es/ecma262/#sec-isarray
// eslint-disable-next-line es/no-array-isarray -- safe
isArray = Array.isArray || function isArray(argument) {
return classof(argument) === 'Array';
};
return isArray;
}
var toStringTagSupport;
var hasRequiredToStringTagSupport;
function requireToStringTagSupport () {
if (hasRequiredToStringTagSupport) return toStringTagSupport;
hasRequiredToStringTagSupport = 1;
var wellKnownSymbol = requireWellKnownSymbol();
var TO_STRING_TAG = wellKnownSymbol('toStringTag');
var test = {};
// eslint-disable-next-line unicorn/no-immediate-mutation -- ES3 syntax limitation
test[TO_STRING_TAG] = 'z';
toStringTagSupport = String(test) === '[object z]';
return toStringTagSupport;
}
var classof;
var hasRequiredClassof;
function requireClassof () {
if (hasRequiredClassof) return classof;
hasRequiredClassof = 1;
var TO_STRING_TAG_SUPPORT = requireToStringTagSupport();
var isCallable = requireIsCallable();
var classofRaw = requireClassofRaw();
var wellKnownSymbol = requireWellKnownSymbol();
var TO_STRING_TAG = wellKnownSymbol('toStringTag');
var $Object = Object;
// ES3 wrong here
var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) === 'Arguments';
// fallback for IE11 Script Access Denied error
var tryGet = function (it, key) {
try {
return it[key];
} catch (error) { /* empty */ }
};
// getting tag from ES6+ `Object.prototype.toString`
classof = TO_STRING_TAG_SUPPORT ? classofRaw : function (it) {
var O, tag, result;
return it === undefined ? 'Undefined' : it === null ? 'Null'
// @@toStringTag case
: typeof (tag = tryGet(O = $Object(it), TO_STRING_TAG)) == 'string' ? tag
// builtinTag case
: CORRECT_ARGUMENTS ? classofRaw(O)
// ES3 arguments fallback
: (result = classofRaw(O)) === 'Object' && isCallable(O.callee) ? 'Arguments' : result;
};
return classof;
}
var isConstructor;
var hasRequiredIsConstructor;
function requireIsConstructor () {
if (hasRequiredIsConstructor) return isConstructor;
hasRequiredIsConstructor = 1;
var uncurryThis = requireFunctionUncurryThis();
var fails = requireFails();
var isCallable = requireIsCallable();
var classof = requireClassof();
var getBuiltIn = requireGetBuiltIn();
var inspectSource = requireInspectSource();
var noop = function () { /* empty */ };
var construct = getBuiltIn('Reflect', 'construct');
var constructorRegExp = /^\s*(?:class|function)\b/;
var exec = uncurryThis(constructorRegExp.exec);
var INCORRECT_TO_STRING = !constructorRegExp.test(noop);
var isConstructorModern = function isConstructor(argument) {
if (!isCallable(argument)) return false;
try {
construct(noop, [], argument);
return true;
} catch (error) {
return false;
}
};
var isConstructorLegacy = function isConstructor(argument) {
if (!isCallable(argument)) return false;
switch (classof(argument)) {
case 'AsyncFunction':
case 'GeneratorFunction':
case 'AsyncGeneratorFunction': return false;
}
try {
// we can't check .prototype since constructors produced by .bind haven't it
// `Function#toString` throws on some built-it function in some legacy engines
// (for example, `DOMQuad` and similar in FF41-)
return INCORRECT_TO_STRING || !!exec(constructorRegExp, inspectSource(argument));
} catch (error) {
return true;
}
};
isConstructorLegacy.sham = true;
// `IsConstructor` abstract operation
// https://tc39.es/ecma262/#sec-isconstructor
isConstructor = !construct || fails(function () {
var called;
return isConstructorModern(isConstructorModern.call)
|| !isConstructorModern(Object)
|| !isConstructorModern(function () { called = true; })
|| called;
}) ? isConstructorLegacy : isConstructorModern;
return isConstructor;
}
var arraySpeciesConstructor;
var hasRequiredArraySpeciesConstructor;
function requireArraySpeciesConstructor () {
if (hasRequiredArraySpeciesConstructor) return arraySpeciesConstructor;
hasRequiredArraySpeciesConstructor = 1;
var isArray = requireIsArray();
var isConstructor = requireIsConstructor();
var isObject = requireIsObject();
var wellKnownSymbol = requireWellKnownSymbol();
var SPECIES = wellKnownSymbol('species');
var $Array = Array;
// a part of `ArraySpeciesCreate` abstract operation
// https://tc39.es/ecma262/#sec-arrayspeciescreate
arraySpeciesConstructor = function (originalArray) {
var C;
if (isArray(originalArray)) {
C = originalArray.constructor;
// cross-realm fallback
if (isConstructor(C) && (C === $Array || isArray(C.prototype))) C = undefined;
else if (isObject(C)) {
C = C[SPECIES];
if (C === null) C = undefined;
}
} return C === undefined ? $Array : C;
};
return arraySpeciesConstructor;
}
var arraySpeciesCreate;
var hasRequiredArraySpeciesCreate;
function requireArraySpeciesCreate () {
if (hasRequiredArraySpeciesCreate) return arraySpeciesCreate;
hasRequiredArraySpeciesCreate = 1;
var arraySpeciesConstructor = requireArraySpeciesConstructor();
// `ArraySpeciesCreate` abstract operation
// https://tc39.es/ecma262/#sec-arrayspeciescreate
arraySpeciesCreate = function (originalArray, length) {
return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length);
};
return arraySpeciesCreate;
}
var createProperty;
var hasRequiredCreateProperty;
function requireCreateProperty () {
if (hasRequiredCreateProperty) return createProperty;
hasRequiredCreateProperty = 1;
var DESCRIPTORS = requireDescriptors();
var definePropertyModule = requireObjectDefineProperty();
var createPropertyDescriptor = requireCreatePropertyDescriptor();
createProperty = function (object, key, value) {
if (DESCRIPTORS) definePropertyModule.f(object, key, createPropertyDescriptor(0, value));
else object[key] = value;
};
return createProperty;
}
var arrayIteration;
var hasRequiredArrayIteration;
function requireArrayIteration () {
if (hasRequiredArrayIteration) return arrayIteration;
hasRequiredArrayIteration = 1;
var bind = requireFunctionBindContext();
var IndexedObject = requireIndexedObject();
var toObject = requireToObject();
var lengthOfArrayLike = requireLengthOfArrayLike();
var arraySpeciesCreate = requireArraySpeciesCreate();
var createProperty = requireCreateProperty();
// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation
var createMethod = function (TYPE) {
var IS_MAP = TYPE === 1;
var IS_FILTER = TYPE === 2;
var IS_SOME = TYPE === 3;
var IS_EVERY = TYPE === 4;
var IS_FIND_INDEX = TYPE === 6;
var IS_FILTER_REJECT = TYPE === 7;
var NO_HOLES = TYPE === 5 || IS_FIND_INDEX;
return function ($this, callbackfn, that) {
var O = toObject($this);
var self = IndexedObject(O);
var length = lengthOfArrayLike(self);
var boundFunction = bind(callbackfn, that);
var index = 0;
var resIndex = 0;
var target = IS_MAP ? arraySpeciesCreate($this, length) : IS_FILTER || IS_FILTER_REJECT ? arraySpeciesCreate($this, 0) : undefined;
var value, result;
for (;length > index; index++) if (NO_HOLES || index in self) {
value = self[index];
result = boundFunction(value, index, O);
if (TYPE) {
if (IS_MAP) createProperty(target, index, result); // map
else if (result) switch (TYPE) {
case 3: return true; // some
case 5: return value; // find
case 6: return index; // findIndex
case 2: createProperty(target, resIndex++, value); // filter
} else switch (TYPE) {
case 4: return false; // every
case 7: createProperty(target, resIndex++, value); // filterReject
}
}
}
return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target;
};
};
arrayIteration = {
// `Array.prototype.forEach` method
// https://tc39.es/ecma262/#sec-array.prototype.foreach
forEach: createMethod(0),
// `Array.prototype.map` method
// https://tc39.es/ecma262/#sec-array.prototype.map
map: createMethod(1),
// `Array.prototype.filter` method
// https://tc39.es/ecma262/#sec-array.prototype.filter
filter: createMethod(2),
// `Array.prototype.some` method
// https://tc39.es/ecma262/#sec-array.prototype.some
some: createMethod(3),
// `Array.prototype.every` method
// https://tc39.es/ecma262/#sec-array.prototype.every
every: createMethod(4),
// `Array.prototype.find` method
// https://tc39.es/ecma262/#sec-array.prototype.find
find: createMethod(5),
// `Array.prototype.findIndex` method
// https://tc39.es/ecma262/#sec-array.prototype.findIndex
findIndex: createMethod(6),
// `Array.prototype.filterReject` method
// https://github.com/tc39/proposal-array-filtering
filterReject: createMethod(7)
};
return arrayIteration;
}
var arrayMethodHasSpeciesSupport;
var hasRequiredArrayMethodHasSpeciesSupport;
function requireArrayMethodHasSpeciesSupport () {
if (hasRequiredArrayMethodHasSpeciesSupport) return arrayMethodHasSpeciesSupport;
hasRequiredArrayMethodHasSpeciesSupport = 1;
var fails = requireFails();
var wellKnownSymbol = requireWellKnownSymbol();
var V8_VERSION = requireEnvironmentV8Version();
var SPECIES = wellKnownSymbol('species');
arrayMethodHasSpeciesSupport = function (METHOD_NAME) {
// We can't use this feature detection in V8 since it causes
// deoptimization and serious performance degradation
// https://github.com/zloirock/core-js/issues/677
return V8_VERSION >= 51 || !fails(function () {
var array = [];
var constructor = array.constructor = {};
constructor[SPECIES] = function () {
return { foo: 1 };
};
return array[METHOD_NAME](Boolean).foo !== 1;
});
};
return arrayMethodHasSpeciesSupport;
}
var hasRequiredEs_array_filter;
function requireEs_array_filter () {
if (hasRequiredEs_array_filter) return es_array_filter;
hasRequiredEs_array_filter = 1;
var $ = require_export();
var $filter = requireArrayIteration().filter;
var arrayMethodHasSpeciesSupport = requireArrayMethodHasSpeciesSupport();
var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('filter');
// `Array.prototype.filter` method
// https://tc39.es/ecma262/#sec-array.prototype.filter
// with adding support of @@species
$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, {
filter: function filter(callbackfn /* , thisArg */) {
return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
});
return es_array_filter;
}
requireEs_array_filter();
var es_array_includes = {};
var objectDefineProperties = {};
var objectKeys;
@@ -1831,6 +2201,32 @@
requireEs_array_includes();
var es_array_map = {};
var hasRequiredEs_array_map;
function requireEs_array_map () {
if (hasRequiredEs_array_map) return es_array_map;
hasRequiredEs_array_map = 1;
var $ = require_export();
var $map = requireArrayIteration().map;
var arrayMethodHasSpeciesSupport = requireArrayMethodHasSpeciesSupport();
var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('map');
// `Array.prototype.map` method
// https://tc39.es/ecma262/#sec-array.prototype.map
// with adding support of @@species
$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, {
map: function map(callbackfn /* , thisArg */) {
return $map(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
});
return es_array_map;
}
requireEs_array_map();
var es_object_assign = {};
var objectAssign;
@@ -1920,61 +2316,6 @@
var es_object_toString = {};
var toStringTagSupport;
var hasRequiredToStringTagSupport;
function requireToStringTagSupport () {
if (hasRequiredToStringTagSupport) return toStringTagSupport;
hasRequiredToStringTagSupport = 1;
var wellKnownSymbol = requireWellKnownSymbol();
var TO_STRING_TAG = wellKnownSymbol('toStringTag');
var test = {};
// eslint-disable-next-line unicorn/no-immediate-mutation -- ES3 syntax limitation
test[TO_STRING_TAG] = 'z';
toStringTagSupport = String(test) === '[object z]';
return toStringTagSupport;
}
var classof;
var hasRequiredClassof;
function requireClassof () {
if (hasRequiredClassof) return classof;
hasRequiredClassof = 1;
var TO_STRING_TAG_SUPPORT = requireToStringTagSupport();
var isCallable = requireIsCallable();
var classofRaw = requireClassofRaw();
var wellKnownSymbol = requireWellKnownSymbol();
var TO_STRING_TAG = wellKnownSymbol('toStringTag');
var $Object = Object;
// ES3 wrong here
var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) === 'Arguments';
// fallback for IE11 Script Access Denied error
var tryGet = function (it, key) {
try {
return it[key];
} catch (error) { /* empty */ }
};
// getting tag from ES6+ `Object.prototype.toString`
classof = TO_STRING_TAG_SUPPORT ? classofRaw : function (it) {
var O, tag, result;
return it === undefined ? 'Undefined' : it === null ? 'Null'
// @@toStringTag case
: typeof (tag = tryGet(O = $Object(it), TO_STRING_TAG)) == 'string' ? tag
// builtinTag case
: CORRECT_ARGUMENTS ? classofRaw(O)
// ES3 arguments fallback
: (result = classofRaw(O)) === 'Object' && isCallable(O.callee) ? 'Arguments' : result;
};
return classof;
}
var objectToString;
var hasRequiredObjectToString;
@@ -2123,399 +2464,6 @@
requireEs_string_includes();
var web_domCollections_forEach = {};
var domIterables;
var hasRequiredDomIterables;
function requireDomIterables () {
if (hasRequiredDomIterables) return domIterables;
hasRequiredDomIterables = 1;
// iterable DOM collections
// flag - `iterable` interface - 'entries', 'keys', 'values', 'forEach' methods
domIterables = {
CSSRuleList: 0,
CSSStyleDeclaration: 0,
CSSValueList: 0,
ClientRectList: 0,
DOMRectList: 0,
DOMStringList: 0,
DOMTokenList: 1,
DataTransferItemList: 0,
FileList: 0,
HTMLAllCollection: 0,
HTMLCollection: 0,
HTMLFormElement: 0,
HTMLSelectElement: 0,
MediaList: 0,
MimeTypeArray: 0,
NamedNodeMap: 0,
NodeList: 1,
PaintRequestList: 0,
Plugin: 0,
PluginArray: 0,
SVGLengthList: 0,
SVGNumberList: 0,
SVGPathSegList: 0,
SVGPointList: 0,
SVGStringList: 0,
SVGTransformList: 0,
SourceBufferList: 0,
StyleSheetList: 0,
TextTrackCueList: 0,
TextTrackList: 0,
TouchList: 0
};
return domIterables;
}
var domTokenListPrototype;
var hasRequiredDomTokenListPrototype;
function requireDomTokenListPrototype () {
if (hasRequiredDomTokenListPrototype) return domTokenListPrototype;
hasRequiredDomTokenListPrototype = 1;
// in old WebKit versions, `element.classList` is not an instance of global `DOMTokenList`
var documentCreateElement = requireDocumentCreateElement();
var classList = documentCreateElement('span').classList;
var DOMTokenListPrototype = classList && classList.constructor && classList.constructor.prototype;
domTokenListPrototype = DOMTokenListPrototype === Object.prototype ? undefined : DOMTokenListPrototype;
return domTokenListPrototype;
}
var functionUncurryThisClause;
var hasRequiredFunctionUncurryThisClause;
function requireFunctionUncurryThisClause () {
if (hasRequiredFunctionUncurryThisClause) return functionUncurryThisClause;
hasRequiredFunctionUncurryThisClause = 1;
var classofRaw = requireClassofRaw();
var uncurryThis = requireFunctionUncurryThis();
functionUncurryThisClause = function (fn) {
// Nashorn bug:
// https://github.com/zloirock/core-js/issues/1128
// https://github.com/zloirock/core-js/issues/1130
if (classofRaw(fn) === 'Function') return uncurryThis(fn);
};
return functionUncurryThisClause;
}
var functionBindContext;
var hasRequiredFunctionBindContext;
function requireFunctionBindContext () {
if (hasRequiredFunctionBindContext) return functionBindContext;
hasRequiredFunctionBindContext = 1;
var uncurryThis = requireFunctionUncurryThisClause();
var aCallable = requireACallable();
var NATIVE_BIND = requireFunctionBindNative();
var bind = uncurryThis(uncurryThis.bind);
// optional / simple context binding
functionBindContext = function (fn, that) {
aCallable(fn);
return that === undefined ? fn : NATIVE_BIND ? bind(fn, that) : function (/* ...args */) {
return fn.apply(that, arguments);
};
};
return functionBindContext;
}
var isArray;
var hasRequiredIsArray;
function requireIsArray () {
if (hasRequiredIsArray) return isArray;
hasRequiredIsArray = 1;
var classof = requireClassofRaw();
// `IsArray` abstract operation
// https://tc39.es/ecma262/#sec-isarray
// eslint-disable-next-line es/no-array-isarray -- safe
isArray = Array.isArray || function isArray(argument) {
return classof(argument) === 'Array';
};
return isArray;
}
var isConstructor;
var hasRequiredIsConstructor;
function requireIsConstructor () {
if (hasRequiredIsConstructor) return isConstructor;
hasRequiredIsConstructor = 1;
var uncurryThis = requireFunctionUncurryThis();
var fails = requireFails();
var isCallable = requireIsCallable();
var classof = requireClassof();
var getBuiltIn = requireGetBuiltIn();
var inspectSource = requireInspectSource();
var noop = function () { /* empty */ };
var construct = getBuiltIn('Reflect', 'construct');
var constructorRegExp = /^\s*(?:class|function)\b/;
var exec = uncurryThis(constructorRegExp.exec);
var INCORRECT_TO_STRING = !constructorRegExp.test(noop);
var isConstructorModern = function isConstructor(argument) {
if (!isCallable(argument)) return false;
try {
construct(noop, [], argument);
return true;
} catch (error) {
return false;
}
};
var isConstructorLegacy = function isConstructor(argument) {
if (!isCallable(argument)) return false;
switch (classof(argument)) {
case 'AsyncFunction':
case 'GeneratorFunction':
case 'AsyncGeneratorFunction': return false;
}
try {
// we can't check .prototype since constructors produced by .bind haven't it
// `Function#toString` throws on some built-it function in some legacy engines
// (for example, `DOMQuad` and similar in FF41-)
return INCORRECT_TO_STRING || !!exec(constructorRegExp, inspectSource(argument));
} catch (error) {
return true;
}
};
isConstructorLegacy.sham = true;
// `IsConstructor` abstract operation
// https://tc39.es/ecma262/#sec-isconstructor
isConstructor = !construct || fails(function () {
var called;
return isConstructorModern(isConstructorModern.call)
|| !isConstructorModern(Object)
|| !isConstructorModern(function () { called = true; })
|| called;
}) ? isConstructorLegacy : isConstructorModern;
return isConstructor;
}
var arraySpeciesConstructor;
var hasRequiredArraySpeciesConstructor;
function requireArraySpeciesConstructor () {
if (hasRequiredArraySpeciesConstructor) return arraySpeciesConstructor;
hasRequiredArraySpeciesConstructor = 1;
var isArray = requireIsArray();
var isConstructor = requireIsConstructor();
var isObject = requireIsObject();
var wellKnownSymbol = requireWellKnownSymbol();
var SPECIES = wellKnownSymbol('species');
var $Array = Array;
// a part of `ArraySpeciesCreate` abstract operation
// https://tc39.es/ecma262/#sec-arrayspeciescreate
arraySpeciesConstructor = function (originalArray) {
var C;
if (isArray(originalArray)) {
C = originalArray.constructor;
// cross-realm fallback
if (isConstructor(C) && (C === $Array || isArray(C.prototype))) C = undefined;
else if (isObject(C)) {
C = C[SPECIES];
if (C === null) C = undefined;
}
} return C === undefined ? $Array : C;
};
return arraySpeciesConstructor;
}
var arraySpeciesCreate;
var hasRequiredArraySpeciesCreate;
function requireArraySpeciesCreate () {
if (hasRequiredArraySpeciesCreate) return arraySpeciesCreate;
hasRequiredArraySpeciesCreate = 1;
var arraySpeciesConstructor = requireArraySpeciesConstructor();
// `ArraySpeciesCreate` abstract operation
// https://tc39.es/ecma262/#sec-arrayspeciescreate
arraySpeciesCreate = function (originalArray, length) {
return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length);
};
return arraySpeciesCreate;
}
var createProperty;
var hasRequiredCreateProperty;
function requireCreateProperty () {
if (hasRequiredCreateProperty) return createProperty;
hasRequiredCreateProperty = 1;
var DESCRIPTORS = requireDescriptors();
var definePropertyModule = requireObjectDefineProperty();
var createPropertyDescriptor = requireCreatePropertyDescriptor();
createProperty = function (object, key, value) {
if (DESCRIPTORS) definePropertyModule.f(object, key, createPropertyDescriptor(0, value));
else object[key] = value;
};
return createProperty;
}
var arrayIteration;
var hasRequiredArrayIteration;
function requireArrayIteration () {
if (hasRequiredArrayIteration) return arrayIteration;
hasRequiredArrayIteration = 1;
var bind = requireFunctionBindContext();
var IndexedObject = requireIndexedObject();
var toObject = requireToObject();
var lengthOfArrayLike = requireLengthOfArrayLike();
var arraySpeciesCreate = requireArraySpeciesCreate();
var createProperty = requireCreateProperty();
// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation
var createMethod = function (TYPE) {
var IS_MAP = TYPE === 1;
var IS_FILTER = TYPE === 2;
var IS_SOME = TYPE === 3;
var IS_EVERY = TYPE === 4;
var IS_FIND_INDEX = TYPE === 6;
var IS_FILTER_REJECT = TYPE === 7;
var NO_HOLES = TYPE === 5 || IS_FIND_INDEX;
return function ($this, callbackfn, that) {
var O = toObject($this);
var self = IndexedObject(O);
var length = lengthOfArrayLike(self);
var boundFunction = bind(callbackfn, that);
var index = 0;
var resIndex = 0;
var target = IS_MAP ? arraySpeciesCreate($this, length) : IS_FILTER || IS_FILTER_REJECT ? arraySpeciesCreate($this, 0) : undefined;
var value, result;
for (;length > index; index++) if (NO_HOLES || index in self) {
value = self[index];
result = boundFunction(value, index, O);
if (TYPE) {
if (IS_MAP) createProperty(target, index, result); // map
else if (result) switch (TYPE) {
case 3: return true; // some
case 5: return value; // find
case 6: return index; // findIndex
case 2: createProperty(target, resIndex++, value); // filter
} else switch (TYPE) {
case 4: return false; // every
case 7: createProperty(target, resIndex++, value); // filterReject
}
}
}
return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target;
};
};
arrayIteration = {
// `Array.prototype.forEach` method
// https://tc39.es/ecma262/#sec-array.prototype.foreach
forEach: createMethod(0),
// `Array.prototype.map` method
// https://tc39.es/ecma262/#sec-array.prototype.map
map: createMethod(1),
// `Array.prototype.filter` method
// https://tc39.es/ecma262/#sec-array.prototype.filter
filter: createMethod(2),
// `Array.prototype.some` method
// https://tc39.es/ecma262/#sec-array.prototype.some
some: createMethod(3),
// `Array.prototype.every` method
// https://tc39.es/ecma262/#sec-array.prototype.every
every: createMethod(4),
// `Array.prototype.find` method
// https://tc39.es/ecma262/#sec-array.prototype.find
find: createMethod(5),
// `Array.prototype.findIndex` method
// https://tc39.es/ecma262/#sec-array.prototype.findIndex
findIndex: createMethod(6),
// `Array.prototype.filterReject` method
// https://github.com/tc39/proposal-array-filtering
filterReject: createMethod(7)
};
return arrayIteration;
}
var arrayMethodIsStrict;
var hasRequiredArrayMethodIsStrict;
function requireArrayMethodIsStrict () {
if (hasRequiredArrayMethodIsStrict) return arrayMethodIsStrict;
hasRequiredArrayMethodIsStrict = 1;
var fails = requireFails();
arrayMethodIsStrict = function (METHOD_NAME, argument) {
var method = [][METHOD_NAME];
return !!method && fails(function () {
// eslint-disable-next-line no-useless-call -- required for testing
method.call(null, argument || function () { return 1; }, 1);
});
};
return arrayMethodIsStrict;
}
var arrayForEach;
var hasRequiredArrayForEach;
function requireArrayForEach () {
if (hasRequiredArrayForEach) return arrayForEach;
hasRequiredArrayForEach = 1;
var $forEach = requireArrayIteration().forEach;
var arrayMethodIsStrict = requireArrayMethodIsStrict();
var STRICT_METHOD = arrayMethodIsStrict('forEach');
// `Array.prototype.forEach` method implementation
// https://tc39.es/ecma262/#sec-array.prototype.foreach
arrayForEach = !STRICT_METHOD ? function forEach(callbackfn /* , thisArg */) {
return $forEach(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
// eslint-disable-next-line es/no-array-prototype-foreach -- safe
} : [].forEach;
return arrayForEach;
}
var hasRequiredWeb_domCollections_forEach;
function requireWeb_domCollections_forEach () {
if (hasRequiredWeb_domCollections_forEach) return web_domCollections_forEach;
hasRequiredWeb_domCollections_forEach = 1;
var globalThis = requireGlobalThis();
var DOMIterables = requireDomIterables();
var DOMTokenListPrototype = requireDomTokenListPrototype();
var forEach = requireArrayForEach();
var createNonEnumerableProperty = requireCreateNonEnumerableProperty();
var handlePrototype = function (CollectionPrototype) {
// some Chrome versions have non-configurable methods on DOMTokenList
if (CollectionPrototype && CollectionPrototype.forEach !== forEach) try {
createNonEnumerableProperty(CollectionPrototype, 'forEach', forEach);
} catch (error) {
CollectionPrototype.forEach = forEach;
}
};
for (var COLLECTION_NAME in DOMIterables) {
if (DOMIterables[COLLECTION_NAME]) {
handlePrototype(globalThis[COLLECTION_NAME] && globalThis[COLLECTION_NAME].prototype);
}
}
handlePrototype(DOMTokenListPrototype);
return web_domCollections_forEach;
}
requireWeb_domCollections_forEach();
/**
* @author: Dennis Hernández
* @update zhixin wen <wenzhixin2010@gmail.com>
@@ -2617,14 +2565,16 @@
key: "showHideColumns",
value: function showHideColumns(checked) {
var _this2 = this;
if (this.options.columnsHidden.length > 0) {
this.columns.forEach(function (column) {
if (_this2.options.columnsHidden.includes(column.field)) {
if (column.visible !== checked) {
_this2._toggleColumn(_this2.fieldsColumnsIndex[column.field], checked, true);
}
}
});
if (this.options.columnsHidden.length === 0) {
return;
}
var fieldsToToggle = this.columns.filter(function (column) {
return _this2.options.columnsHidden.includes(column.field) && column.visible !== checked;
}).map(function (column) {
return column.field;
});
if (fieldsToToggle.length > 0) {
this._toggleColumns(fieldsToToggle, checked, true);
}
}
}, {
File diff suppressed because one or more lines are too long.
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
@@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.27.1
* @version v1.27.3
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
Loaded 100 of 278 files, more files were not shown because too many files have changed in this diff. Show more