diff --git a/.travis.yml b/.travis.yml index ddcd1e519..531fed4da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,9 +31,7 @@ install: env: global: - - DEB_BUILD_OPTIONS="parallel=4" - - DEBUILD_LINTIAN="no" - - SMPFLAGS="-j4" + - SMPFLAGS=-j4 matrix: - OS=el DIST=6 - OS=el DIST=6 ARCH=i386 DOCKER_REPO=knnniggett/packpack diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 10ce1ff0a..0226d777f 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -83,6 +83,10 @@ BuildRequires: libcurl-devel BuildRequires: libv4l-devel BuildRequires: ffmpeg-devel +# Required for mp4 container support +BuildRequires: libmp4v2-devel +BuildRequires: x264-devel + %{?with_nginx:Requires: nginx} %{?with_nginx:Requires: fcgiwrap} %{?with_nginx:Requires: php-fpm} diff --git a/distros/ubuntu1604/changelog b/distros/ubuntu1604/changelog index 3ea02e3ea..616f75178 100644 --- a/distros/ubuntu1604/changelog +++ b/distros/ubuntu1604/changelog @@ -1,7 +1,3 @@ -zoneminder (1.31.4-vivid1) vivid; urgency=medium - - * Release 1.31.4 - - -- Isaac Connor Thu, 21 Sep 2017 09:55:31 -0700 - - +zoneminder (1.31.39~20180223.27-stretch-1) unstable; urgency=low + * + -- Isaac Connor Fri, 23 Feb 2018 14:15:59 -0500 diff --git a/docs/api.rst b/docs/api.rst index 36467525a..706748880 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -98,15 +98,15 @@ This command will add a new http monitor. :: - curl -XPOST http://server/zm/api/monitors.json -d "Monitor[Name]=Cliff-Burton \ - &Monitor[Function]=Modect \ - &Monitor[Protocol]=http \ - &Monitor[Method]=simple \ - &Monitor[Host]=usr:pass@192.168.11.20 \ - &Monitor[Port]=80 \ - &Monitor[Path]=/mjpg/video.mjpg \ - &Monitor[Width]=704 \ - &Monitor[Height]=480 \ + curl -XPOST http://server/zm/api/monitors.json -d "Monitor[Name]=Cliff-Burton\ + &Monitor[Function]=Modect\ + &Monitor[Protocol]=http\ + &Monitor[Method]=simple\ + &Monitor[Host]=usr:pass@192.168.11.20\ + &Monitor[Port]=80\ + &Monitor[Path]=/mjpg/video.mjpg\ + &Monitor[Width]=704\ + &Monitor[Height]=480\ &Monitor[Colours]=4" Edit monitor 1 @@ -304,26 +304,26 @@ Create a Zone :: - curl -XPOST http://server/zm/api/zones.json -d "Zone[Name]=Jason-Newsted \ - &Zone[MonitorId]=3 \ - &Zone[Type]=Active \ - &Zone[Units]=Percent \ - &Zone[NumCoords]=4 \ - &Zone[Coords]=0,0 639,0 639,479 0,479 \ - &Zone[AlarmRGB]=16711680 \ - &Zone[CheckMethod]=Blobs \ - &Zone[MinPixelThreshold]=25 \ - &Zone[MaxPixelThreshold]= \ - &Zone[MinAlarmPixels]=9216 \ - &Zone[MaxAlarmPixels]= \ - &Zone[FilterX]=3 \ - &Zone[FilterY]=3 \ - &Zone[MinFilterPixels]=9216 \ - &Zone[MaxFilterPixels]=230400 \ - &Zone[MinBlobPixels]=6144 \ - &Zone[MaxBlobPixels]= \ - &Zone[MinBlobs]=1 \ - &Zone[MaxBlobs]= \ + curl -XPOST http://server/zm/api/zones.json -d "Zone[Name]=Jason-Newsted\ + &Zone[MonitorId]=3\ + &Zone[Type]=Active\ + &Zone[Units]=Percent\ + &Zone[NumCoords]=4\ + &Zone[Coords]=0,0 639,0 639,479 0,479\ + &Zone[AlarmRGB]=16711680\ + &Zone[CheckMethod]=Blobs\ + &Zone[MinPixelThreshold]=25\ + &Zone[MaxPixelThreshold]=\ + &Zone[MinAlarmPixels]=9216\ + &Zone[MaxAlarmPixels]=\ + &Zone[FilterX]=3\ + &Zone[FilterY]=3\ + &Zone[MinFilterPixels]=9216\ + &Zone[MaxFilterPixels]=230400\ + &Zone[MinBlobPixels]=6144\ + &Zone[MaxBlobPixels]=\ + &Zone[MinBlobs]=1\ + &Zone[MaxBlobs]=\ &Zone[OverloadFrames]=0" PTZ Control APIs diff --git a/docs/faq.rst b/docs/faq.rst index 87421ddb9..d6694b153 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -71,22 +71,22 @@ The 1.2 at the start is basically adding 20% on top of the calculation to accoun The math breakdown for 4 cameras running at 1280x960 capture, 50 frame buffer, 24 bit color space: :: - 1280*960 = 1,228,800 (bits) - 1,228,800 * 24 = 2,359,296,000 (bits) - 2,359,296,000 * 50 = 5,898,240,000 (bits) - 5,898,240,000 * 4 = 7,077,888,000 (bits) - 7,077,888,000 / 8 = 884,736,000 (bytes) - 884,736,000 / 1000 = 884,736 (Kilobytes) - 884,736 / 1000 = 864 (Megabytes) - 864 / 1000 = 0.9 (Gigabyte) + 1280*960 = 1,228,800 (bytes) + 1,228,800 * (3 bytes for 24 bit) = 3,686,400 (bytes) + 3,686,400 * 50 = 184,320,000 (bytes) + 184,320,000 * 4 = 737,280,000 (bytes) + 737,280,000 / 1024 = 720,000 (Kilobytes) + 720,000 / 1024 = 703.125 (Megabytes) + 703.125 / 1024 = 0.686 (Gigabytes) -Around 900MB of memory. +Around 700MB of memory. So if you have 2GB of memory, you should be all set. Right? **Not, really**: * This is just the base memory required to capture the streams. Remember ZM is always capturing streams irrespective of whether you are actually recording or not - to make sure its image ring buffer is there with pre images when an alarm kicks in. * You also need to account for other processes not related to ZM running in your box * You also need to account for other ZM processes - for example, I noticed the audit daemon takes up a good amount of memory when it runs, DB updates also take up memory + * If you are using H264 encoding, that buffers a lot of frames in memory as well. So a good rule of thumb is to make sure you have twice the memory as the calculation above (and if you are using the ZM server for other purposes, please factor in those memory requirements as well) @@ -128,15 +128,14 @@ So, for example: :: 384x288 capture resolution, that makes: 110 592 pixels - in 24 bit color that's x24 = 2 654 208 bits per frame - by 80 frames ring buffer x80 = 212 336 640 bits per camera - by 4 cameras x4 = 849 346 560 bits. - Plus 10% overhead = 934 281 216 bits - That's 116 785 152 bytes, and - = 114 048 kB, respectively 111.38 MB. - If my shared memory is set to 134 217 728, which is exactly 128MB, + in 24 bit color that's x 3 = 331,776 bytes per frame + by 80 frames ring buffer x80 = 26,542,080 bytes per camera + by 4 cameras x4 = 106,168,320 bytes. + Plus 10% overhead = 116,785,152 bytes + Thats 114,048 kB, respectively 111.38 MB. + If my shared memory is set to 134,217,728, which is exactly 128MB, that means I shouldn't have any problem. - (Note that 1 byte = 8 bits and 1kbyte = 1024bytes, 1MB = 1024 kB) + (Note that 1kbyte = 1024bytes, 1MB = 1024 kB) If for instance you were using 24bit 640x480 then this would come to about 92Mb if you are using the default buffer size of 100. If this is too large then you can either reduce the image or buffer sizes or increase the maximum amount of shared memory available. If you are using RedHat then you can get details on how to change these settings `here `__ diff --git a/docs/installationguide/packpack.rst b/docs/installationguide/packpack.rst index 8e52ad876..dd54ef4b2 100644 --- a/docs/installationguide/packpack.rst +++ b/docs/installationguide/packpack.rst @@ -1,4 +1,4 @@ -All Distros - A Simpler Way to Build ZoneMinder +All Distros - A Docker Way to Build ZoneMinder =============================================== .. contents:: @@ -27,6 +27,8 @@ Procedure - If the desired distro does not appear in either list, then unfortuantely you cannot use the procedure described here. +- If the desired distro architecture is arm, refer to `Appendix A - Enable Qemu On the Host`_ to enable qemu emulation on your amd64 host machine. + **Step 2:** Install Docker. You need to have a working installation of Docker so head over to the `Docker site `_ and get it working. Before continuing to the next step, verify you can run the Docker "Hello World" container as a normal user. To run a Docker container as a normal user, issue the following: @@ -99,7 +101,27 @@ For advanced users who really want to go out into uncharted waters, it is theore Building arm packages in this manner has not been tested by us, however. +Appendix A - Enable Qemu On the Host +------------------------------------ +If you intend to build ZoneMinder packages for arm on an amd64 host, then Debian users can following these steps to enable transparent Qemu emulation: +:: + sudo apt-get install binfmt-support qemu qemu-user-static +Verify arm emulation is enabled by issuing: + +:: + + sudo update-binfmts --enable qemu-arm + +You may get a message stating emulation for this processor is already enabled. + +More testing needs to be done for Redhat distros but it appears Fedora users can just run: + +:: + + sudo systemctl start systemd-binfmt + +TO-DO: Verify the details behind enabling qemu emulation on redhat distros. Pull requests are welcome. diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 1a0e74560..589045f14 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -88,12 +88,12 @@ bool StreamBase::checkCommandQueue() { //Error( "Partial message received, expected %d bytes, got %d", sizeof(msg), nbytes ); //} else { -Debug(2, "Message length is (%d)", nbytes ); + Debug(2, "Message length is (%d)", nbytes ); processCommand( &msg ); return( true ); } } else { - Error("sd is < 0"); + Warning("No sd in checkCommandQueue, comms not open?"); } return( false ); } diff --git a/utils/packpack/nolintian.patch b/utils/packpack/nolintian.patch new file mode 100644 index 000000000..89b93a4c3 --- /dev/null +++ b/utils/packpack/nolintian.patch @@ -0,0 +1,22 @@ +From 634281a4204467b9a3c8a1a5febcc8dd9828e0f6 Mon Sep 17 00:00:00 2001 +From: Andy Bauer +Date: Thu, 22 Feb 2018 08:53:50 -0600 +Subject: [PATCH] don't run lintian checks to speed up build + +--- + pack/deb.mk | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pack/deb.mk b/pack/deb.mk +index de4a0b7..bddf9df 100644 +--- a/packpack/pack/deb.mk ++++ b/packpack/pack/deb.mk +@@ -130,7 +130,7 @@ $(BUILDDIR)/$(DPKG_CHANGES): $(BUILDDIR)/$(PRODUCT)-$(VERSION)/debian \ + @echo "Building Debian packages" + @echo "-------------------------------------------------------------------" + cd $(BUILDDIR)/$(PRODUCT)-$(VERSION) && \ +- debuild --preserve-envvar CCACHE_DIR --prepend-path=/usr/lib/ccache \ ++ debuild --no-lintian --preserve-envvar CCACHE_DIR --prepend-path=/usr/lib/ccache \ + -Z$(TARBALL_COMPRESSOR) -uc -us $(SMPFLAGS) + rm -rf $(BUILDDIR)/$(PRODUCT)-$(VERSION)/ + @echo "------------------------------------------------------------------" diff --git a/utils/packpack/setarch.patch b/utils/packpack/setarch.patch new file mode 100644 index 000000000..4f196ec86 --- /dev/null +++ b/utils/packpack/setarch.patch @@ -0,0 +1,57 @@ +From 62a98b36fd62d328956503bc9427ae128bb811af Mon Sep 17 00:00:00 2001 +From: Andrew Bauer +Date: Mon, 26 Feb 2018 10:05:02 -0600 +Subject: [PATCH] fix 32bit rpm builds + +--- + pack/rpm.mk | 2 +- + packpack | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/packpack/pack/rpm.mk b/packpack/pack/rpm.mk +index c74e942..9a6b016 100644 +--- a/packpack/pack/rpm.mk ++++ b/packpack/pack/rpm.mk +@@ -124,7 +124,7 @@ package: $(BUILDDIR)/$(RPMSRC) + @echo "-------------------------------------------------------------------" + @echo "Building RPM packages" + @echo "-------------------------------------------------------------------" +- rpmbuild \ ++ setarch $(ARCH) rpmbuild \ + --define '_topdir $(BUILDDIR)' \ + --define '_sourcedir $(BUILDDIR)' \ + --define '_specdir $(BUILDDIR)' \ +diff --git a/packpack/packpack b/packpack/packpack +index 6f4c80f..c329399 100755 +--- a/packpack/packpack ++++ b/packpack/packpack +@@ -125,7 +125,7 @@ chmod a+x ${BUILDDIR}/userwrapper.sh + # + # Save defined configuration variables to ./env file + # +-env | grep -E "^PRODUCT=|^VERSION=|^RELEASE=|^ABBREV=|^TARBALL_|^CHANGELOG_|^CCACHE_|^PACKAGECLOUD_|^SMPFLAGS=|^OS=|^DIST=" \ ++env | grep -E "^PRODUCT=|^VERSION=|^RELEASE=|^ABBREV=|^TARBALL_|^CHANGELOG_|^CCACHE_|^PACKAGECLOUD_|^SMPFLAGS=|^OS=|^DIST=|^ARCH=" \ + > ${BUILDDIR}/env + + # +diff --git a/packpack/packpack b/packpack/packpack +index c329399..6ffaa9c 100755 +--- a/packpack/packpack ++++ b/packpack/packpack +@@ -19,11 +19,11 @@ DOCKER_REPO=${DOCKER_REPO:-packpack/packpack} + if [ -z "${ARCH}" ]; then + # Use uname -m instead of HOSTTYPE + case "$(uname -m)" in +- i*86) ARCH="i386" ;; +- arm*) ARCH="armhf" ;; +- x86_64) ARCH="x86_64"; ;; +- aarch64) ARCH="aarch64" ;; +- *) ARCH="${HOSTTYPE}" ;; ++ i*86) export ARCH="i386" ;; ++ arm*) export ARCH="armhf" ;; ++ x86_64) export ARCH="x86_64"; ;; ++ aarch64) export ARCH="aarch64" ;; ++ *) export ARCH="${HOSTTYPE}" ;; + esac + fi + diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 9d58a40a9..6e1cff817 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -108,6 +108,18 @@ commonprep () { patch -p1 < utils/packpack/packpack-rpm.patch fi + # Skip deb lintian checks to speed up the build + patch --dry-run --silent -f -p1 < utils/packpack/nolintian.patch + if [ $? -eq 0 ]; then + patch -p1 < utils/packpack/nolintian.patch + fi + + # fix 32bit rpm builds + patch --dry-run --silent -f -p1 < utils/packpack/setarch.patch + if [ $? -eq 0 ]; then + patch -p1 < utils/packpack/setarch.patch + fi + # The rpm specfile requires we download the tarball and manually move it into place # Might as well do this for Debian as well, rather than git submodule init CRUDVER="3.0.10" diff --git a/web/api/app/Config/routes.php b/web/api/app/Config/routes.php index 0f9343644..1eaaa9cd4 100644 --- a/web/api/app/Config/routes.php +++ b/web/api/app/Config/routes.php @@ -35,7 +35,8 @@ /* Add new API to retrieve camera controls - for PTZ */ /* refer to https://github.com/ZoneMinder/ZoneMinder/issues/799#issuecomment-105233112 */ - Router::mapResources('controls'); + Router::mapResources('controls'); + Router::mapResources('groups'); Router::parseExtensions(); /** diff --git a/web/api/app/Controller/GroupsController.php b/web/api/app/Controller/GroupsController.php index 525d500bf..7859f492e 100644 --- a/web/api/app/Controller/GroupsController.php +++ b/web/api/app/Controller/GroupsController.php @@ -4,15 +4,24 @@ App::uses('AppController', 'Controller'); * Groups Controller * * @property Group $Group + * @property PaginatorComponent $Paginator */ class GroupsController extends AppController { - /** * Components * * @var array */ - public $components = array('RequestHandler'); + public $components = array('Paginator', 'RequestHandler'); + + public function beforeFilter() { + parent::beforeFilter(); + $canView = $this->Session->Read('groupsPermission'); + if ( $canView == 'None' ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + } /** * index method @@ -55,6 +64,12 @@ class GroupsController extends AppController { */ public function add() { if ($this->request->is('post')) { + + if ($this->Session->Read('groupPermission') != 'Edit') { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + $this->Group->create(); if ($this->Group->save($this->request->data)) { return $this->flash(__('The group has been saved.'), array('action' => 'index')); @@ -75,16 +90,26 @@ class GroupsController extends AppController { if (!$this->Group->exists($id)) { throw new NotFoundException(__('Invalid group')); } - if ($this->request->is(array('post', 'put'))) { + if ( $this->request->is(array('post', 'put'))) { + if ( $this->Session->Read('groupPermission') != 'Edit' ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } if ($this->Group->save($this->request->data)) { return $this->flash(__('The group has been saved.'), array('action' => 'index')); + } else { + $message = 'Error'; } } else { $options = array('conditions' => array('Group.' . $this->Group->primaryKey => $id)); $this->request->data = $this->Group->find('first', $options); } $monitors = $this->Group->Monitor->find('list'); - $this->set(compact('monitors')); + $this->set(array( + 'message' => $message, + 'monitors'=> $monitors, + '_serialize' => array('message',) + )); } /** @@ -100,9 +125,15 @@ class GroupsController extends AppController { throw new NotFoundException(__('Invalid group')); } $this->request->allowMethod('post', 'delete'); + if ( $this->Session->Read('groupPermission') != 'Edit' ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + if ($this->Group->delete()) { return $this->flash(__('The group has been deleted.'), array('action' => 'index')); } else { return $this->flash(__('The group could not be deleted. Please, try again.'), array('action' => 'index')); } - }} + } // end function delete +} // end class GroupController diff --git a/web/api/app/Model/Group.php b/web/api/app/Model/Group.php index 595bc3b3a..1017df7b7 100644 --- a/web/api/app/Model/Group.php +++ b/web/api/app/Model/Group.php @@ -4,6 +4,7 @@ App::uses('AppModel', 'Model'); * Group Model * * @property Event $Event + * @property Zone $Zone */ class Group extends AppModel { @@ -21,6 +22,15 @@ class Group extends AppModel { */ public $primaryKey = 'Id'; +/** + * Display field + * + * @var string + */ + public $displayField = 'Name'; + + public $recursive = -1; + /** * Validation rules * @@ -30,6 +40,9 @@ class Group extends AppModel { 'Name' => array( 'notEmpty' => array( 'rule' => array('notEmpty'), + 'Id' => array( + 'numeric' => array( + 'rule' => array('numeric'), //'message' => 'Your custom message here', //'allowEmpty' => false, //'required' => false, @@ -39,7 +52,6 @@ class Group extends AppModel { ), ); - public $recursive = -1; //The Associations below have been created with all possible keys, those that are not needed can be removed /** diff --git a/web/api/app/View/Groups/json/edit.ctp b/web/api/app/View/Groups/json/edit.ctp new file mode 100644 index 000000000..ce19d17e8 --- /dev/null +++ b/web/api/app/View/Groups/json/edit.ctp @@ -0,0 +1,2 @@ +echo json_encode($message); +echo json_encode($group); diff --git a/web/api/app/View/Groups/json/index.ctp b/web/api/app/View/Groups/json/index.ctp new file mode 100644 index 000000000..330bf9262 --- /dev/null +++ b/web/api/app/View/Groups/json/index.ctp @@ -0,0 +1 @@ +echo json_encode($groups); diff --git a/web/api/app/View/Groups/json/view.ctp b/web/api/app/View/Groups/json/view.ctp new file mode 100644 index 000000000..04dc11392 --- /dev/null +++ b/web/api/app/View/Groups/json/view.ctp @@ -0,0 +1 @@ +echo json_encode($group); diff --git a/web/api/app/View/Groups/xml/edit.ctp b/web/api/app/View/Groups/xml/edit.ctp new file mode 100644 index 000000000..09fb8979a --- /dev/null +++ b/web/api/app/View/Groups/xml/edit.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $message)); +echo $xml->asXML(); diff --git a/web/api/app/View/Groups/xml/index.ctp b/web/api/app/View/Groups/xml/index.ctp new file mode 100644 index 000000000..8f45dfd14 --- /dev/null +++ b/web/api/app/View/Groups/xml/index.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $groups)); +echo $xml->asXML(); diff --git a/web/api/app/View/Groups/xml/view.ctp b/web/api/app/View/Groups/xml/view.ctp new file mode 100644 index 000000000..b54cad5ca --- /dev/null +++ b/web/api/app/View/Groups/xml/view.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $group)); +echo $xml->asXML(); diff --git a/web/includes/Control.php b/web/includes/Control.php new file mode 100644 index 000000000..de5f221ff --- /dev/null +++ b/web/includes/Control.php @@ -0,0 +1,230 @@ + 0, + 'CanMoveDiag' => 0, + 'CanMoveMap' => 0, + 'CanMoveAbs' => 0, + 'CanMoveRel' => 0, + 'CanMoveCon' => 0, + 'CanPan' => 0, + 'MinPanRange' => NULL, + 'MaxPanRange' => NULL, + 'MinPanStep' => NULL, + 'MaxPanStep' => NULL, + 'HasPanSpeed' => 0, + 'MinPanSpeed' => NULL, + 'MaxPanSpeed' => NULL, + 'HasTurboPan' => 0, + 'TurboPanSpeed' => NULL, + 'CanTilt' => 0, + 'MinTiltRange' => NULL, + 'MaxTiltRange' => NULL, + 'MinTiltStep' => NULL, + 'MaxTiltStep' => NULL, + 'HasTiltSpeed' => 0, + 'MinTiltSpeed' => NULL, + 'MaxTiltSpeed' => NULL, + 'HasTurboTilt' => 0, + 'TurboTiltSpeed' => NULL, + 'CanZoom' => 0, + 'CanZoomAbs' => 0, + 'CanZoomRel' => 0, + 'CanZoomCon' => 0, + 'MinZoomRange' => NULL, + 'MaxZoomRange' => NULL, + 'MinZoomStep' => NULL, + 'MaxZoomStep' => NULL, + 'HasZoomSpeed' => 0, + 'MinZoomSpeed' => NULL, + 'MaxZoomSpeed' => NULL, + 'CanFocus' => 0, + 'CanAutoFocus' => 0, + 'CanFocusAbs' => 0, + 'CanFocusRel' => 0, + 'CanFocusCon' => 0, + 'MinFocusRange' => NULL, + 'MaxFocusRange' => NULL, + 'MinFocusStep' => NULL, + 'MaxFocusStep' => NULL, + 'HasFocusSpeed' => 0, + 'MinFocusSpeed' => NULL, + 'MaxFocusSpeed' => NULL, + 'CanIris' => 0, + 'CanAutoIris' => 0, + 'CanIrisAbs' => 0, + 'CanIrisRel' => 0, + 'CanIrisCon' => 0, + 'MinIrisRange' => NULL, + 'MaxIrisRange' => NULL, + 'MinIrisStep' => NULL, + 'MaxIrisStep' => NULL, + 'HasIrisSpeed' => 0, + 'MinIrisSpeed' => NULL, + 'MaxIrisSpeed' => NULL, + 'CanGain' => 0, + 'CanAutoGain' => 0, + 'CanGainAbs' => 0, + 'CanGainRel' => 0, + 'CanGainCon' => 0, + 'MinGainRange' => NULL, + 'MaxGainRange' => NULL, + 'MinGainStep' => NULL, + 'MaxGainStep' => NULL, + 'HasGainSpeed' => 0, + 'MinGainSpeed' => NULL, + 'MaxGainSpeed' => NULL, + 'CanWhite' => 0, + 'CanAutoWhite' => 0, + 'CanWhiteAbs' => 0, + 'CanWhiteRel' => 0, + 'CanWhiteCon' => 0, + 'MinWhiteRange' => NULL, + 'MaxWhiteRange' => NULL, + 'MinWhiteStep' => NULL, + 'MaxWhiteStep' => NULL, + 'HasWhiteSpeed' => 0, + 'MinWhiteSpeed' => NULL, + 'MaxWhiteSpeed' => NULL, + 'HasPresets' => 0, + 'NumPresets' => 0, + 'HasHomePreset' => 0, + 'CanSetPresets' => 0, + 'Name' => 'New', + 'Type' => 'Local', + 'Protocol' => NULL + ); + + public function __construct( $IdOrRow = NULL ) { + if ( $IdOrRow ) { + $row = NULL; + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Control WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Control record for Id=" . $IdOrRow ); + } + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } else { + Error("Unknown argument passed to Control Constructor ($IdOrRow)"); + return; + } + + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + } else { + Error('No row for Control ' . $IdOrRow ); + } + } # end if isset($IdOrRow) + } // end function __construct + + public function __call($fn, array $args){ + if ( count($args) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists($fn, $this) ) { + return $this->{$fn}; + #array_unshift($args, $this); + #call_user_func_array( $this->{$fn}, $args); + } else { + if ( array_key_exists($fn, $this->control_fields) ) { + return $this->control_fields{$fn}; + } else if ( array_key_exists( $fn, $this->defaults ) ) { + return $this->defaults{$fn}; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Warning( "Unknown function call Control->$fn from $file:$line" ); + } + } + } + + public function set( $data ) { + foreach ($data as $k => $v) { + if ( is_array( $v ) ) { + # perhaps should turn into a comma-separated string + $this->{$k} = implode(',',$v); + } else if ( is_string( $v ) ) { + $this->{$k} = trim( $v ); + } else if ( is_integer( $v ) ) { + $this->{$k} = $v; + } else if ( is_bool( $v ) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } + } + public static function find_all( $parameters = null, $options = null ) { + $filters = array(); + $sql = 'SELECT * FROM Controls '; + $values = array(); + + if ( $parameters ) { + $fields = array(); + $sql .= 'WHERE '; + foreach ( $parameters as $field => $value ) { + if ( $value == null ) { + $fields[] = $field.' IS NULL'; + } else if ( is_array( $value ) ) { + $func = function(){return '?';}; + $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; + $values += $value; + + } else { + $fields[] = $field.'=?'; + $values[] = $value; + } + } + $sql .= implode(' AND ', $fields ); + } + if ( $options and isset($options['order']) ) { + $sql .= ' ORDER BY ' . $options['order']; + } + $result = dbQuery($sql, $values); + $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Control'); + foreach ( $results as $row => $obj ) { + $filters[] = $obj; + } + return $filters; + } + + public function save( $new_values = null ) { + + if ( $new_values ) { + foreach ( $new_values as $k=>$v ) { + $this->{$k} = $v; + } + } + // Set default values + foreach ( $this->defaults as $k=>$v ) { + if ( ( ! array_key_exists( $k, $this ) ) or ( $this->{$k} == '' ) ) { + $this->{$k} = $v; + } + } + + $fields = array_keys( $this->defaults ); + + if ( array_key_exists( 'Id', $this ) ) { + $sql = 'UPDATE Controls SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?'; + $values = array_map( function($field){return $this->{$field};}, $fields ); + $values[] = $this->{'Id'}; + dbQuery( $sql, $values ); + } else { + $sql = 'INSERT INTO Controls SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ''; + $values = array_map( function($field){return $this->{$field};}, $fields ); + dbQuery( $sql, $values ); + $this->{'Id'} = dbInsertId(); + } + } // end function save + +} // end class Control +?> diff --git a/web/includes/actions.php b/web/includes/actions.php index a5d615caf..ff1a22347 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -273,34 +273,12 @@ if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) { // Control capability actions, require control edit permissions if ( canEdit( 'Control' ) ) { if ( $action == 'controlcap' ) { - if ( !empty($_REQUEST['cid']) ) { - $control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) ); - } else { - $control = array(); - } + require_once( 'Control.php' ); + $Control = new Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null ); - // Define a field type for anything that's not simple text equivalent - $types = array( - // Empty - ); - - $columns = getTableColumns( 'Controls' ); - foreach ( $columns as $name=>$type ) { - if ( preg_match( '/^(Can|Has)/', $name ) ) { - $types[$name] = 'toggle'; - } - } - $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['cid']) ) { - dbQuery( 'update Controls set '.implode( ', ', $changes ).' where Id = ?', array($_REQUEST['cid']) ); - } else { - dbQuery( 'insert into Controls set '.implode( ', ', $changes ) ); - //$_REQUEST['cid'] = dbInsertId(); - } - $refreshParent = true; - } + //$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); + $Control->save( $_REQUEST['newControl'] ); + $refreshParent = true; $view = 'none'; } elseif ( $action == 'delete' ) { if ( isset($_REQUEST['markCids']) ) { diff --git a/web/includes/functions.php b/web/includes/functions.php index b2b08cca1..e073b0587 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -633,6 +633,7 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) { if ( !isset($types[$key]) ) $types[$key] = false; + switch( $types[$key] ) { case 'set' : { @@ -694,6 +695,16 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) { } break; } + case 'toggle' : + if ( (!isset($values[$key])) or $values[$key] != $value ) { + if ( empty($value) ) { + $changes[$key] = "$key = 0"; + } else { + $changes[$key] = "$key = 0"; + //$changes[$key] = $key . ' = '.dbEscape(trim($value)); + } + } + break; default : { if ( !isset($values[$key]) || ($values[$key] != $value) ) { diff --git a/web/skins/classic/views/js/log.js b/web/skins/classic/views/js/log.js index 565177dc9..2e5990af6 100644 --- a/web/skins/classic/views/js/log.js +++ b/web/skins/classic/views/js/log.js @@ -287,7 +287,7 @@ sortReverse: true warningPrefix: "", errorPrefix: "" }); - new Asset.css( "/css/spinner.css" ); + new Asset.css( "css/spinner.css" ); fetchNextLogs(); }