Merge pull request #1 from connortechnology/libvnc

Libvnc
This commit is contained in:
Kartik 2020-03-28 13:09:17 -07:00 committed by GitHub
commit ba8d9ee768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 194 additions and 159 deletions

5
db/zm_update-1.34.7.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.34.6 database to 1.34.7
--
-- No changes required
--

1
db/zm_update-1.35.2.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE Monitors MODIFY `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','NVSocket','VNC') NOT NULL default 'Local';

View File

@ -28,7 +28,7 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.35.1
Version: 1.35.2
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons

View File

@ -829,14 +829,20 @@ sub sendEmail {
Data => $body
);
### Add the attachments
my $total_size = 0;
foreach my $attachment ( @attachments ) {
Info("Attaching '$attachment->{path}'");
my $size = -s $attachment->{path};
$total_size += $size;
Info("Attaching '$attachment->{path}' which is $size bytes");
$mail->attach(
Path => $attachment->{path},
Type => $attachment->{type},
Disposition => 'attachment'
);
}
if ( $total_size > 10*1024*1024 ) {
Warning('Emails larger than 10Mb will often not be delivered! This one is '.int($total_size/(1024*1024)).'Mb');
}
### Send the Message
if ( $Config{ZM_SSMTP_MAIL} ) {
my $ssmtp_location = $Config{ZM_SSMTP_PATH};
@ -867,13 +873,20 @@ sub sendEmail {
Data => $body
);
my $total_size = 0;
foreach my $attachment ( @attachments ) {
Info("Attaching '$attachment->{path}'");
my $size = -s $attachment->{path};
$total_size += $size;
Info("Attaching '$attachment->{path}' which is $size bytes");
$mail->attach(
Path => $attachment->{path},
Type => $attachment->{type},
Encoding => 'base64'
);
} # end foreach attachment
if ( $total_size > 10*1024*1024 ) {
Warning('Emails larger than 10Mb will often not be delivered! This one is '.int($total_size/(1024*1024)).'Mb');
}
$mail->smtpsend(Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL});
}

View File

@ -847,9 +847,9 @@ if ( $version ) {
}
$cascade = !undef;
}
if ( $cascade || $version eq "1.24.4" ) {
if ( $cascade || $version eq '1.24.4' ) {
# Patch the database
patchDB( $dbh, "1.24.4" );
patchDB($dbh, '1.24.4');
# Copy the FTP specific values to the new general config
my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'";
@ -863,12 +863,12 @@ if ( $version ) {
}
$cascade = !undef;
}
if ( $cascade || $version lt "1.26.0" ) {
my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' );
if ( $cascade || $version lt '1.26.0' ) {
my $sth = $dbh->prepare_cached('SELECT * FROM Monitors LIMIT 0,1');
die "Error: " . $dbh->errstr . "\n" unless ($sth);
die "Error: " . $sth->errstr . "\n" unless ($sth->execute);
my $columns = $sth->{'NAME'};
my $columns = $sth->{NAME};
if ( ! grep(/^Colours$/, @$columns ) ) {
$dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;});
} # end if
@ -898,28 +898,31 @@ if ( $version ) {
die "Should have found upgrade scripts at $updateDir\n";
} # end if
my $sql = "UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_DB_VERSION'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
foreach my $patch ( @files ) {
my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/;
#PP make sure we use version compare
if ( version->parse('v'.$v) > version->parse('v'.$version) ) {
print("Upgrading DB to $v from $version\n");
patchDB( $dbh, $v );
my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
if ( patchDB($dbh, $v) ) {
my $res = $sth->execute($version) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
}
#patchDB_using_do( $dbh, $version, $updateDir.'/'.$patch );
} # end if newer version
} # end foreach patchfile
$sth->finish();
$cascade = !undef;
} # end if
if ( $cascade ) {
my $installed_version = ZM_VERSION;
my $sql = 'update Config set Value = ? where Name = ?';
# This is basically here so that we don't need zm-update-blah.sql files for versions without db changes
my $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = ?';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( "$installed_version", 'ZM_DYN_DB_VERSION' ) or die( "Can't execute: ".$sth->errstr() );
$res = $sth->execute( "$installed_version", 'ZM_DYN_CURR_VERSION' ) or die( "Can't execute: ".$sth->errstr() );
$sth->execute(ZM_VERSION, 'ZM_DYN_DB_VERSION') or die( "Can't execute: ".$sth->errstr() );
$sth->execute(ZM_VERSION, 'ZM_DYN_CURR_VERSION') or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
} else {
zmDbDisconnect();
@ -931,7 +934,8 @@ if ( $version ) {
#my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() );
#$sth->finish();
print("\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n");
}
} # end if version
zmDbDisconnect();
exit(0);
@ -943,28 +947,28 @@ sub patchDB_using_do {
my $sql = <$fh>;
close $fh;
if ( $sql ) {
$dbh->{'AutoCommit'} = 0;
$dbh->{AutoCommit} = 0;
$dbh->do($sql);
if ( $dbh->errstr() ) {
$dbh->rollback();
die "Error: " . $dbh->errstr(). ". Rolled back.\n";
die 'Error: '.$dbh->errstr().". Rolled back.\n";
} # end if error
my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() );
my $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = \'ZM_DYN_DB_VERSION\'';
my $sth = $dbh->prepare_cached($sql) or die "Can't prepare '$sql': ".$dbh->errstr();
my $res = $sth->execute($version) or die 'Can\'t execute: '.$sth->errstr();
$sth->finish();
$dbh->{'AutoCommit'} = 1;
$dbh->{AutoCommit} = 1;
} else {
Warning("Empty db update file at $file");
}
}
} # end sub patchDB_using_do
sub patchDB {
my $dbh = shift;
my $version = shift;
my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ );
my $command = 'mysql';
if ( defined($portOrSocket) ) {
@ -988,7 +992,7 @@ sub patchDB {
}
$command .= '/zm_update-'.$version.'.sql';
print( "Executing '$command'\n" ) if ( logDebugging() );
print("Executing '$command'\n") if logDebugging();
my $output = qx($command);
my $status = $? >> 8;
if ( $status || logDebugging() ) {
@ -999,28 +1003,27 @@ sub patchDB {
die("Command '$command' exited with status: $status\n");
}
print("\nDatabase successfully upgraded to version $version.\n");
}
} # end sub patchDB
sub migratePasswords {
print ("Migratings passwords, if any...\n");
my $sql = "select * from Users";
my $sql = 'SELECT * FROM `Users`';
my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die("Can't execute: ".$sth->errstr());
while( my $user = $sth->fetchrow_hashref() ) {
my $scheme = substr($user->{Password}, 0, 1);
if ($scheme eq "*") {
print ("-->".$user->{Username}. " password will be migrated\n");
if ($scheme eq '*') {
print ('-->'.$user->{Username}." password will be migrated\n");
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
my $settings = '$2a$10$'.$salt;
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);
my $new_pass_hash = "-ZM-".$pass_hash;
$sql = "UPDATE Users SET PASSWORD=? WHERE Username=?";
my $new_pass_hash = '-ZM-'.$pass_hash;
$sql = 'UPDATE Users SET `Password`=? WHERE `Username`=?';
my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($new_pass_hash, $user->{Username}) or die("Can't execute: ".$sth->errstr());
}
}
}
} # end sub migratePasswords
sub migratePaths {

View File

@ -374,18 +374,20 @@ void EventStream::processCommand(const CmdMsg *msg) {
}
break;
case CMD_SLOWFWD :
Debug(1, "Got SLOW FWD command");
paused = true;
replay_rate = ZM_RATE_BASE;
step = 1;
if ( (unsigned int)curr_frame_id < event_data->frame_count )
curr_frame_id += 1;
Debug(1, "Got SLOWFWD command new frame id %d", curr_frame_id);
break;
case CMD_SLOWREV :
Debug(1, "Got SLOW REV command");
paused = true;
replay_rate = ZM_RATE_BASE;
step = -1;
curr_frame_id -= 1;
if ( curr_frame_id < 1 ) curr_frame_id = 1;
Debug(1, "Got SLOWREV command new frame id %d", curr_frame_id);
break;
case CMD_FASTREV :
Debug(1, "Got FAST REV command");
@ -848,20 +850,15 @@ void EventStream::runStream() {
// commands may set send_frame to true
while ( checkCommandQueue() && !zm_terminate ) {
// The idea is to loop here processing all commands before proceeding.
Debug(1, "Have command queue");
}
Debug(2, "Done command queue");
// Update modified time of the socket .lock file so that we can tell which ones are stale.
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
touch(sock_path_lock);
last_comm_update = now;
}
} else {
Debug(2, "Not checking command queue");
}
// Get current frame data
FrameData *frame_data = &event_data->frames[curr_frame_id-1];
@ -1017,6 +1014,7 @@ void EventStream::runStream() {
curr_frame_id += step;
// Detects when we hit end of event and will load the next event or previous event
if ( !paused )
checkEventLoaded();
} // end while ! zm_terminate
#if HAVE_LIBAVCODEC

View File

@ -20,9 +20,6 @@
#ifndef ZM_EVENTSTREAM_H
#define ZM_EVENTSTREAM_H
#include <set>
#include <map>
#include "zm_image.h"
#include "zm_stream.h"
#include "zm_video.h"

View File

@ -103,15 +103,27 @@ void VncCamera::Terminate() {
int VncCamera::PrimeCapture() {
Info("Priming capture from %s", mHost.c_str());
if ( mRfb->si.framebufferWidth != width || mRfb->si.framebufferHeight != height ) {
Info("Expected screen resolution does not match with the provided resolution, using scaling");
Info("Expected screen resolution (%dx%d) does not match the provided resolution (%dx%d), using scaling",
width, height, mRfb->si.framebufferWidth, mRfb->si.framebufferHeight);
mScale = true;
sws = sws_getContext(
mRfb->si.framebufferWidth, mRfb->si.framebufferHeight, AV_PIX_FMT_RGBA,
width, height, AV_PIX_FMT_RGBA, SWS_BICUBIC,
NULL, NULL, NULL);
if ( !sws ) {
Error("Could not scale image");
return -1;
}
}
return 0;
}
int VncCamera::PreCapture() {
Debug(2, "PreCapture");
WaitForMessage(mRfb, 500);
Debug(2, "After Wait ");
rfbBool res = HandleRFBServerMessage(mRfb);
Debug(2, "After Handle ");
return res == TRUE ? 1 : -1 ;
}
@ -120,46 +132,39 @@ int VncCamera::Capture(Image &image) {
int srcLineSize[4];
int dstLineSize[4];
int dstSize;
if ( mScale ) {
sws = sws_getContext(mRfb->si.framebufferWidth, mRfb->si.framebufferHeight, AV_PIX_FMT_RGBA,
width, height, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, NULL, NULL);
if(!sws) {
Error("Could not scale image");
uint8_t* directbuffer;
/* Request a writeable buffer of the target image */
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == NULL ) {
Error("Failed requesting writeable buffer for the captured image.");
return -1;
}
if ( av_image_fill_arrays(dstbuf, dstLineSize, directbuffer, AV_PIX_FMT_RGBA,
width, height, 16) < 0) {
Error("Could not allocate dst image. Scaling failed");
return -1;
}
if ( av_image_fill_arrays(srcbuf, srcLineSize, mVncData.buffer, AV_PIX_FMT_RGBA,
mRfb->si.framebufferWidth, mRfb->si.framebufferHeight, 16) < 0) {
sws_freeContext(sws);
Error("Could not allocate source image. Scaling failed");
return -1;
}
if ((dstSize = av_image_alloc(dstbuf, dstLineSize, width, height,
AV_PIX_FMT_RGBA, 1)) < 0) {
av_freep(&srcbuf[0]);
sws_freeContext(sws);
Error("Could not allocate dest image. Scaling failed");
return -1;
}
sws_scale(sws, (const uint8_t* const*)srcbuf, srcLineSize, 0, mRfb->si.framebufferHeight,
dstbuf, dstLineSize);
}
else{
dstbuf[0] = mVncData.buffer;
}
} else {
image.Assign(width, height, colours, subpixelorder, mVncData.buffer, width * height * 4);
}
return 1;
}
int VncCamera::PostCapture() {
if(mScale) {
av_freep(&srcbuf[0]);
av_freep(&dstbuf[0]);
sws_freeContext(sws);
}
return 0;
}
@ -168,6 +173,15 @@ int VncCamera::CaptureAndRecord(Image &image, timeval recording, char* event_dir
}
int VncCamera::Close() {
#if HAVE_LIBSWSCALE
if ( mScale ) {
av_freep(&srcbuf[0]);
av_freep(&dstbuf[0]);
sws_freeContext(sws);
sws = NULL;
}
#endif
rfbClientCleanup(mRfb);
return 0;
}

View File

@ -745,12 +745,12 @@ void LocalCamera::Initialise() {
Debug(4,
" v4l2_data.fmt.type = %08x\n"
" v4l2_data.fmt.fmt.pix.width = %08x\n"
" v4l2_data.fmt.fmt.pix.height = %08x\n"
" v4l2_data.fmt.fmt.pix.width = %d\n"
" v4l2_data.fmt.fmt.pix.height = %d\n"
" v4l2_data.fmt.fmt.pix.pixelformat = %08x\n"
" v4l2_data.fmt.fmt.pix.field = %08x\n"
" v4l2_data.fmt.fmt.pix.bytesperline = %08x\n"
" v4l2_data.fmt.fmt.pix.sizeimage = %08x\n"
" v4l2_data.fmt.fmt.pix.bytesperline = %d\n"
" v4l2_data.fmt.fmt.pix.sizeimage = %d\n"
" v4l2_data.fmt.fmt.pix.colorspace = %08x\n"
" v4l2_data.fmt.fmt.pix.priv = %08x\n"
, v4l2_data.fmt.type
@ -788,12 +788,12 @@ void LocalCamera::Initialise() {
/* Note VIDIOC_S_FMT may change width and height. */
Debug(4,
" v4l2_data.fmt.type = %08x\n"
" v4l2_data.fmt.fmt.pix.width = %08x\n"
" v4l2_data.fmt.fmt.pix.height = %08x\n"
" v4l2_data.fmt.fmt.pix.width = %d\n"
" v4l2_data.fmt.fmt.pix.height = %d\n"
" v4l2_data.fmt.fmt.pix.pixelformat = %08x\n"
" v4l2_data.fmt.fmt.pix.field = %08x\n"
" v4l2_data.fmt.fmt.pix.bytesperline = %08x\n"
" v4l2_data.fmt.fmt.pix.sizeimage = %08x\n"
" v4l2_data.fmt.fmt.pix.bytesperline = %d\n"
" v4l2_data.fmt.fmt.pix.sizeimage = %d\n"
" v4l2_data.fmt.fmt.pix.colorspace = %08x\n"
" v4l2_data.fmt.fmt.pix.priv = %08x\n"
, v4l2_data.fmt.type

View File

@ -80,7 +80,7 @@ fi;
if [ "$DISTROS" == "" ]; then
if [ "$RELEASE" != "" ]; then
DISTROS="xenial,bionic,disco,eoan,trusty"
DISTROS="xenial,bionic,disco,eoan,focal,trusty"
else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi;

View File

@ -1 +1 @@
1.35.1
1.35.2

View File

@ -379,6 +379,8 @@ class MonitorsController extends AppController {
$args = '';
if ( $daemon == 'zmc' and $monitor['Type'] == 'Local' ) {
$args = '-d ' . $monitor['Device'];
} else if ( $daemon == 'zmcontrol.pl' ) {
$args = '--id '.$id;
} else {
$args = '-m ' . $id;
}

View File

@ -497,6 +497,10 @@ class Monitor extends ZM_Object {
if ( !count($options) ) {
if ( $command == 'quit' ) {
$options['command'] = 'quit';
} else if ( $command == 'start' ) {
$options['command'] = 'start';
} else if ( $command == 'stop' ) {
$options['command'] = 'stop';
} else {
Warning("No commands to send to zmcontrol from $command");
return false;
@ -531,7 +535,7 @@ class Monitor extends ZM_Object {
} else if ( $this->ServerId() ) {
$Server = $this->Server();
$url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmcontrol.json';
$url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$command.'/zmcontrol.pl.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
@ -548,11 +552,11 @@ class Monitor extends ZM_Object {
try {
$result = file_get_contents($url, false, $context);
if ( $result === FALSE ) { /* Handle error */
Error("Error restarting zma using $url");
Error("Error sending command using $url");
return false;
}
} catch ( Exception $e ) {
Error("Except $e thrown trying to restart zma");
Error("Exception $e thrown trying to send command to $url");
return false;
}
} else {

View File

@ -27,32 +27,21 @@ if ( ! canEdit('Groups') ) {
}
if ( $action == 'Save' ) {
$monitors = empty($_POST['newGroup']['MonitorIds']) ? '' : implode(',', $_POST['newGroup']['MonitorIds']);
$group_id = null;
if ( !empty($_POST['gid']) ) {
if ( !empty($_POST['gid']) )
$group_id = $_POST['gid'];
dbQuery(
'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?',
$group = new ZM\Group($group_id);
$group->save(
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
$group_id,
'Name'=> $_POST['newGroup']['Name'],
'ParentId'=>( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
)
);
dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id));
} else {
dbQuery(
'INSERT INTO Groups (Name,ParentId) VALUES (?,?)',
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
)
);
$group_id = dbInsertId();
}
dbQuery('DELETE FROM `Groups_Monitors` WHERE `GroupId`=?', array($group_id));
$group_id = $group->Id();
if ( $group_id ) {
foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) {
dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid));
dbQuery('INSERT INTO `Groups_Monitors` (`GroupId`,`MonitorId`) VALUES (?,?)', array($group_id, $mid));
}
}
$view = 'none';

View File

@ -92,6 +92,9 @@ if ( $action == 'monitor' ) {
if ( $monitor->Type() != 'WebSite' ) {
$monitor->zmaControl('stop');
$monitor->zmcControl('stop');
if ( $monitor->Controllable() ) {
$monitor->sendControlCommand('stop');
}
}
# These are used in updating zones
@ -264,8 +267,7 @@ if ( $action == 'monitor' ) {
$monitor->zmaControl('start');
if ( $monitor->Controllable() ) {
require_once('includes/control_functions.php');
$monitor->sendControlCommand('quit');
$monitor->sendControlCommand('start');
}
}
// really should thump zmwatch and maybe zmtrigger too.

View File

@ -36,7 +36,7 @@ if ( isset($_REQUEST['object']) ) {
}
$Layout->Positions($_REQUEST['Positions']);
$Layout->save();
session_start();
zm_session_start();
$_SESSION['zmMontageLayout'] = $Layout->Id();
setcookie('zmMontageLayout', $Layout->Id(), 1);
session_write_close();

View File

@ -210,7 +210,6 @@ function Monitor(monitorData) {
* @param {*} element - the event data passed by onchange callback
*/
function selectLayout(element) {
console.log(element);
layout = $j(element).val();
if ( layout_id = parseInt(layout) ) {
@ -222,7 +221,7 @@ function selectLayout(element) {
monitor_frame = $j('#monitorFrame'+monitor.id);
if ( !monitor_frame ) {
console.log("Error finding frame for " + monitor.id);
console.log('Error finding frame for ' + monitor.id);
continue;
}
@ -262,6 +261,10 @@ function selectLayout(element) {
if ( streamImg.nodeName == 'IMG' ) {
var src = streamImg.src;
src = src.replace(/width=[\.\d]+/i, 'width=0' );
if ( $j('#height').val() == 'auto' ) {
src = src.replace(/height=[\.\d]+/i, 'height=0' );
streamImg.style.height = 'auto';
}
if ( src != streamImg.src ) {
streamImg.src = '';
streamImg.src = src;

View File

@ -66,17 +66,19 @@ foreach ( $layouts as $l ) {
}
}
foreach ( $layouts as $l ) {
if ( $l->Name() != "Freeform" )
if ( $l->Name() != 'Freeform' )
$layoutsById[$l->Id()] = $l;
}
session_start();
zm_session_start();
$layout_id = '';
if ( isset($_COOKIE['zmMontageLayout']) ) {
$layout_id = $_SESSION['zmMontageLayout'] = $_COOKIE['zmMontageLayout'];
#} elseif ( isset($_SESSION['zmMontageLayout']) ) {
#$layout_id = $_SESSION['zmMontageLayout'];
ZM\Logger::Debug("Using layout $layout_id");
} elseif ( isset($_SESSION['zmMontageLayout']) ) {
$layout_id = $_SESSION['zmMontageLayout'];
ZM\Logger::Debug("Using layout $layout_id from session");
}
$options = array();
@ -85,6 +87,8 @@ $Positions = '';
if ( $layout_id and is_numeric($layout_id) and isset($layoutsById[$layout_id]) ) {
$Layout = $layoutsById[$layout_id];
$Positions = json_decode($Layout->Positions(), true);
} else {
ZM\Logger::Debug("Layout not found");
}
if ( $Layout and ( $Layout->Name() != 'Freeform' ) ) {
// Use layout instead of other options