Skip to content

Commit 1604b54

Browse files
Merge remote-tracking branch 'upstream/master' into ai_server
2 parents b7ee0a8 + 62583ec commit 1604b54

File tree

12 files changed

+673
-148
lines changed

12 files changed

+673
-148
lines changed

db/zm_create.sql.in

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,39 @@ CREATE TABLE `Notifications` (
13531353
CONSTRAINT `Notifications_ibfk_1` FOREIGN KEY (`UserId`) REFERENCES `Users` (`Id`) ON DELETE CASCADE
13541354
) ENGINE=@ZM_MYSQL_ENGINE@;
13551355

1356+
--
1357+
-- Table structure for table `Menu_Items`
1358+
--
1359+
1360+
DROP TABLE IF EXISTS `Menu_Items`;
1361+
CREATE TABLE `Menu_Items` (
1362+
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
1363+
`MenuKey` varchar(32) NOT NULL,
1364+
`Enabled` tinyint(1) NOT NULL DEFAULT 1,
1365+
`Label` varchar(64) DEFAULT NULL,
1366+
`SortOrder` smallint NOT NULL DEFAULT 0,
1367+
`Icon` varchar(128) DEFAULT NULL,
1368+
`IconType` enum('material','fontawesome','image','none') NOT NULL DEFAULT 'material',
1369+
PRIMARY KEY (`Id`),
1370+
UNIQUE KEY `Menu_Items_MenuKey_idx` (`MenuKey`)
1371+
) ENGINE=@ZM_MYSQL_ENGINE@;
1372+
1373+
INSERT INTO `Menu_Items` (`MenuKey`, `Enabled`, `SortOrder`) VALUES
1374+
('Console', 1, 10),
1375+
('Montage', 1, 20),
1376+
('MontageReview', 1, 30),
1377+
('Events', 1, 40),
1378+
('Options', 1, 50),
1379+
('Log', 1, 60),
1380+
('Devices', 1, 70),
1381+
('IntelGpu', 1, 80),
1382+
('Groups', 1, 90),
1383+
('Filters', 1, 100),
1384+
('Snapshots', 1, 110),
1385+
('Reports', 1, 120),
1386+
('ReportEventAudit', 1, 130),
1387+
('Map', 1, 140);
1388+
13561389
source @PKGDATADIR@/db/Object_Types.sql
13571390
-- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts.
13581391
source @PKGDATADIR@/db/triggers.sql

db/zm_update-1.39.3.sql

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
--
2+
-- Add Menu_Items table for customizable navbar/sidebar menu
3+
--
4+
5+
SET @s = (SELECT IF(
6+
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE()
7+
AND table_name = 'Menu_Items'
8+
) > 0,
9+
"SELECT 'Table Menu_Items already exists'",
10+
"CREATE TABLE `Menu_Items` (
11+
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
12+
`MenuKey` varchar(32) NOT NULL,
13+
`Enabled` tinyint(1) NOT NULL DEFAULT 1,
14+
`Label` varchar(64) DEFAULT NULL,
15+
`SortOrder` smallint NOT NULL DEFAULT 0,
16+
`Icon` varchar(128) DEFAULT NULL,
17+
`IconType` enum('material','fontawesome','image','none') NOT NULL DEFAULT 'material',
18+
PRIMARY KEY (`Id`),
19+
UNIQUE KEY `Menu_Items_MenuKey_idx` (`MenuKey`)
20+
) ENGINE=InnoDB"
21+
));
22+
23+
PREPARE stmt FROM @s;
24+
EXECUTE stmt;
25+
DEALLOCATE PREPARE stmt;
26+
27+
--
28+
-- Seed default menu items if table is empty
29+
--
30+
31+
SET @s = (SELECT IF(
32+
(SELECT COUNT(*) FROM `Menu_Items`) > 0,
33+
"SELECT 'Menu_Items already has data'",
34+
"INSERT INTO `Menu_Items` (`MenuKey`, `Enabled`, `SortOrder`) VALUES
35+
('Console', 1, 10),
36+
('Montage', 1, 20),
37+
('MontageReview', 1, 30),
38+
('Events', 1, 40),
39+
('Options', 1, 50),
40+
('Log', 1, 60),
41+
('Devices', 1, 70),
42+
('IntelGpu', 1, 80),
43+
('Groups', 1, 90),
44+
('Filters', 1, 100),
45+
('Snapshots', 1, 110),
46+
('Reports', 1, 120),
47+
('ReportEventAudit', 1, 130),
48+
('Map', 1, 140)"
49+
));
50+
51+
PREPARE stmt FROM @s;
52+
EXECUTE stmt;
53+
DEALLOCATE PREPARE stmt;
54+
55+
--
56+
-- Add Icon and IconType columns if they don't exist
57+
--
58+
59+
SET @s = (SELECT IF(
60+
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
61+
AND table_name = 'Menu_Items' AND column_name = 'Icon'
62+
) > 0,
63+
"SELECT 'Column Icon already exists'",
64+
"ALTER TABLE `Menu_Items` ADD `Icon` varchar(128) DEFAULT NULL AFTER `SortOrder`"
65+
));
66+
67+
PREPARE stmt FROM @s;
68+
EXECUTE stmt;
69+
DEALLOCATE PREPARE stmt;
70+
71+
SET @s = (SELECT IF(
72+
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
73+
AND table_name = 'Menu_Items' AND column_name = 'IconType'
74+
) > 0,
75+
"SELECT 'Column IconType already exists'",
76+
"ALTER TABLE `Menu_Items` ADD `IconType` enum('material','fontawesome','image','none') NOT NULL DEFAULT 'material' AFTER `Icon`"
77+
));
78+
79+
PREPARE stmt FROM @s;
80+
EXECUTE stmt;
81+
DEALLOCATE PREPARE stmt;

distros/redhat/zoneminder.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
%global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}}
2222

2323
Name: zoneminder
24-
Version: 1.39.0
24+
Version: 1.39.3
2525
Release: 1%{?dist}
2626
Summary: A camera monitoring and analysis tool
2727
Group: System Environment/Daemons

version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.39.1
1+
1.39.3

web/includes/MenuItem.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
namespace ZM;
3+
require_once('database.php');
4+
require_once('Object.php');
5+
6+
class MenuItem extends ZM_Object {
7+
protected static $table = 'Menu_Items';
8+
9+
protected $defaults = array(
10+
'Id' => null,
11+
'MenuKey' => '',
12+
'Enabled' => 1,
13+
'Label' => null,
14+
'SortOrder' => 0,
15+
'Icon' => null,
16+
'IconType' => 'material',
17+
);
18+
19+
// Default material icons for each menu key
20+
public static $defaultIcons = array(
21+
'Console' => 'dashboard',
22+
'Montage' => 'live_tv',
23+
'MontageReview' => 'movie',
24+
'Events' => 'event',
25+
'Options' => 'settings',
26+
'Log' => 'notification_important',
27+
'Devices' => 'devices_other',
28+
'IntelGpu' => 'memory',
29+
'Groups' => 'group',
30+
'Filters' => 'filter_alt',
31+
'Snapshots' => 'preview',
32+
'Reports' => 'report',
33+
'ReportEventAudit' => 'shield',
34+
'Map' => 'language',
35+
);
36+
37+
public function effectiveIcon() {
38+
if ($this->{'Icon'} !== null && $this->{'Icon'} !== '') {
39+
return $this->{'Icon'};
40+
}
41+
return isset(self::$defaultIcons[$this->{'MenuKey'}]) ? self::$defaultIcons[$this->{'MenuKey'}] : 'menu';
42+
}
43+
44+
public function effectiveIconType() {
45+
if ($this->{'IconType'} == 'none') {
46+
return 'none';
47+
}
48+
if ($this->{'Icon'} !== null && $this->{'Icon'} !== '') {
49+
return $this->{'IconType'};
50+
}
51+
return 'material';
52+
}
53+
54+
public static function find($parameters = array(), $options = array()) {
55+
return ZM_Object::_find(self::class, $parameters, $options);
56+
}
57+
58+
public static function find_one($parameters = array(), $options = array()) {
59+
return ZM_Object::_find_one(self::class, $parameters, $options);
60+
}
61+
62+
public function displayLabel() {
63+
if ($this->{'Label'} !== null && $this->{'Label'} !== '') {
64+
return $this->{'Label'};
65+
}
66+
return translate($this->{'MenuKey'});
67+
}
68+
}

web/includes/actions/options.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,5 +189,95 @@
189189
}
190190
}
191191
}
192+
} else if ($action == 'menuitems') {
193+
if (!canEdit('System')) {
194+
ZM\Warning('Need System permission to edit menu items');
195+
} else if (isset($_REQUEST['items'])) {
196+
require_once('includes/MenuItem.php');
197+
$allItems = ZM\MenuItem::find();
198+
foreach ($allItems as $item) {
199+
$id = $item->Id();
200+
$enabled = isset($_REQUEST['items'][$id]['Enabled']) ? 1 : 0;
201+
$label = isset($_REQUEST['items'][$id]['Label']) ? trim($_REQUEST['items'][$id]['Label']) : null;
202+
$sortOrder = isset($_REQUEST['items'][$id]['SortOrder']) ? intval($_REQUEST['items'][$id]['SortOrder']) : $item->SortOrder();
203+
if ($label === '') $label = null;
204+
205+
$iconType = isset($_REQUEST['items'][$id]['IconType']) ? $_REQUEST['items'][$id]['IconType'] : $item->IconType();
206+
if (!in_array($iconType, ['material', 'fontawesome', 'image', 'none'])) $iconType = 'material';
207+
$icon = isset($_REQUEST['items'][$id]['Icon']) ? trim($_REQUEST['items'][$id]['Icon']) : $item->Icon();
208+
if ($icon === '') $icon = null;
209+
210+
// Handle image upload
211+
if (isset($_FILES['items']['name'][$id]['IconFile'])
212+
&& $_FILES['items']['error'][$id]['IconFile'] == UPLOAD_ERR_OK) {
213+
$uploadDir = ZM_PATH_WEB.'/graphics/menu/';
214+
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
215+
216+
$tmpName = $_FILES['items']['tmp_name'][$id]['IconFile'];
217+
$origName = basename($_FILES['items']['name'][$id]['IconFile']);
218+
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
219+
$allowedExts = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico'];
220+
if (in_array($ext, $allowedExts)) {
221+
// Validate it's actually an image (except SVG/ICO)
222+
if ($ext == 'svg' || $ext == 'ico' || getimagesize($tmpName) !== false) {
223+
$safeName = 'menu_'.$id.'_'.time().'.'.$ext;
224+
$destPath = $uploadDir.$safeName;
225+
if (move_uploaded_file($tmpName, $destPath)) {
226+
// Remove old uploaded icon if it exists
227+
if ($item->IconType() == 'image' && $item->Icon() && file_exists(ZM_PATH_WEB.'/'.$item->Icon())) {
228+
unlink(ZM_PATH_WEB.'/'.$item->Icon());
229+
}
230+
$icon = 'graphics/menu/'.$safeName;
231+
$iconType = 'image';
232+
}
233+
}
234+
}
235+
}
236+
237+
// If user cleared icon, reset to default
238+
if ($iconType != 'image' && ($icon === null || $icon === '')) {
239+
$icon = null;
240+
}
241+
242+
$item->save([
243+
'Enabled' => $enabled,
244+
'Label' => $label,
245+
'SortOrder' => $sortOrder,
246+
'Icon' => $icon,
247+
'IconType' => $iconType,
248+
]);
249+
}
250+
}
251+
$redirect = '?view=options&tab=menu';
252+
} else if ($action == 'resetmenu') {
253+
if (!canEdit('System')) {
254+
ZM\Warning('Need System permission to reset menu items');
255+
} else {
256+
// Clean up any uploaded icon files
257+
require_once('includes/MenuItem.php');
258+
$oldItems = ZM\MenuItem::find();
259+
foreach ($oldItems as $item) {
260+
if ($item->IconType() == 'image' && $item->Icon() && file_exists(ZM_PATH_WEB.'/'.$item->Icon())) {
261+
unlink(ZM_PATH_WEB.'/'.$item->Icon());
262+
}
263+
}
264+
dbQuery('DELETE FROM Menu_Items');
265+
dbQuery("INSERT INTO `Menu_Items` (`MenuKey`, `Enabled`, `SortOrder`) VALUES
266+
('Console', 1, 10),
267+
('Montage', 1, 20),
268+
('MontageReview', 1, 30),
269+
('Events', 1, 40),
270+
('Options', 1, 50),
271+
('Log', 1, 60),
272+
('Devices', 1, 70),
273+
('IntelGpu', 1, 80),
274+
('Groups', 1, 90),
275+
('Filters', 1, 100),
276+
('Snapshots', 1, 110),
277+
('Reports', 1, 120),
278+
('ReportEventAudit', 1, 130),
279+
('Map', 1, 140)");
280+
}
281+
$redirect = '?view=options&tab=menu';
192282
} // end if object vs action
193283
?>

0 commit comments

Comments
 (0)