diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 5f80fe591..4e33ce4e0 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas: -- The ZoneMinder IRC channel - irc.freenode.net #zoneminder +- The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/) - The [ZoneMinder Forum](https://forums.zoneminder.com/) **Do not post feature or enhancement requests, general discussions or support questions here.** diff --git a/CMakeLists.txt b/CMakeLists.txt index c847e06a3..6d070c6d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -802,6 +802,24 @@ if(WITH_SYSTEMD) endif(NOT POLKIT_FOUND) endif(WITH_SYSTEMD) +# Find the path to an arp compatible executable +if(ZM_PATH_ARP STREQUAL "") + find_program(ARP_EXECUTABLE arp) + if(ARP_EXECUTABLE) + set(ZM_PATH_ARP "${ARP_EXECUTABLE}") + mark_as_advanced(ARP_EXECUTABLE) + else(ARP_EXECUTABLE) + find_program(ARP_EXECUTABLE ip) + if(ARP_EXECUTABLE) + set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh") + mark_as_advanced(ARP_EXECUTABLE) + endif(ARP_EXECUTABLE) + endif(ARP_EXECUTABLE) + if(ARP_EXECUTABLE-NOTFOUND) + message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function." ) + endif(ARP_EXECUTABLE-NOTFOUND) +endif(ZM_PATH_ARP STREQUAL "") + # Some variables that zm expects set(ZM_PID "${ZM_RUNDIR}/zm.pid") set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf") diff --git a/README.md b/README.md index 3bdf1ffd1..8f607c0c0 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ZoneMinder [![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) +[![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://zoneminder-chat.herokuapp.com) + All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org ## Overview diff --git a/conf.d/01-system-paths.conf.in b/conf.d/01-system-paths.conf.in index 9f45abdbd..e97433ba2 100644 --- a/conf.d/01-system-paths.conf.in +++ b/conf.d/01-system-paths.conf.in @@ -50,4 +50,4 @@ ZM_PATH_SWAP=@ZM_TMPDIR@ # Full path to optional arp binary # ZoneMinder will find the arp binary automatically on most systems -ZM_PATH_ARP=@ZM_PATH_ARP@ +ZM_PATH_ARP="@ZM_PATH_ARP@" diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index d0482c479..3448a962f 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -517,6 +517,7 @@ CREATE TABLE `Monitors` ( `ArchivedEvents` int(10) default NULL, `ArchivedEventDiskSpace` bigint default NULL, `ZoneCount` TINYINT NOT NULL DEFAULT 0, + `Refresh` int(10) unsigned default NULL, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -581,7 +582,7 @@ DROP TABLE IF EXISTS `Stats`; CREATE TABLE `Stats` ( `MonitorId` int(10) unsigned NOT NULL default '0', `ZoneId` int(10) unsigned NOT NULL default '0', - `EventId` int(10) unsigned NOT NULL default '0', + `EventId` BIGINT UNSIGNED NOT NULL, `FrameId` int(10) unsigned NOT NULL default '0', `PixelDiff` tinyint(3) unsigned NOT NULL default '0', `AlarmPixels` int(10) unsigned NOT NULL default '0', @@ -778,7 +779,9 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,1 INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); - +INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -- -- Add some monitor preset values -- @@ -820,6 +823,7 @@ INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,' INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video','0',255,'http','simple','','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100); INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); diff --git a/db/zm_update-1.31.43.sql b/db/zm_update-1.31.43.sql index d8d6eaefd..05a92288e 100644 --- a/db/zm_update-1.31.43.sql +++ b/db/zm_update-1.31.43.sql @@ -16,7 +16,7 @@ SET @s = (SELECT IF( AND column_name = 'Refresh' ) > 0, "SELECT 'Column Refresh exists in Monitors'", -"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL" +"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`" )); PREPARE stmt FROM @s; diff --git a/db/zm_update-1.31.45.sql b/db/zm_update-1.31.45.sql new file mode 100644 index 000000000..c3be6c4cd --- /dev/null +++ b/db/zm_update-1.31.45.sql @@ -0,0 +1,23 @@ +-- +-- This updates a 1.31.44 database to 1.31.45 +-- +-- Add WebSite enum to Monitor.Type +-- Add Refresh column to Monitors table + +-- This is the same as the update to 1.31.43, but due to Refresh not being added to zm_create.sql.in we need to have it +-- again in order to fix people who did a fresh install from 1.31.43 or 1.31.44. +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'Refresh' + ) > 0, +"SELECT 'Column Refresh exists in Monitors'", +"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.31.46.sql b/db/zm_update-1.31.46.sql new file mode 100644 index 000000000..8c9767e75 --- /dev/null +++ b/db/zm_update-1.31.46.sql @@ -0,0 +1,2 @@ +ALTER TABLE Stats MODIFY COLUMN EventId bigint unsigned NOT NULL; + diff --git a/db/zm_update-1.31.47.sql b/db/zm_update-1.31.47.sql new file mode 100644 index 000000000..60cad9eb1 --- /dev/null +++ b/db/zm_update-1.31.47.sql @@ -0,0 +1,15 @@ +-- +-- Add Prefix column to Storage +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Servers' + AND column_name = 'PathPrefix' + ) > 0, +"SELECT 'Column PathPrefix already exists in Servers'", +"ALTER TABLE Servers ADD `PathPrefix` TEXT AFTER `Hostname`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/distros/debian/control b/distros/debian/control index 9de030e87..29be56e25 100644 --- a/distros/debian/control +++ b/distros/debian/control @@ -13,6 +13,7 @@ Build-Depends: debhelper (>= 9), cmake , libv4l-dev (>= 0.8.3) , libbz2-dev , ffmpeg | libav-tools + , net-tools , libnetpbm10-dev , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev diff --git a/distros/opensuse/CMakeLists.txt b/distros/opensuse/CMakeLists.txt index 10654f26d..6ce1a999f 100644 --- a/distros/opensuse/CMakeLists.txt +++ b/distros/opensuse/CMakeLists.txt @@ -8,16 +8,6 @@ SET(zmgid_final www) SET(webroot /srv/www/htdocs) SET(zm_webdir ${webroot}/zoneminder) -# Download jscalendar & move files into position -file(DOWNLOAD http://downloads.sourceforge.net/jscalendar/jscalendar-1.0.zip ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar-1.0.zip STATUS download_jsc) -if(download_jsc EQUAL 0) -message(STATUS "Jscalander successfully downloaded. Installing...") -execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ERROR_VARIABLE unzip_jsc) -message(STATUS "Status of jscalender script was: ${unzip_jsc}") -else(download_jsc EQUAL 0) -message(STATUS "Unable to download optional jscalander. Skipping...") -endif(download_jsc EQUAL 0) - # Create several empty folders file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) @@ -45,7 +35,3 @@ install(FILES zoneminder.tmpfiles DESTINATION /etc/tmpfiles.d RENAME zoneminder. install(FILES redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) -# Install jscalendar -if(unzip_jsc STREQUAL "") -install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar) -endif(unzip_jsc STREQUAL "") diff --git a/distros/opensuse/jscalendar.sh b/distros/opensuse/jscalendar.sh deleted file mode 100755 index 80acaafec..000000000 --- a/distros/opensuse/jscalendar.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -unzip -o jscalendar-1.0.zip -mkdir -v jscalendar-doc -cd jscalendar-1.0 -mv -v *html *php doc/* README ../jscalendar-doc -rmdir -v doc diff --git a/distros/opensuse/zoneminder.cmake.OS13.spec b/distros/opensuse/zoneminder.cmake.OS13.spec index e1a55d3cb..e1ed14325 100644 --- a/distros/opensuse/zoneminder.cmake.OS13.spec +++ b/distros/opensuse/zoneminder.cmake.OS13.spec @@ -16,7 +16,6 @@ Version: 1.27.0 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ # Mootools is under the MIT license: http://mootools.net/ License: GPLv2+ and LGPLv2+ and MIT URL: http://www.zoneminder.com/ @@ -141,7 +140,7 @@ fi %files %defattr(-,root,root,-) -%doc AUTHORS COPYING README.md distros/opensuse/README.OpenSuse distros/opensuse/jscalendar-doc +%doc AUTHORS COPYING README.md distros/opensuse/README.OpenSuse %docdir /opt/zoneminder/share/man %config %attr(640,root,%{zmgid_final}) /etc/zm.conf %config(noreplace) %attr(644,root,root) /etc/apache2/conf.d/zoneminder.conf diff --git a/distros/redhat/CMakeLists.txt b/distros/redhat/CMakeLists.txt index e42c5db61..683a475b1 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -27,18 +27,6 @@ else(ZM_WEB_USER STREQUAL "nginx") configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY) endif(ZM_WEB_USER STREQUAL "nginx") -# Unpack jscalendar & move files into position -message(STATUS "Unpacking and Installing jscalendar...") -execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/misc/jscalendar.sh - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ERROR_VARIABLE unzip_jsc - ) -if("${unzip_jsc}" STREQUAL "") - message(STATUS "jscalendar successfully installed.") -else("${unzip_jsc}" STREQUAL "") - message(FATAL_ERROR "\nAn error occurred while jscalendar was being processed:\n${unzip_jsc}") -endif("${unzip_jsc}" STREQUAL "") - # Create several empty folders file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) @@ -58,7 +46,6 @@ install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DE # Install auxiliary files install(FILES misc/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar) # Install zoneminder service files install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) diff --git a/distros/redhat/misc/jscalendar-1.0.zip b/distros/redhat/misc/jscalendar-1.0.zip deleted file mode 100644 index f33dc072c..000000000 Binary files a/distros/redhat/misc/jscalendar-1.0.zip and /dev/null differ diff --git a/distros/redhat/misc/jscalendar.sh b/distros/redhat/misc/jscalendar.sh deleted file mode 100755 index 068e47bf9..000000000 --- a/distros/redhat/misc/jscalendar.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -unzip -o misc/jscalendar-1.0.zip -mkdir -v jscalendar-doc -cd jscalendar-1.0 -mv -v *html *php doc/* README ../jscalendar-doc -rmdir -v doc diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 644bd5ee7..042abb40e 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -26,11 +26,10 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.31.44 +Version: 1.31.45 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ # Mootools is inder the MIT license: http://mootools.net/ # CakePHP is under the MIT license: https://github.com/cakephp/cakephp # Crud is under the MIT license: https://github.com/FriendsOfCake/crud @@ -53,6 +52,7 @@ BuildRequires: pcre-devel BuildRequires: libjpeg-turbo-devel BuildRequires: findutils BuildRequires: coreutils +BuildRequires: net-tools BuildRequires: perl BuildRequires: perl-generators BuildRequires: perl(Archive::Tar) @@ -252,7 +252,7 @@ EOF %files %license COPYING -%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc +%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https # We want these two folders to have "normal" read permission # compared to the folder contents diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 0b934b7d2..3054a2d55 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -11,6 +11,8 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libavformat-dev (>= 6:10~) ,libavutil-dev (>= 6:10~) ,libswscale-dev (>= 6:10~) + ,ffmpeg | libav-tools + ,net-tools ,libbz2-dev ,libgcrypt-dev | libgcrypt11-dev ,libcurl4-gnutls-dev diff --git a/docs/api.rst b/docs/api.rst index 706748880..a5bdd35f3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -13,8 +13,8 @@ The API is built in CakePHP and lives under the ``/api`` directory. It provides a RESTful service and supports CRUD (create, retrieve, update, delete) functions for Monitors, Events, Frames, Zones and Config. -Security -^^^^^^^^^ +Login, Logout & API Security +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The APIs tie into ZoneMinder's existing security model. This means if you have OPT_AUTH enabled, you need to log into ZoneMinder using the same browser you plan to use the APIs from. If you are developing an app that relies on the API, you need @@ -23,11 +23,32 @@ to do a POST login from the app into ZoneMinder before you can access the API. Then, you need to re-use the authentication information of the login (returned as cookie states) with subsequent APIs for the authentication information to flow through to the APIs. -This means if you plan to use cuRL to experiment with these APIs, you first need to do +This means if you plan to use cuRL to experiment with these APIs, you first need to login: + +**Login process for ZoneMinder v1.32.0 and above** :: - curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php + curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/login.json + +Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this: + +:: + + curl -b cookies.txt http://yourzmip/zm/api/logout.json + + +**Login process for older versions of ZoneMinder** + +:: + + curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php + +The equivalent logout process for older versions of ZoneMinder is: + +:: + + curl -XPOST -d "username=XXXX&password=YYYY&action=logout&view=console" -b cookies.txt http://yourzmip/zm/index.php replacing *XXXX* and *YYYY* with your username and password, respectively. @@ -36,25 +57,55 @@ and the command will silently fail. What the "-c cookies.txt" does is store a cookie state reflecting that you have logged into ZM. You now need -to apply that cookie state to all subsequent APIs. You do that by using a '-b cookies.txt' to subsequent APIs if you are +to apply that cookie state to all subsequent APIs. You do that by using a '-b cookies.txt' to subsequent APIs if you are using CuRL like so: :: - curl -b cookies.txt http://yourzmip/zm/api/monitors.json + curl -b cookies.txt http://yourzmip/zm/api/monitors.json + +This would return a list of monitors and pass on the authentication information to the ZM API layer. + +A deeper dive into the login process +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As you might have seen above, there are two ways to login, one that uses the `login.json` API and the other that logs in using the ZM portal. If you are running ZoneMinder 1.32.0 and above, it is *strongly* recommended you use the `login.json` approach. The "old" approach will still work but is not as powerful as the API based login. Here are the reasons why: + + * The "old" approach basically uses the same login webpage (`index.php`) that a user would log into when viewing the ZM console. This is not really using an API and more importantly, if you have additional components like reCAPTCHA enabled, this will not work. Using the API approach is much cleaner and will work irrespective of reCAPTCHA + + * The new login API returns important information that you can use to stream videos as well, right after login. Consider for example, a typical response to the login API (`/login.json`): + +:: + + { + "credentials": "auth=f5b9cf48693fe8552503c8ABCD5", + "append_password": 0, + "version": "1.31.44", + "apiversion": "1.0" + } + +In this example I have `OPT_AUTH` enabled in ZoneMinder and it returns my credential key. You can then use this key to stream images like so: + +:: + + + +Where `authval` is the credentials returned to start streaming videos. + +The `append_password` field will contain 1 when it is necessary for you to append your ZM password. This is the case when you set `AUTH_RELAY` in ZM options to "plain", for example. In that case, the `credentials` field may contain something like `&user=admin&pass=` and you have to add your password to that string. + + +.. NOTE:: It is recommended you invoke the `login` API once every 60 minutes to make sure the session stays alive. The same is true if you use the old login method too. -This would return a list of monitors and pass on the authentication information to the ZM API layer. -So remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using -CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests -in your app. Examples (please read security notice above) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You will see each URL ending in either ``.xml`` or ``.json``. This is the -format of the request, and it determines the format that any data returned to -you will be in. I like json, however you can use xml if you'd like. +Please remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using +CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests +in your app. + (In all examples, replace 'server' with IP or hostname & port where ZoneMinder is running) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 7a86b51a8..d48747703 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -149,7 +149,7 @@ BEGIN { foreach my $str ( <$CONFIG> ) { next if ( $str =~ /^\s*$/ ); next if ( $str =~ /^\s*#/ ); - my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; + my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/; if ( ! $name ) { print( STDERR "Warning, bad line in $config_file: $str\n" ); next; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/DericamP2.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DericamP2.pm new file mode 100644 index 000000000..90221c770 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DericamP2.pm @@ -0,0 +1,475 @@ +# ========================================================================== +# +# ZoneMinder Dericam P2 Control Protocol Module +# Copyright (C) Roman Dissertori +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module contains the implementation of the Dericam P2 device control protocol +# +package ZoneMinder::Control::DericamP2; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our %CamParams = (); + +# ========================================================================== +# +# Dericam P2 Control Protocol +# +# On ControlAddress use the format : +# USERNAME:PASSWORD@ADDRESS:PORT +# eg : admin:@10.1.2.1:80 +# zoneminder:zonepass@10.0.100.1:40000 +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +use Time::HiRes qw( usleep ); + +sub open +{ + my $self = shift; + + $self->loadMonitor(); + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + + $self->{state} = 'open'; +} + +sub printMsg +{ + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); + + Debug( $msg."[".$msg_len."]" ); +} + +sub sendCmd +{ + my $self = shift; + my $cmd = shift; + my $result = undef; + printMsg( $cmd, "Tx" ); + + my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); + Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + $result = !undef; + } + else + { + Error( "Error check failed:'".$res->status_line()."'" ); + } + + return( $result ); +} + +sub getCamParams +{ + my $self = shift; + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=getimageattr"; + + my $req = $self->sendCmd( $cmd ); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + # Parse results setting values in %FCParams + my $content = $res->decoded_content; + + while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { + $CamParams{$1} = $2; + } + } + else + { + Error( "Error check failed:'".$res->status_line()."'" ); + } +} + +#autoStop +#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab +sub autoStop +{ + my $self = shift; + my $stop_command = shift; + my $autostop = shift; + if( $stop_command && $autostop) + { + Debug( "Auto Stop" ); + usleep( $autostop ); + my $cmd = "decoder_control.cgi?command=".$stop_command; + $self->sendCmd( $cmd ); + } + +} + +# Reset the Camera +sub reset +{ + my $self = shift; + Debug( "Camera Reset" ); + # Move to default position + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=home"; + $self->sendCmd( $cmd ); + + # Reset all other values to default + $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-image_type=1&-default=on"; + $self->sendCmd( $cmd ); +} + +# Reboot Camera (on Sleep button) +sub sleep +{ + my $self = shift; + Debug( "Camera Reboot" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=sysreboot"; + $self->sendCmd( $cmd ); +} + +# Stop the Camera +sub moveStop +{ + my $self = shift; + Debug( "Camera Stop" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=stop"; + $self->sendCmd( $cmd ); +} + +#Up Arrow +sub moveConUp +{ + my $self = shift; + my $stop_command = "1"; + Debug( "Move Up" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=up&-speed=45"; + $self->sendCmd( $cmd ); + #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Down Arrow +sub moveConDown +{ + my $self = shift; + my $stop_command = "3"; + Debug( "Move Down" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=down&-speed=45"; + $self->sendCmd( $cmd ); + #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Left Arrow +sub moveConLeft +{ + my $self = shift; + my $stop_command = "5"; + Debug( "Move Left" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=left&-speed=45"; + $self->sendCmd( $cmd ); + #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Right Arrow +sub moveConRight +{ + my $self = shift; + my $stop_command = "7"; + Debug( "Move Right" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=right&-speed=45"; + $self->sendCmd( $cmd ); + #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Zoom In +sub zoomConTele +{ + my $self = shift; + Debug( "Zoom Tele" ); + my $cmd = "decoder_control.cgi?command=18"; + $self->sendCmd( $cmd ); +} + +#Zoom Out +sub zoomConWide +{ + my $self = shift; + Debug( "Zoom Wide" ); + my $cmd = "decoder_control.cgi?command=16"; + $self->sendCmd( $cmd ); +} + +#Diagonally Up Right Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConUpRight +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upright&-speed=45"; + $self->sendCmd( $cmd ); +} + +#Diagonally Down Right Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConDownRight +{ + my $self = shift; + Debug( "Move Diagonally Down Right" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downright&-speed=45"; + $self->sendCmd( $cmd ); +} + +#Diagonally Up Left Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConUpLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Left" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upleft&-speed=45"; + $self->sendCmd( $cmd ); +} + +#Diagonally Down Left Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConDownLeft +{ + my $self = shift; + Debug( "Move Diagonally Down Left" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downnleft&-speed=45"; + $self->sendCmd( $cmd ); +} + +#Set Camera Preset +#Presets must be translated into values internal to the camera +#Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + + if (( $preset >= 1 ) && ( $preset <= 8 )) { + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=set&-number=".(($preset*2) + 28); + $self->sendCmd( $cmd ); + } +} + +#Recall Camera Preset +#Presets must be translated into values internal to the camera +#Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + + if (( $preset >= 1 ) && ( $preset <= 8 )) { + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number=".(($preset*2) + 29); + $self->sendCmd( $cmd ); + } + + if ( $preset == 9 ) { + $self->horizontalPatrol(); + } + + if ( $preset == 10 ) { + $self->verticalPatrol(); + } +} + +#Horizontal Patrol +sub horizontalPatrol +{ + my $self = shift; + Debug( "Horizontal Patrol" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=hscan"; + $self->sendCmd( $cmd ); +} + +#Vertical Patrol +sub verticalPatrol +{ + my $self = shift; + Debug( "Vertical Patrol" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=vscan"; + $self->sendCmd( $cmd ); +} + +# Increase Brightness +sub irisAbsOpen +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'brightness'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'brightness'} += $step; + $CamParams{'brightness'} = 100 if ($CamParams{'brightness'} > 100); + Debug( "Increase Brightness" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'}; + $self->sendCmd( $cmd ); +} + +# Decrease Brightness +sub irisAbsClose +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'brightness'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'brightness'} -= $step; + $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); + Debug( "Decrease Brightness" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'}; + $self->sendCmd( $cmd ); +} + +# Increase Contrast +sub whiteAbsIn +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'contrast'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'contrast'} += $step; + $CamParams{'contrast'} = 100 if ($CamParams{'contrast'} > 100); + Debug( "Increase Contrast" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'}; + $self->sendCmd( $cmd ); +} + +# Decrease Contrast +sub whiteAbsOut +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'contrast'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'contrast'} -= $step; + $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); + Debug( "Decrease Contrast" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'}; + $self->sendCmd( $cmd ); +} + +#TODO Saturation cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=44 [0-255] +sub satIncrease +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'saturation'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'saturation'} += $step; + $CamParams{'saturation'} = 255 if ($CamParams{'saturation'} > 255); + Debug( "Increase Saturation" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'}; + $self->sendCmd( $cmd ); +} + +sub satDecrease +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'saturation'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'saturation'} -= $step; + $CamParams{'saturation'} = 0 if ($CamParams{'saturation'} < 0); + Debug( "Decrease Saturation" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'}; + $self->sendCmd( $cmd ); +} +#TODO Sharpness cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=37 [0-100] +sub sharpIncrease +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'sharpness'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'sharpness'} += $step; + $CamParams{'sharpness'} = 100 if ($CamParams{'sharpness'} > 100); + Debug( "Increase Sharpness" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'}; + $self->sendCmd( $cmd ); +} + +sub sharpDecrease +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'sharpness'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'sharpness'} -= $step; + $CamParams{'sharpness'} = 0 if ($CamParams{'sharpness'} < 0); + Debug( "Decrease Sharpness" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'}; + $self->sendCmd( $cmd ); +} + +#TODO Hue cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=37 [0-100] +sub hueIncrease +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'hue'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'hue'} += $step; + $CamParams{'hue'} = 100 if ($CamParams{'hue'} > 100); + Debug( "Increase Hue" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'}; + $self->sendCmd( $cmd ); +} + +sub hueDecrease +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'hue'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'hue'} -= $step; + $CamParams{'hue'} = 0 if ($CamParams{'hue'} < 0); + Debug( "Decrease Hue" ); + my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'}; + $self->sendCmd( $cmd ); +} + +1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCAMIOS.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCAMIOS.pm new file mode 100644 index 000000000..8465ee472 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCAMIOS.pm @@ -0,0 +1,326 @@ +# ========================================================================== +# +# ZoneMinder iPhone Control Protocol Module, $Date: 2018-07-15 00:20:00 +0000 $, $Revision: 0003 $ +# Copyright (C) 2001-2008 Philip Coombes +# +# Modified for iPhone ipcamera for IOS BY PETER ZARGLIS n 2018-06-09 13:45:00 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the implementation of the iPhone ipcamera for IOS +# control protocol. +# +# ========================================================================== +package ZoneMinder::Control::IPCAMIOS; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# iPhone ipcamera for IOS Protocol +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); +use Time::HiRes qw( usleep ); + +my $loopfactor=100000; + +sub new +{ + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + my $logindetails = ""; + bless( $self, $class ); + srand( time() ); + return $self; +} + +our $AUTOLOAD; + +sub AUTOLOAD +{ + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); +} + +sub open +{ + my $self = shift; + + $self->loadMonitor(); + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent" ); + + + $self->{state} = 'open'; +} + +sub close +{ + my $self = shift; + $self->{state} = 'closed'; +} + +sub sendCmd +{ + my $self = shift; + my $cmd = shift; + my $result = undef; + my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); + my $res = $self->{ua}->request($req); + if ( $res->is_success ) + { + $result = $res->decoded_content; + } + else + { + Error( "Error check failed: '".$res->status_line()."'" ); + } + return( $result ); +} +sub getDisplayAttr +{ + my $self = shift; + my $param = shift; + my $cmdget = "parameters?"; + my $resp = $self->sendCmd( $cmdget ); + my @fields = split(',',$resp); + my $response=$fields[$param]; + my @buffer=split(':',$response); + my $response2=$buffer[1]; + return ($response2); +} + +sub sleep +{ + +} +# Flip image vertically -> Horz -> off +sub moveConUp +{ + my $self = shift; + my $params = shift; + Debug( "Flip Image" ); + my $dvalue=$self->getDisplayAttr(3); + if ( $dvalue == 2 ) + { + $dvalue=0; + my $cmd = "parameters?flip=$dvalue"; + $self->sendCmd( $cmd ); + } + else + { + $dvalue=$dvalue+1; + my $cmd = "parameters?flip=$dvalue"; + $self->sendCmd( $cmd ); + } +} +# Change camera (front facing or back) +sub moveConDown +{ + my $self = shift; + my $params = shift; + Debug( "Change Camera" ); + my $dvalue=$self->getDisplayAttr(7); + if ( $dvalue == 0 ) + { + my $cmd = "parameters?camera=1"; + $self->sendCmd( $cmd ); + } + else + { + my $cmd = "parameters?camera=0"; + $self->sendCmd( $cmd ); + } +} +# Picture Orientation Clockwise +sub moveConRight +{ + my $self = shift; + my $params = shift; + Debug( "Orientation" ); + my $dvalue=$self->getDisplayAttr(10); + if ( $dvalue == 1 ) + { + $dvalue=4; + my $cmd = "parameters?rotation=$dvalue"; + $self->sendCmd( $cmd ); + } + else + { + $dvalue=$dvalue-1; + my $cmd = "parameters?rotation=$dvalue"; + $self->sendCmd( $cmd ); + } +} +# Picture Orientation Anti-Clockwise +sub moveConLeft +{ + my $self = shift; + my $params = shift; + Debug( "Orientation" ); + my $dvalue=$self->getDisplayAttr(10); + if ( $dvalue == 4 ) + { + $dvalue=1; + my $cmd = "parameters?rotation=$dvalue"; + $self->sendCmd( $cmd ); + } + else + { + $dvalue=$dvalue+1; + my $cmd = "parameters?rotation=$dvalue"; + $self->sendCmd( $cmd ); + } +} + +# presetHome is used to turn off Torch, unlock Focus, unlock Exposure, unlock white-balance, rotation, image flipping +# Just basically reset all the little variables and set it to medium quality +# Rotation = 0 means it will autoselect using built in detection +sub presetHome +{ + my $self = shift; + Debug( "Home Preset" ); + my $cmd = "parameters?torch=0&focus=0&wb=0&exposure=0&rotation=0&flip=0&quality=0.5"; + $self->sendCmd( $cmd ); +} + +sub focusAbsNear +# Focus Un/Lock +{ + my $self = shift; + my $params = shift; + Debug( "Focus Un/Lock" ); + my $dvalue=$self->getDisplayAttr(2); + if ( $dvalue == 0 ) + { + my $cmd = "parameters?focus=1"; + $self->sendCmd( $cmd ); + } + else + { + my $cmd = "parameters?focus=0"; + $self->sendCmd( $cmd ); + } +} + +sub focusAbsFar +# Exposure Un/Lock +{ + my $self = shift; + my $params = shift; + Debug( "Exposure Un/Lock" ); + my $dvalue=$self->getDisplayAttr(11); + if ( $dvalue == 0 ) + { + my $cmd = "parameters?exposure=1"; + $self->sendCmd( $cmd ); + } + else + { + my $cmd = "parameters?exposure=0"; + $self->sendCmd( $cmd ); + } +} +# Increase stream Quality (from 0 to 10) +sub irisAbsOpen +{ + my $self = shift; + my $params = shift; + Debug( "Quality" ); + my $dvalue=$self->getDisplayAttr(8); + if ( $dvalue < 1 ) + { + $dvalue=$dvalue+0.1; + my $cmd = "parameters?quality=$dvalue"; + $self->sendCmd( $cmd ); + } +} + +# Decrease stream Quality (from 10 to 0) +sub irisAbsClose +{ + my $self = shift; + my $params = shift; + Debug( "Quality" ); + my $dvalue=$self->getDisplayAttr(8); + if ( $dvalue > 0 ) + { + $dvalue=$dvalue-0.1; + my $cmd = "parameters?quality=$dvalue"; + $self->sendCmd( $cmd ); + } +} +# White Balance Un/Lock +sub whiteAbsIn +{ + my $self = shift; + my $params = shift; + Debug( "White Balance" ); + my $dvalue=$self->getDisplayAttr(9); + if ( $dvalue == 0 ) + { + my $cmd = "parameters?wb=1"; + $self->sendCmd( $cmd ); + } + else + { + my $cmd = "parameters?wb=0"; + $self->sendCmd( $cmd ); + } +} + +# Torch control on/off +sub whiteAbsOut +{ + my $self = shift; + my $params = shift; + Debug( "Torch" ); + my $dvalue=$self->getDisplayAttr(5); + if ( $dvalue == 0 ) + { + my $cmd = "parameters?torch=1"; + $self->sendCmd( $cmd ); + } + else + { + my $cmd = "parameters?torch=0"; + $self->sendCmd( $cmd ); + } +} + +1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm index e345e5eab..efffd2d19 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm @@ -74,377 +74,351 @@ use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); -sub open -{ - my $self = shift; +sub open { + my $self = shift; - $self->loadMonitor(); + $self->loadMonitor(); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); - $self->{state} = 'open'; + $self->{state} = 'open'; } -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); +sub printMsg { + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); - Debug( $msg."[".$msg_len."]" ); + Debug($msg.'['.$msg_len.']'); } -sub sendCmd -{ - my $self = shift; - my $cmd = shift; - my $msg = shift; - my $content_type = shift; - my $result = undef; +sub sendCmd { + my $self = shift; + my $cmd = shift; + my $msg = shift; + my $content_type = shift; + my $result = undef; - printMsg( $cmd, "Tx" ); + printMsg($cmd, 'Tx'); - my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/$cmd"; - my $req = HTTP::Request->new( POST => $server_endpoint ); - $req->header('content-type' => $content_type); - $req->header('Host' => $self->{Monitor}->{ControlAddress}); - $req->header('content-length' => length($msg)); - $req->header('accept-encoding' => 'gzip, deflate'); - $req->header('connection' => 'Close'); - $req->content($msg); + my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd; + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => $content_type); + $req->header('Host' => $self->{Monitor}->{ControlAddress}); + $req->header('content-length' => length($msg)); + $req->header('accept-encoding' => 'gzip, deflate'); + $req->header('connection' => 'Close'); + $req->content($msg); - my $res = $self->{ua}->request($req); + my $res = $self->{ua}->request($req); - if ( $res->is_success ) { - $result = !undef; - } else { - Error( "After sending PTZ command, camera returned the following error:'".$res->status_line()."'" ); + if ( $res->is_success ) { + $result = !undef; + } else { + Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'"); + } + return $result; +} + +sub getCamParams { + my $self = shift; + my $msg = '000'; + my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/onvif/imaging'; + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"'); + $req->header('Host' => $self->{Monitor}->{ControlAddress}); + $req->header('content-length' => length($msg)); + $req->header('accept-encoding' => 'gzip, deflate'); + $req->header('connection' => 'Close'); + $req->content($msg); + + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) { + # We should really use an xml or soap library to parse the xml tags + my $content = $res->decoded_content; + + if ( $content =~ /.*(.+)<\/tt:Brightness>.*/ ) { + $CamParams{$1} = $2; } - return( $result ); -} - -sub getCamParams -{ - my $self = shift; - my $msg = '000'; - my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/onvif/imaging"; - my $req = HTTP::Request->new( POST => $server_endpoint ); - $req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"'); - $req->header('Host' => $self->{Monitor}->{ControlAddress}); - $req->header('content-length' => length($msg)); - $req->header('accept-encoding' => 'gzip, deflate'); - $req->header('connection' => 'Close'); - $req->content($msg); - - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) { - # We should really use an xml or soap library to parse the xml tags - my $content = $res->decoded_content; - - if ($content =~ /.*(.+)<\/tt:Brightness>.*/) { - $CamParams{$1} = $2; - } - if ($content =~ /.*(.+)<\/tt:Contrast>.*/) { - $CamParams{$1} = $2; - } - } - else - { - Error( "Unable to retrieve camera image settings:'".$res->status_line()."'" ); + if ( $content =~ /.*(.+)<\/tt:Contrast>.*/ ) { + $CamParams{$1} = $2; } + } else { + Error("Unable to retrieve camera image settings:'".$res->status_line()."'"); + } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab -sub autoStop -{ - my $self = shift; - my $autostop = shift; +sub autoStop { + my $self = shift; + my $autostop = shift; - if( $autostop ) { - Debug( "Auto Stop" ); - my $cmd = 'onvif/PTZ'; - my $msg = '000truefalse'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - usleep( $autostop ); - $self->sendCmd( $cmd, $msg, $content_type ); - } + if ( $autostop ) { + Debug('Auto Stop'); + my $cmd = 'onvif/PTZ'; + my $msg = '000truefalse'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + usleep($autostop); + $self->sendCmd($cmd, $msg, $content_type); + } } # Reset the Camera -sub reset -{ - Debug( "Camera Reset" ); - my $self = shift; - my $cmd = ""; - my $msg = ''; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"'; - $self->sendCmd( $cmd, $msg, $content_type ); +sub reset { + Debug('Camera Reset'); + my $self = shift; + my $cmd = ''; + my $msg = ''; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"'; + $self->sendCmd($cmd, $msg, $content_type); } #Up Arrow -sub moveConUp -{ - Debug( "Move Up" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConUp { + Debug('Move Up'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Down Arrow -sub moveConDown -{ - Debug( "Move Down" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConDown { + Debug('Move Down'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Left Arrow -sub moveConLeft -{ - Debug( "Move Left" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConLeft { + Debug('Move Left'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Right Arrow -sub moveConRight -{ - Debug( "Move Right" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConRight { + Debug('Move Right'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Zoom In -sub zoomConTele -{ - Debug( "Zoom Tele" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub zoomConTele { + Debug('Zoom Tele'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Zoom Out -sub zoomConWide -{ - Debug( "Zoom Wide" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub zoomConWide { + Debug('Zoom Wide'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConUpRight -{ - Debug( "Move Diagonally Up Right" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConUpRight { + Debug('Move Diagonally Up Right'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConDownRight -{ - Debug( "Move Diagonally Down Right" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConDownRight { + Debug('Move Diagonally Down Right'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConUpLeft -{ - Debug( "Move Diagonally Up Left" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConUpLeft { + Debug('Move Diagonally Up Left'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConDownLeft -{ - Debug( "Move Diagonally Down Left" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); - $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); +sub moveConDownLeft { + Debug('Move Diagonally Down Left'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Stop -sub moveStop -{ - Debug( "Move Stop" ); - my $self = shift; - my $cmd = 'onvif/PTZ'; - my $msg ='000truefalse'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; - $self->sendCmd( $cmd, $msg, $content_type ); +sub moveStop { + Debug('Move Stop'); + my $self = shift; + my $cmd = 'onvif/PTZ'; + my $msg ='000truefalse'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg, $content_type); } #Set Camera Preset -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = 'onvif/PTZ'; - my $msg ='000'.$preset.''; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"'; - $self->sendCmd( $cmd, $msg, $content_type ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Set Preset $preset"); + my $cmd = 'onvif/PTZ'; + my $msg ='000'.$preset.''; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"'; + $self->sendCmd($cmd, $msg, $content_type); } #Recall Camera Preset -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = 'onvif/PTZ'; - my $msg ='000'.$preset.''; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"'; - $self->sendCmd( $cmd, $msg, $content_type ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Goto Preset $preset"); + my $cmd = 'onvif/PTZ'; + my $msg ='000'.$preset.''; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"'; + $self->sendCmd( $cmd, $msg, $content_type ); } #Horizontal Patrol #To be determined if this camera supports this feature -sub horizontalPatrol -{ - Debug( "Horizontal Patrol" ); - my $self = shift; - my $cmd = ''; - my $msg =''; - my $content_type = ''; -# $self->sendCmd( $cmd, $msg, $content_type ); - Error( "PTZ Command not implemented in control script." ); +sub horizontalPatrol { + Debug('Horizontal Patrol'); + my $self = shift; + my $cmd = ''; + my $msg =''; + my $content_type = ''; + # $self->sendCmd( $cmd, $msg, $content_type ); + Error('PTZ Command not implemented in control script.'); } #Horizontal Patrol Stop #To be determined if this camera supports this feature -sub horizontalPatrolStop -{ - Debug( "Horizontal Patrol Stop" ); - my $self = shift; - my $cmd = ''; - my $msg =''; - my $content_type = ''; -# $self->sendCmd( $cmd, $msg, $content_type ); - Error( "PTZ Command not implemented in control script." ); +sub horizontalPatrolStop { + Debug('Horizontal Patrol Stop'); + my $self = shift; + my $cmd = ''; + my $msg =''; + my $content_type = ''; + # $self->sendCmd( $cmd, $msg, $content_type ); + Error('PTZ Command not implemented in control script.'); } # Increase Brightness -sub irisAbsOpen -{ - Debug( "Iris $CamParams{'Brightness'}" ); - my $self = shift; - my $params = shift; - $self->getCamParams() unless($CamParams{'Brightness'}); - my $step = $self->getParam( $params, 'step' ); - my $max = 100; +sub irisAbsOpen { + Debug("Iris $CamParams{Brightness}"); + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{Brightness}); + my $step = $self->getParam($params, 'step'); + my $max = 100; - $CamParams{'Brightness'} += $step; - $CamParams{'Brightness'} = $max if ($CamParams{'Brightness'} > $max); + $CamParams{Brightness} += $step; + $CamParams{Brightness} = $max if ($CamParams{Brightness} > $max); - my $cmd = 'onvif/imaging'; - my $msg ='000'.$CamParams{'Brightness'}.'true'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; - $self->sendCmd( $cmd, $msg, $content_type ); + my $cmd = 'onvif/imaging'; + my $msg ='000'.$CamParams{Brightness}.'true'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + $self->sendCmd( $cmd, $msg, $content_type ); } # Decrease Brightness sub irisAbsClose { - Debug( "Iris $CamParams{'Brightness'}" ); - my $self = shift; - my $params = shift; - $self->getCamParams() unless($CamParams{'brightness'}); - my $step = $self->getParam( $params, 'step' ); - my $min = 0; + Debug( "Iris $CamParams{Brightness}" ); + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{brightness}); + my $step = $self->getParam( $params, 'step' ); + my $min = 0; - $CamParams{'Brightness'} -= $step; - $CamParams{'Brightness'} = $min if ($CamParams{'Brightness'} < $min); + $CamParams{Brightness} -= $step; + $CamParams{Brightness} = $min if ($CamParams{Brightness} < $min); - my $cmd = 'onvif/imaging'; - my $msg ='000'.$CamParams{'Brightness'}.'true'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; - $self->sendCmd( $cmd, $msg, $content_type ); + my $cmd = 'onvif/imaging'; + my $msg ='000'.$CamParams{Brightness}.'true'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + $self->sendCmd( $cmd, $msg, $content_type ); } # Increase Contrast -sub whiteAbsIn -{ - Debug( "Iris $CamParams{'Contrast'}" ); - my $self = shift; - my $params = shift; - $self->getCamParams() unless($CamParams{'Contrast'}); - my $step = $self->getParam( $params, 'step' ); - my $max = 100; +sub whiteAbsIn { + Debug("Iris $CamParams{Contrast}"); + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{Contrast}); + my $step = $self->getParam( $params, 'step' ); + my $max = 100; - $CamParams{'Contrast'} += $step; - $CamParams{'Contrast'} = $max if ($CamParams{'Contrast'} > $max); + $CamParams{Contrast} += $step; + $CamParams{Contrast} = $max if ($CamParams{Contrast} > $max); - my $cmd = 'onvif/imaging'; - my $msg ='000'.$CamParams{'Contrast'}.'true'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + my $cmd = 'onvif/imaging'; + my $msg ='000'.$CamParams{Contrast}.'true'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; } # Decrease Contrast -sub whiteAbsOut -{ - Debug( "Iris $CamParams{'Contrast'}" ); - my $self = shift; - my $params = shift; - $self->getCamParams() unless($CamParams{'Contrast'}); - my $step = $self->getParam( $params, 'step' ); - my $min = 0; +sub whiteAbsOut { + Debug("Iris $CamParams{Contrast}"); + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{Contrast}); + my $step = $self->getParam($params, 'step'); + my $min = 0; - $CamParams{'Contrast'} -= $step; - $CamParams{'Contrast'} = $min if ($CamParams{'Contrast'} < $min); + $CamParams{Contrast} -= $step; + $CamParams{Contrast} = $min if ($CamParams{Contrast} < $min); - my $cmd = 'onvif/imaging'; - my $msg ='000'.$CamParams{'Contrast'}.'true'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + my $cmd = 'onvif/imaging'; + my $msg ='000'.$CamParams{Contrast}.'true'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; } 1; - +__END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm deleted file mode 100644 index 827540dce..000000000 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm +++ /dev/null @@ -1,504 +0,0 @@ -# ========================================================================= -# -# ZoneMinder Trendnet TV-IP862IC IP Control Protocol Module, $Date: $, $Revision: $ -# Copyright (C) 2014 Vincent Giovannone -# -# -# ========================================================================== -# -# 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, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# ========================================================================== -# -# This module contains the implementation of the Trendnet TV-IP672PI IP camera control -# protocol. Also works or TV-IP862IC -# -# For Zoneminder 1.26+ -# -# Under control capability: -# -# * Main: name it (suggest TVIP672PI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672PI -# * Main (more): Can wake, can sleep, can reset -# * Move: Can move, can move diagonally, can move mapped, can move relative -# * Pan: Can pan -# * Tilt: Can tilt -# * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.) -# -# Under control tab in the monitor itself: -# -# * Controllable -# * Control type is the name you gave it in control capability above -# * Control device is the password you use to authenticate to the camera (see further below if you need to change the username from "admin") -# * Control address is the camera's ip address AND web port. example: 192.168.1.1:80 -# -# -# If using with anything but a TV-IP672PI (ex: TV-IP672WI), YOU MUST MATCH THE REALM TO MATCH YOUR CAMERA FURTHER DOWN! -# -# -# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4. -# (see much further down for why, if you care...) -# - - -package ZoneMinder::Control::TVIP862; - -use 5.006; -use strict; -use warnings; - -require ZoneMinder::Base; -require ZoneMinder::Control; - -our @ISA = qw(ZoneMinder::Control); - -# -# ******** YOU MUST CHANGE THE FOLLOWING LINES TO MATCH YOUR CAMERA! ********** -# -# I assume that "TV-IP672WI" would work for the TV-IP672WI, but can't test since I don't own one. -# -# TV-IP672PI works for the PI version, of course. -# -# Finally, the username is the username you'd like to authenticate as. -# -our $REALM = 'TV-IP862IC'; -our $USERNAME = 'admin'; -our $PASSWORD = ''; -our $ADDRESS = ''; - -# ========================================================================== -# -# Trendnet TV-IP672PI Control Protocol -# -# ========================================================================== - -use ZoneMinder::Logger qw(:all); -use ZoneMinder::Config qw(:all); - -sub open -{ - my $self = shift; - $self->loadMonitor(); - - my ( $protocol, $username, $password, $address ) - = $self->{Monitor}->{ControlAddress} =~ /^(https?:\/\/)?([^:]+):([^\/@]+)@(.*)$/; - if ( $username ) { - $USERNAME = $username; - $PASSWORD = $password; - $ADDRESS = $address; - } else { - Error( "Failed to parse auth from address"); - $ADDRESS = $self->{Monitor}->{ControlAddress}; - } - if ( ! $ADDRESS =~ /:/ ) { - Error( "You generally need to also specify the port. I will append :80" ); - $ADDRESS .= ':80'; - } - - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION ); - $self->{state} = 'open'; -# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) - Debug ( "sendCmd credentials control address:'".$ADDRESS - ."' realm:'" . $REALM - . "' username:'" . $USERNAME - . "' password:'".$PASSWORD - ."'" - ); - $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); - - # Detect REALM - my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" ); - my $res = $self->{ua}->request($req); - - if ( ! $res->is_success ) { - Debug("Need newer REALM"); - if ( $res->status_line() eq '401 Unauthorized' ) { - my $headers = $res->headers(); - foreach my $k ( keys %$headers ) { - Debug("Initial Header $k => $$headers{$k}"); - } # end foreach - if ( $$headers{'www-authenticate'} ) { - my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; - if ( $tokens =~ /\w+="([^"]+)"/i ) { - $REALM = $1; - Debug( "Changing REALM to $REALM" ); - $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); - } # end if - } else { - Debug("No headers line"); - } # end if headers - } # end if $res->status_line() eq '401 Unauthorized' - } # end if ! $res->is_success -} - -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); - - Debug( $msg."[".$msg_len."]" ); -} - -sub sendCmd -{ - -# This routine is used for all moving, which are all GET commands... - - my $self = shift; - my $cmd = shift; - - my $result = undef; - - my $url = "http://".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd; - my $req = HTTP::Request->new( GET=>$url ); - - Debug ("sendCmd command: " . $url ); - - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) { - $result = !undef; - } else { - if ( $res->status_line() eq '401 Unauthorized' ) { - Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); - Error("Content was " . $res->content() ); - my $res = $self->{ua}->request($req); - if ( $res->is_success ) { - $result = !undef; - } else { - Error("Content was " . $res->content() ); - } - } - if ( ! $result ) { - Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); - } - } - - return( $result ); -} - - - -sub sendCmdPost -{ - -# -# This routine is used for setting/clearing presets and IR commands, which are POST commands... -# - - my $self = shift; - my $url = shift; - my $cmd = shift; - - my $result = undef; - - if ($url eq undef) - { - Error ("url passed to sendCmdPost is undefined."); - return(-1); - } - - Debug ("sendCmdPost url: " . $url . " cmd: " . $cmd); - - my $req = HTTP::Request->new(POST => "http://".$ADDRESS.$url); - $req->content_type('application/x-www-form-urlencoded'); - $req->content($cmd); - - Debug ( "sendCmdPost credentials control address:'".$ADDRESS."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'"); - - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "sendCmdPost Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); - if ( $res->status_line() eq '401 Unauthorized' ) { - Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); - } else { - Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); - } # endif - } - - return( $result ); -} - - - -sub move -{ - my $self = shift; - my $panSteps = shift; - my $tiltSteps = shift; - - my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps"; - $self->sendCmd( $cmd ); -} - -sub moveRelUpLeft -{ - my $self = shift; - Debug( "Move Up Left" ); - $self->move(-3, 3); -} - -sub moveRelUp -{ - my $self = shift; - Debug( "Move Up" ); - $self->move(0, 3); -} - -sub moveRelUpRight -{ - my $self = shift; - Debug( "Move Up Right" ); - $self->move(3, 3); -} - -sub moveRelLeft -{ - my $self = shift; - Debug( "Move Left" ); - $self->move(-3, 0); -} - -sub moveRelRight -{ - my $self = shift; - Debug( "Move Right" ); - $self->move(3, 0); -} - -sub moveRelDownLeft -{ - my $self = shift; - Debug( "Move Down Left" ); - $self->move(-3, -3); -} - -sub moveRelDown -{ - my $self = shift; - Debug( "Move Down" ); - $self->move(0, -3); -} - -sub moveRelDownRight -{ - my $self = shift; - Debug( "Move Down Right" ); - $self->move(3, -3); -} - - -# moves the camera to center on the point that the user clicked on in the video image. -# This isn't mega accurate but good enough for most purposes - -sub moveMap -{ - - # If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!) - # They scale the movement and are here to compensate for manufacturing variation. - # It's never going to be perfect, so just get somewhere in the ballpark and call it a day. - # (Don't forget to kill the zmcontrol process while tweaking!) - - # 1280x800 - my $hscale = 31; - my $vscale = 25; - - # 1280x800 with fisheye - #my $hscale = 15; - #my $vscale = 15; - - # 640x400 - #my $hscale = 14; - #my $vscale = 12; - - - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); - - my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale; - my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale; - - $hor = int($hor); - $ver = -1 * int($ver); - - Debug( "Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver" ); - $self->move( $hor, $ver ); -} - - -# **** PRESETS **** -# -# OK, presets work a little funky but they DO work, provided you define them in order and don't skip any. -# -# The problem is that when you load the web page for this camera, it gives a list of preset names tied to index numbers. -# So let's say you have four presets... A, B, C, and D, and defined them in that order. -# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell the camera to go to a preset, you actually tell it by number, not by name. -# (So "Go to D" is really "go to index 3".) -# -# Now let's say somebody deletes C via the camera's web GUI. The camera re-numbers the existing presets A=0, B=1, D=2. -# There's really no easy way for ZM to discover this re-numbering, so zoneminder would still send "go to preset 3" thinking -# it's telling the camera to go to point D. In actuality it's telling the camera to go to a preset that no longer exists. -# -# As long as you define your presets in order (i.e. define preset 1, then preset 2, then preset 3, etc.) everything will work just -# fine in ZoneMinder. -# -# (Home preset needs to be set via the camera's web gui, and is unaffected by any of this.) -# -# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only through!) ZM AND DON'T SKIP ANY. -# - - -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "presetName=$preset&command=del"; - my $url = "/eng/admin/cam_control.cgi"; - Debug ("presetClear: " . $preset . " cmd: " . $cmd); - $self->sendCmdPost($url,$cmd); -} - - -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "presetName=$preset&command=add"; - my $url = "/eng/admin/cam_control.cgi"; - Debug ("presetSet " . $preset . " cmd: " . $cmd); - $self->sendCmdPost ($url,$cmd); -} - -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - $preset = $preset - 1; - Debug( "Goto Preset $preset" ); - my $cmd = "goto_preset_position&index=$preset"; - $self->sendCmd( $cmd ); -} - -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "go_home"; - $self->sendCmd( $cmd ); -} - - -# -# **** IR CONTROLS **** -# -# -# Wake: Force IR on, always. (always night mode) -# -# Sleep: Force IR off, always. (always day mode) -# -# Reset: Automatic IR mode. (day/night mode determined by camera) -# - - -sub wake -{ - # force IR on ("always night mode") - - my $self = shift; - my $url = "/eng/admin/adv_audiovideo.cgi"; - my $cmd = "irMode=3"; - - Debug("Wake -- IR on"); - - $self->sendCmdPost ($url,$cmd); -} - -sub sleep -{ - # force IR off ("always day mode") - - my $self=shift; - my $url = "/eng/admin/adv_audiovideo.cgi"; - my $cmd = "irMode=2"; - - Debug("Sleep -- IR off"); - - $self->sendCmdPost ($url,$cmd); -} - -sub reset -{ - # IR auto - - my $self=shift; - my $url = "/eng/admin/adv_audiovideo.cgi"; - my $cmd = "irMode=0"; - - Debug("Reset -- IR auto"); - - $self->sendCmdPost ($url,$cmd); -} - - -1; -__END__ -# Below is stub documentation for your module. You'd better edit it! - -=head1 NAME - -ZoneMinder::Database - Perl extension for Trendnet TVIP672 - -=head1 SYNOPSIS - - use ZoneMinder::Database; - stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich - -=head1 DESCRIPTION - -Stub documentation for Trendnet TVIP672, created by Vince. - -=head2 EXPORT - -None by default. - - - -=head1 SEE ALSO - -Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0 - - -=head1 AUTHOR - -Vincent Giovannone, I'd rather you not email me. - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2014 by Vincent Giovannone - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.8.3 or, -at your option, any later version of Perl 5 you may have available. - - -=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm new file mode 100644 index 000000000..30534c516 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm @@ -0,0 +1,431 @@ +package ZoneMinder::Control::Trendnet; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +# You do not need to change the REALM, but you can get slightly faster response +# by setting so that the first auth request succeeds. +# +# The username and password should be passed in the ControlAddress field but you +# can set them here if you want. +# + +our $REALM = ''; +our $PROTOCOL = 'http://'; +our $USERNAME = 'admin'; +our $PASSWORD = ''; +our $ADDRESS = ''; + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +sub open { + my $self = shift; + $self->loadMonitor(); + + if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/ ) ) { + $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; + $USERNAME = $+{USERNAME} if $+{USERNAME}; + $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; + $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; + } else { + Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); + $ADDRESS = $self->{Monitor}->{ControlAddress}; + } + if ( !($ADDRESS =~ /:/) ) { + Error('You generally need to also specify the port. I will append :80'); + $ADDRESS .= ':80'; + } + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent('ZoneMinder Control Agent/'.$ZoneMinder::Base::ZM_VERSION); + $self->{state} = 'closed'; + # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) + Debug ( "sendCmd credentials control address:'".$ADDRESS + ."' realm:'" . $REALM + . "' username:'" . $USERNAME + . "' password:'".$PASSWORD + ."'" + ); + $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); + + # Detect REALM + my $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi'); + + if ( $res->is_success ) { + $self->{state} = 'open'; + return; + } + + if ( $res->status_line() eq '401 Unauthorized' ) { + + my $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Initial Header $k => $$headers{$k}"); + } + + if ( $$headers{'www-authenticate'} ) { + my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; + if ( $tokens =~ /\w+="([^"]+)"/i ) { + if ( $REALM ne $1 ) { + $REALM = $1; + Debug("Changing REALM to $REALM"); + $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); + $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi'); + if ( $res->is_success() ) { + $self->{state} = 'open'; + return; + } + Error('Authentication still failed after updating REALM' . $res->status_line); + $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Initial Header $k => $$headers{$k}"); + } # end foreach + } else { + Error('Authentication failed, not a REALM problem'); + } + } else { + Error('Failed to match realm in tokens'); + } # end if + } else { + Debug('No headers line'); + } # end if headers + } # end if $res->status_line() eq '401 Unauthorized' +} # end sub open + +sub printMsg { + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); + + Debug($msg.'['.$msg_len.']'); +} + +sub sendCmd { + + # This routine is used for all moving, which are all GET commands... + + my $self = shift; + my $cmd = shift; + + my $url = $PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi?command='.$cmd; + my $res = $self->{ua}->get($url); + + Debug('sendCmd command: ' . $url); + if ( $res->is_success ) { + return !undef; + } + Error("Error check failed: '".$res->status_line()."' cmd:'".$cmd."'"); + return; +} + +sub sendCmdPost { + + # + # This routine is used for setting/clearing presets and IR commands, which are POST commands... + # + + my $self = shift; + my $url = shift; + my $form = shift; + + my $result = undef; + + if ( $url eq undef ) { + Error('url passed to sendCmdPost is undefined.'); + return -1; + } + + #Debug('sendCmdPost url: ' . $url . ' cmd: ' . $cmd); + + my $res; + $res = $self->{ua}->post( + $PROTOCOL.$ADDRESS.$url, + Referer=>$PROTOCOL.$ADDRESS.$url, + Content=>$form + ); + + Debug("sendCmdPost credentials control to: $PROTOCOL$ADDRESS$url realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'"); + + if ( $res->is_success ) { + return !undef; + } + Error("sendCmdPost Error check failed: '".$res->status_line()."' cmd:"); + + return $result; +} # end sub sendCmdPost + +sub move { + my $self = shift; + my $panSteps = shift; + my $tiltSteps = shift; + + my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps"; + $self->sendCmd($cmd); +} + +sub moveRelUpLeft { + my $self = shift; + Debug('Move Up Left'); + $self->move(-3, 3); +} + +sub moveRelUp { + my $self = shift; + Debug('Move Up'); + $self->move(0, 3); +} + +sub moveRelUpRight { + my $self = shift; + Debug('Move Up Right'); + $self->move(3, 3); +} + +sub moveRelLeft { + my $self = shift; + Debug('Move Left'); + $self->move(-3, 0); +} + +sub moveRelRight { + my $self = shift; + Debug('Move Right'); + $self->move(3, 0); +} + +sub moveRelDownLeft { + my $self = shift; + Debug('Move Down Left'); + $self->move(-3, -3); +} + +sub moveRelDown { + my $self = shift; + Debug('Move Down'); + $self->move(0, -3); +} + +sub moveRelDownRight { + my $self = shift; + Debug('Move Down Right'); + $self->move(3, -3); +} + +# moves the camera to center on the point that the user clicked on in the video image. +# This isn't mega accurate but good enough for most purposes + +sub moveMap { + + # If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!) + # They scale the movement and are here to compensate for manufacturing variation. + # It's never going to be perfect, so just get somewhere in the ballpark and call it a day. + # (Don't forget to kill the zmcontrol process while tweaking!) + + # 1280x800 + my $hscale = 31; + my $vscale = 25; + + # 1280x800 with fisheye + #my $hscale = 15; + #my $vscale = 15; + + # 640x400 + #my $hscale = 14; + #my $vscale = 12; + + + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); + + my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale; + my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale; + + $hor = int($hor); + $ver = -1 * int($ver); + + Debug("Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver"); + $self->move($hor, $ver); +} + + +# **** PRESETS **** +# +# OK, presets work a little funky but they DO work, provided you define them +# in order and don't skip any. +# +# The problem is that when you load the web page for this camera, it gives a +# list of preset names tied to index numbers. +# So let's say you have four presets... A, B, C, and D, and defined them in +# that order. +# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell +# the camera to go to a preset, you actually tell it by number, not by name. +# (So "Go to D" is really "go to index 3".) +# +# Now let's say somebody deletes C via the camera's web GUI. The camera +# re-numbers the existing presets A=0, B=1, D=2. +# There's really no easy way for ZM to discover this re-numbering, so +# zoneminder would still send "go to preset 3" thinking +# it's telling the camera to go to point D. In actuality it's telling the +# camera to go to a preset that no longer exists. +# +# As long as you define your presets in order (i.e. define preset 1, then +# preset 2, then preset 3, etc.) everything will work just +# fine in ZoneMinder. +# +# (Home preset needs to be set via the camera's web gui, and is unaffected by +# any of this.) +# +# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only +# through!) ZM AND DON'T SKIP ANY. +# + +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + my $cmd = "presetName=$preset&command=del"; + my $url = '/eng/admin/cam_control.cgi'; + Debug('presetClear: ' . $preset . ' cmd: ' . $cmd); + $self->sendCmdPost($url,{presetName=>$preset, command=>'del'}); +} + +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + my $cmd = "presetName=$preset&command=add"; + my $url = '/eng/admin/cam_control.cgi'; + Debug('presetSet ' . $preset . ' cmd: ' . $cmd); + $self->sendCmdPost($url,{presetName=>$preset, command=>'add', Submit=>'Add'}); +} + +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + $preset = $preset - 1; + Debug("Goto Preset $preset"); + my $cmd = "goto_preset_position&index=$preset"; + $self->sendCmd($cmd); +} + +sub presetHome { + my $self = shift; + Debug('Home Preset'); + my $cmd = 'go_home'; + $self->sendCmd($cmd); +} + +# +# **** IR CONTROLS **** +# +# +# Wake: Force IR on, always. (always night mode) +# +# Sleep: Force IR off, always. (always day mode) +# +# Reset: Automatic IR mode. (day/night mode determined by camera) +# + +sub wake { + # force IR on ("always night mode") + + my $self = shift; + my $url = '/eng/admin/adv_audiovideo.cgi'; + my $cmd = 'irMode=3'; + + Debug('Wake -- IR on'); + + $self->sendCmdPost($url,$cmd); +} + +sub sleep { + # force IR off ("always day mode") + + my $self = shift; + my $url = '/eng/admin/adv_audiovideo.cgi'; + my $cmd = 'irMode=2'; + + Debug('Sleep -- IR off'); + + $self->sendCmdPost($url,$cmd); +} + +sub reset { + # IR auto + + my $self=shift; + my $url = '/eng/admin/adv_audiovideo.cgi'; + my $cmd = 'irMode=0'; + + Debug('Reset -- IR auto'); + + $self->sendCmdPost($url,$cmd); +} + +1; +__END__ + +=head1 NAME + +ZoneMinder::Control::Trendnet - Perl module for Trendnet cameras + +=head1 SYNOPSIS + +use ZoneMinder::Control::Trendnet; +place this in /usr/share/perl5/ZoneMinder/Control + +=head1 DESCRIPTION + +This module contains the implementation of the Trendnet # IP camera control +protocol. Has been tested with TV-IP862IC + +Under control capability: + +* Main: Can wake, can sleep, can reset +* Move: Can move, can move diagonally, can move mapped, can move relative +* Pan: Can pan +* Tilt: Can tilt +* Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.) + +Under control tab in the monitor itself: + +Controllable +Control type is the name you gave it in control capability above +Control address is the camera's ip address AND web port. example: 192.168.1.1:80 +You can also put the authentication information here and change the +protocol to https using something like https://admin:password@192.168.1.1:80 + +=head2 EXPORT + +None by default. + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2018 ZoneMinder LLC + +This library 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 library 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, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index fcc1392a2..12e3c7065 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -184,16 +184,19 @@ sub zmDbGetMonitor { my $id = shift; - return( undef ) if ( !defined($id) ); + if ( !defined($id) ) { + croak("Undefined id in zmDbgetMonitor"); + return undef ; + } - my $sql = "select * from Monitors where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $id ) - or croak( "Can't execute '$sql': ".$sth->errstr() ); + my $sql = 'SELECT * FROM Monitors WHERE Id = ?'; + my $sth = $dbh->prepare_cached($sql) + or croak("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($id) + or croak("Can't execute '$sql': ".$sth->errstr()); my $monitor = $sth->fetchrow_hashref(); - return( $monitor ); + return $monitor; } sub zmDbGetMonitorAndControl { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 16dc92b76..fc09e3f3f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -347,14 +347,14 @@ sub delete { my $event = $_[0]; if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { my ( $caller, undef, $line ) = caller; - Warning( "Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n" ); + Warning("Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n"); return; } if ( ! -e $event->Storage()->Path() ) { Warning("Not deleting event because storage path doesn't exist"); return; } - Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n" ); + Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n"); $ZoneMinder::Database::dbh->ping(); $ZoneMinder::Database::dbh->begin_work(); @@ -362,9 +362,9 @@ sub delete { { my $sql = 'DELETE FROM Frames WHERE EventId=?'; - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) + my $res = $sth->execute($event->{Id}) or Error( "Can't execute '$sql': ".$sth->errstr() ); $sth->finish(); if ( $ZoneMinder::Database::dbh->errstr() ) { @@ -373,10 +373,10 @@ sub delete { } $sql = 'DELETE FROM Stats WHERE EventId=?'; - $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - $res = $sth->execute( $event->{Id} ) - or Error( "Can't execute '$sql': ".$sth->errstr() ); + $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) + or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); + $res = $sth->execute($event->{Id}) + or Error("Can't execute '$sql': ".$sth->errstr()); $sth->finish(); if ( $ZoneMinder::Database::dbh->errstr() ) { $ZoneMinder::Database::dbh->commit(); @@ -387,10 +387,10 @@ sub delete { # Do it individually to avoid locking up the table for new events { my $sql = 'DELETE FROM Events WHERE Id=?'; - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Error( "Can't execute '$sql': ".$sth->errstr() ); + my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) + or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); + my $res = $sth->execute($event->{Id}) + or Error("Can't execute '$sql': ".$sth->errstr()); $sth->finish(); } $ZoneMinder::Database::dbh->commit(); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index 692af30d4..c91511869 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -557,8 +557,8 @@ our $hasJSONAny = 0; sub _testJSON { return if ( $testedJSON ); my $result = eval { - require JSON::Any; - JSON::Any->import(); + require JSON::MaybeXS; + JSON::MaybeXS->import(); }; $testedJSON = 1; $hasJSONAny = 1 if ( $result ); @@ -581,7 +581,7 @@ sub jsonEncode { _testJSON(); if ( $hasJSONAny ) { - my $string = eval { JSON::Any->objToJson( $value ) }; + my $string = eval { JSON::MaybeXS->encode_json( $value ) }; Fatal( "Unable to encode object to JSON: $@" ) unless( $string ); return( $string ); } @@ -616,7 +616,7 @@ sub jsonDecode { _testJSON(); if ( $hasJSONAny ) { - my $object = eval { JSON::Any->jsonToObj( $value ) }; + my $object = eval { JSON::MaybeXS->decode_json( $value ) }; Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object ); return( $object ); } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index f19d60bda..bac118a13 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -487,7 +487,7 @@ sub openSyslog { sub closeSyslog { my $this = shift; -#closelog(); + closelog(); } sub logFile { @@ -527,32 +527,35 @@ sub logPrint { my $this = shift; my $level = shift; my $string = shift; + my ($caller, undef, $line) = @_ ? @_ : caller; if ( $level <= $this->{effectiveLevel} ) { $string =~ s/[\r\n]+$//g; - - my $code = $codes{$level}; + if ( $level <= $this->{syslogLevel} ) { + syslog($priorities{$level}, $codes{$level}.' [%s]', $string); + } my ($seconds, $microseconds) = gettimeofday(); - my $message = sprintf( - '%s.%06d %s[%d].%s [%s]' - , strftime('%x %H:%M:%S', localtime($seconds)) - , $microseconds - , $this->{id} - , $$ - , $code - , $string - ); - if ( $this->{trace} ) { - $message = Carp::shortmess($message); - } else { - $message = $message."\n"; + if ( $level <= $this->{fileLevel} or $level <= $this->{termLevel} ) { + my $message = sprintf( + '%s.%06d %s[%d].%s [%s:%d] [%s]' + , strftime('%x %H:%M:%S', localtime($seconds)) + , $microseconds + , $this->{id} + , $$ + , $codes{$level} + , $caller + , $line + , $string + ); + if ( $this->{trace} ) { + $message = Carp::shortmess($message); + } else { + $message = $message."\n"; + } + print($LOGFILE $message) if $level <= $this->{fileLevel}; + print(STDERR $message) if $level <= $this->{termLevel}; } - if ( $level <= $this->{syslogLevel} ) { - syslog($priorities{$level}, $code.' [%s]', $string); - } - print($LOGFILE $message) if $level <= $this->{fileLevel}; - print(STDERR $message) if $level <= $this->{termLevel}; if ( $level <= $this->{databaseLevel} ) { if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) { @@ -576,7 +579,7 @@ sub logPrint { , $this->{id} , $$ , $level - , $code + , $codes{$level} , $string , $this->{fileName} ); @@ -660,39 +663,39 @@ sub Dump { sub debug { my $log = shift; - $log->logPrint(DEBUG, @_); - } + $log->logPrint(DEBUG, @_, caller); +} sub Debug( @ ) { - fetch()->logPrint(DEBUG, @_); + fetch()->logPrint(DEBUG, @_, caller); } sub Info( @ ) { - fetch()->logPrint(INFO, @_); + fetch()->logPrint(INFO, @_, caller); } sub info { my $log = shift; - $log->logPrint(INFO, @_); + $log->logPrint(INFO, @_, caller); } sub Warning( @ ) { - fetch()->logPrint(WARNING, @_); + fetch()->logPrint(WARNING, @_, caller); } sub warn { my $log = shift; - $log->logPrint(WARNING, @_); + $log->logPrint(WARNING, @_, caller); } sub Error( @ ) { - fetch()->logPrint(ERROR, @_); + fetch()->logPrint(ERROR, @_, caller); } sub error { my $log = shift; - $log->logPrint(ERROR, @_); + $log->logPrint(ERROR, @_, caller); } sub Fatal( @ ) { - fetch()->logPrint(FATAL, @_); + fetch()->logPrint(FATAL, @_, caller); if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) { $SIG{TERM}(); } @@ -700,7 +703,7 @@ sub Fatal( @ ) { } sub Panic( @ ) { - fetch()->logPrint(PANIC, @_); + fetch()->logPrint(PANIC, @_, caller); confess($_[0]); } diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 80d675f05..28a505801 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -252,36 +252,37 @@ MAIN: while( $loop ) { foreach my $day_dir ( @day_dirs ) { Debug( "Checking day dir $day_dir" ); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint - if ( ! chdir( $day_dir ) ) { - Error( "Can't chdir to '$$Storage{Path}/$day_dir': $!" ); + if ( !chdir($day_dir) ) { + Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); next; } - if ( ! opendir( DIR, '.' ) ) { - Error( "Can't open directory '$$Storage{Path}/$day_dir': $!" ); + if ( ! opendir(DIR, '.') ) { + Error("Can't open directory '$$Storage{Path}/$day_dir': $!"); next; } my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - closedir( DIR ); + Debug("Have " . @event_links . ' event links'); + closedir(DIR); my $count = 0; foreach my $event_link ( @event_links ) { if ( $event_link =~ /[^\d\.]/ ) { Warning("Non-event link found $event_link in $day_dir, skipping"); next; } - Debug( "Checking link $event_link" ); + Debug("Checking link $event_link"); ( my $event = $event_link ) =~ s/^.*\.//; #Event path is hour/minute/sec - my $event_path = readlink( $event_link ); + my $event_path = readlink($event_link); if ( !($event_path and -e $event_path) ) { - aud_print( "Event link $day_dir/$event_link does not point to valid target" ); + aud_print("Event link $day_dir/$event_link does not point to valid target"); if ( confirm() ) { ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint - unlink( $event_link ); + unlink($event_link); $cleaned = 1; } } else { - Debug( "Checking link $event_link points to $event_path " ); + Debug("Checking link $event_link points to $event_path "); my $Event = $fs_events->{$event} = new ZoneMinder::Event(); $$Event{Id} = $event; $$Event{Path} = join('/', $Storage->Path(), $day_dir,$event_path); @@ -292,6 +293,42 @@ MAIN: while( $loop ) { $Event->DiskSpace( undef ); } # event path exists } # end foreach event_link + + # Now check for events that have lost their link + + my @time_dirs = glob('[0-9][0-9]/[0-9][0-9]/[0-9][0-9]'); + foreach my $event_dir ( @time_dirs ) { + Debug("Checking time dir $event_dir"); + ( $event_dir ) = ( $event_dir =~ /^(.*)$/ ); # De-taint + + my $event_id = undef; + + my @mp4_files = glob("$event_dir/[0-9]+\-video.mp4"); + foreach my $mp4_file ( @mp4_files ) { + my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/; + if ( $id ) { + $event_id = $id; + last; + } + } + if ( $event_id ) { + my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); + $$Event{Id} = $event_id; + $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir); + $$Event{RelativePath} = join('/', $day_dir, $event_dir); + $$Event{Scheme} = 'Deep'; + $Event->MonitorId( $monitor_dir ); + $Event->StorageId( $Storage->Id() ); + $Event->DiskSpace( undef ); + } else { + aud_print("Deleting event directories with no event id information at $day_dir/$event_dir"); + if ( confirm() ) { + my $command = "rm -rf $event_dir"; + executeShellCommand( $command ); + $cleaned = 1; + } + } # end if able to find id + } # end foreach event_dir without link chdir( $Storage->Path() ); } # end foreach day dir } @@ -701,17 +738,18 @@ FROM Frames WHERE EventId=?'; if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) { $Config{ZM_LOG_DATABASE_LIMIT} = $1; } - my $deleteLogByTimeSql = - 'DELETE low_priority FROM Logs - WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.')'; - my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) - or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); - $res = $deleteLogByTimeSth->execute() - or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); - if ( $deleteLogByTimeSth->rows() ){ - aud_print( 'Deleted '.$deleteLogByTimeSth->rows() - ." log table entries by time\n" ); - } + my $deleted_rows; + do { + my $deleteLogByTimeSql = + 'DELETE FROM Logs + WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 10'; + my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) + or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); + $res = $deleteLogByTimeSth->execute() + or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); + $deleted_rows = $deleteLogByTimeSth->rows(); + aud_print( "Deleted $deleted_rows log table entries by time\n" ); + } while ( $deleted_rows ); } } # end if ZM_LOG_DATABASE_LIMIT $loop = $continuous; diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 79aeccb10..7d7053335 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -21,6 +21,195 @@ # # ========================================================================== +use strict; + +@EXTRA_PERL_LIB@ +use ZoneMinder; +use Getopt::Long; +use autouse 'Pod::Usage'=>qw(pod2usage); +use POSIX qw/strftime EPIPE/; +use Socket; +#use Data::Dumper; +use Module::Load::Conditional qw{can_load};; + +use constant MAX_CONNECT_DELAY => 15; +use constant MAX_COMMAND_WAIT => 1800; + +$| = 1; + +$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; +$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; +delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; + +logInit(); + +my $arg_string = join( " ", @ARGV ); + +my $id; +my %options; + +GetOptions( + 'id=i' =>\$id, + 'command=s' =>\$options{command}, + 'xcoord=i' =>\$options{xcoord}, + 'ycoord=i' =>\$options{ycoord}, + 'speed=i' =>\$options{speed}, + 'step=i' =>\$options{step}, + 'panspeed=i' =>\$options{panspeed}, + 'tiltspeed=i' =>\$options{tiltspeed}, + 'panstep=i' =>\$options{panstep}, + 'tiltstep=i' =>\$options{tiltstep}, + 'preset=i' =>\$options{preset}, + 'autostop' =>\$options{autostop}, +) or pod2usage(-exitstatus => -1); + +if ( !$id || !$options{command} ) { + print( STDERR "Please give a valid monitor id and command\n" ); + pod2usage(-exitstatus => -1); +} + +( $id ) = $id =~ /^(\w+)$/; + +Debug("zmcontrol: arg string: $arg_string"); + +my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; + +socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + +my $saddr = sockaddr_un($sock_file); +my $server_up = connect(CLIENT, $saddr); +if ( !$server_up ) { + # The server isn't there + my $monitor = zmDbGetMonitorAndControl($id); + if ( !$monitor ) { + Fatal("Unable to load control data for monitor $id"); + } + my $protocol = $monitor->{Protocol}; + + if ( -x $protocol ) { + # Protocol is actually a script! + # Holdover from previous versions + my $command .= $protocol.' '.$arg_string; + Debug($command); + + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + chomp($output); + Debug("Output: $output"); + } + if ( $status ) { + Error("Command '$command' exited with status: $status"); + exit($status); + } + exit(0); + } + + Info("Starting control server $id/$protocol"); + close(CLIENT); + + if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { + Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); + } + + if ( my $cpid = fork() ) { + logReinit(); + + # Parent process just sleep and fall through + socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) + or die("Can't open socket: $!"); + my $attempts = 0; + while ( !connect(CLIENT, $saddr) ) { + $attempts++; + Fatal("Can't connect: $! after $attempts attempts to $sock_file") if $attempts > MAX_CONNECT_DELAY; + sleep(1); + } + } elsif ( defined($cpid) ) { + close(STDOUT); + close(STDERR); + + setpgrp(); + + logReinit(); + + Info("Control server $id/$protocol starting at " + .strftime('%y/%m/%d %H:%M:%S', localtime()) + ); + + $0 = $0." --id $id"; + + my $control = "ZoneMinder::Control::$protocol"->new($id); + my $control_key = $control->getKey(); + $control->loadMonitor(); + + $control->open(); + + socket(SERVER, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + unlink($sock_file); + bind(SERVER, $saddr) or Fatal("Can't bind: $!"); + listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); + + my $rin = ''; + vec( $rin, fileno(SERVER), 1 ) = 1; + my $win = $rin; + my $ein = $win; + my $timeout = MAX_COMMAND_WAIT; + while( 1 ) { + my $nfound = select(my $rout = $rin, undef, undef, $timeout); + if ( $nfound > 0 ) { + if ( vec( $rout, fileno(SERVER), 1 ) ) { + my $paddr = accept(CLIENT, SERVER); + my $message = ; + + next if !$message; + + my $params = jsonDecode($message); + #Debug( Dumper( $params ) ); + + my $command = $params->{command}; + close( CLIENT ); + if ( $command eq 'quit' ) { + last; + } + $control->$command($params); + } else { + Fatal('Bogus descriptor'); + } + } elsif ( $nfound < 0 ) { + if ( $! == EPIPE ) { + Error("Can't select: $!"); + } else { + Fatal("Can't select: $!"); + } + } else { + #print( "Select timed out\n" ); + last; + } + } # end while forever + Info("Control server $id/$protocol exiting"); + unlink($sock_file); + $control->close(); + exit(0); + } else { + Fatal("Can't fork: $!"); + } +} # end if !server up + +# The server is there, connect to it +#print( "Writing commands\n" ); +CLIENT->autoflush(); + +my $message = jsonEncode(\%options); +print(CLIENT $message); +shutdown(CLIENT, 1); + +exit(0); + +1; +__END__ + =head1 NAME zmcontrol.pl - ZoneMinder control script @@ -47,214 +236,3 @@ FIXME FIXME --preset [ arg ] - =cut -use strict; - -@EXTRA_PERL_LIB@ -use ZoneMinder; -use Getopt::Long; -use autouse 'Pod::Usage'=>qw(pod2usage); -use POSIX qw/strftime EPIPE/; -use Socket; -#use Data::Dumper; -use Module::Load::Conditional qw{can_load};; - -use constant MAX_CONNECT_DELAY => 10; -use constant MAX_COMMAND_WAIT => 1800; - -$| = 1; - -$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; -$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; -delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; - -logInit(); - -my $arg_string = join( " ", @ARGV ); - -my $id; -my %options; - -GetOptions( - 'id=i' =>\$id, - 'command=s' =>\$options{command}, - 'xcoord=i' =>\$options{xcoord}, - 'ycoord=i' =>\$options{ycoord}, - 'speed=i' =>\$options{speed}, - 'step=i' =>\$options{step}, - 'panspeed=i' =>\$options{panspeed}, - 'tiltspeed=i' =>\$options{tiltspeed}, - 'panstep=i' =>\$options{panstep}, - 'tiltstep=i' =>\$options{tiltstep}, - 'preset=i' =>\$options{preset}, - 'autostop' =>\$options{autostop}, -) or pod2usage(-exitstatus => -1); - -if ( !$id || !$options{command} ) -{ - print( STDERR "Please give a valid monitor id and command\n" ); - pod2usage(-exitstatus => -1); -} - -( $id ) = $id =~ /^(\w+)$/; - -Debug( $arg_string ); - -my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; - -socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - -my $saddr = sockaddr_un( $sock_file ); -my $server_up = connect( CLIENT, $saddr ); -if ( !$server_up ) -{ - # The server isn't there - my $monitor = zmDbGetMonitorAndControl( $id ); - if ( !$monitor ) - { - Fatal( "Unable to load control data for monitor $id" ); - } - my $protocol = $monitor->{Protocol}; - - if ( -x $protocol ) - { - # Protocol is actually a script! - # Holdover from previous versions - my $command .= $protocol.' '.$arg_string; - Debug( $command."\n" ); - - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Command '$command' exited with status: $status\n" ); - exit( $status ); - } - exit( 0 ); - } - - Info( "Starting control server $id/$protocol" ); - close( CLIENT ); - - if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { - Fatal("Can't load ZoneMinder::Control::$protocol"); - } - - if ( my $cpid = fork() ) - { - logReinit(); - - # Parent process just sleep and fall through - socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) - or die( "Can't open socket: $!" ); - my $attempts = 0; - while (!connect( CLIENT, $saddr )) - { - $attempts++; - Fatal( "Can't connect: $! after $attempts attempts to $sock_file" ) if ($attempts > MAX_CONNECT_DELAY); - sleep(1); - } - } - elsif ( defined($cpid) ) - { - close( STDOUT ); - close( STDERR ); - - setpgrp(); - - logReinit(); - - Info( "Control server $id/$protocol starting at " - .strftime( '%y/%m/%d %H:%M:%S', localtime() ) - ); - - $0 = $0." --id $id"; - - my $control = "ZoneMinder::Control::$protocol"->new( $id ); - my $control_key = $control->getKey(); - $control->loadMonitor(); - - $control->open(); - - socket( SERVER, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - unlink( $sock_file ); - bind( SERVER, $saddr ) or Fatal( "Can't bind: $!" ); - listen( SERVER, SOMAXCONN ) or Fatal( "Can't listen: $!" ); - - my $rin = ''; - vec( $rin, fileno(SERVER), 1 ) = 1; - my $win = $rin; - my $ein = $win; - my $timeout = MAX_COMMAND_WAIT; - while( 1 ) - { - my $nfound = select( my $rout = $rin, undef, undef, $timeout ); - if ( $nfound > 0 ) - { - if ( vec( $rout, fileno(SERVER), 1 ) ) - { - my $paddr = accept( CLIENT, SERVER ); - my $message = ; - - next if ( !$message ); - - my $params = jsonDecode( $message ); - #Debug( Dumper( $params ) ); - - my $command = $params->{command}; - close( CLIENT ); - if ( $command eq 'quit' ) { - last; - } - $control->$command( $params ); - } - else - { - Fatal( "Bogus descriptor" ); - } - } - elsif ( $nfound < 0 ) - { - if ( $! == EPIPE ) - { - Error( "Can't select: $!" ); - } - else - { - Fatal( "Can't select: $!" ); - } - } - else - { - #print( "Select timed out\n" ); - last; - } - } - Info( "Control server $id/$protocol exiting at " - .strftime( '%y/%m/%d %H:%M:%S', localtime() ) - ); - unlink( $sock_file ); - $control->close(); - exit( 0 ); - } - else - { - Fatal( "Can't fork: $!" ); - } -} - -# The server is there, connect to it -#print( "Writing commands\n" ); -CLIENT->autoflush(); - -my $message = jsonEncode( \%options ); -print( CLIENT $message ); -shutdown( CLIENT, 1 ); - -exit( 0 ); diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 2972067ff..6e6663b18 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -370,7 +370,6 @@ sub run { restartPending(); check_for_processes_to_kill() if %terminating_processes; reaper() if %pids_to_reap; - } # end while dPrint(ZoneMinder::Logger::INFO, 'Server exiting at ' @@ -437,7 +436,7 @@ sub start { $dbh = zmDbConnect(1); # This logReinit is required. Not sure why. - logReinit(); + #logReinit(); $process->{pid} = $cpid; $process->{started} = time(); @@ -514,7 +513,7 @@ sub send_stop { .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); - sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; + sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; return(); } @@ -647,7 +646,6 @@ sub chld_sig_handler { sub reaper { foreach my $cpid ( keys %pids_to_reap ) { my $process = $pid_hash{$cpid}; -Debug("Reaping pricess $cpid"); delete $pid_hash{$cpid}; my $reap_info = $pids_to_reap{$cpid}; my ( $status, $stopped ) = @$reap_info{'status','stopped'}; @@ -713,7 +711,6 @@ Debug("Reaping pricess $cpid"); } else { delete $cmd_hash{$$process{command}}; } -Debug("Reaping pricess $cpid"); } # end foreach pid_to_reap } # end sub reaper diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 28f4dc7ee..cfea3bfe4 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -70,16 +70,18 @@ use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); +my $daemon = 0; my $filter_name = ''; my $filter_id; my $version = 0; my $zm_terminate = 0; GetOptions( - 'filter=s' =>\$filter_name, - 'filter_id=s' =>\$filter_id, - 'version' =>\$version - ) or pod2usage(-exitstatus => -1); + daemon =>\$daemon, + 'filter=s' =>\$filter_name, + 'filter_id=s' =>\$filter_id, + version =>\$version +) or pod2usage(-exitstatus => -1); if ( $version ) { print ZoneMinder::Base::ZM_VERSION . "\n"; @@ -96,15 +98,21 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) logInit($filter_id?(id=>'zmfilter_'.$filter_id):()); sub HupHandler { - Info("Received HUP, reloading"); + # This idea at this time is to just exit, freeing up the memory. + # zmfilter.pl will be respawned by zmdc. + TermHandler(); + return; + + Info('Received HUP, reloading'); + ZoneMinder::Object::init_cache(); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { - Info("Received TERM, exiting"); + Info('Received TERM, exiting'); $zm_terminate = 1; } sub Term { - exit( 0 ); + exit(0); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; @@ -173,7 +181,7 @@ if ( $filter_name ) { } if ( ! ( $filter_name or $filter_id ) ) { - Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...'); + Debug('Sleeping due to start delay: ' . START_DELAY . ' seconds...'); sleep(START_DELAY); } @@ -201,7 +209,7 @@ while( !$zm_terminate ) { } } - last if $filter_name or $filter_id or $zm_terminate; + last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate; Debug("Sleeping for $delay seconds\n"); sleep($delay); @@ -265,8 +273,7 @@ sub checkFilter { Info( join(' ', 'Checking filter', $filter->{Name}, - join( ', ', - + join(', ', ($filter->{AutoDelete}?'delete':()), ($filter->{AutoArchive}?'archive':()), ($filter->{AutoVideo}?'video':()), @@ -383,7 +390,7 @@ sub generateVideo { $format = $ffmpeg_formats[0]; } - my $command = join( '', + my $command = join('', $Config{ZM_PATH_BIN}, '/zmvideo.pl -e ', $event->{Id}, @@ -395,7 +402,7 @@ sub generateVideo { $format, ); my $output = qx($command); - chomp( $output ); + chomp($output); my $status = $? >> 8; if ( $status || logDebugging() ) { Debug("Output: $output\n"); diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 62771c7aa..d11e04d5a 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -41,9 +41,10 @@ use autouse 'Pod::Usage'=>qw(pod2usage); $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; -my $store_state=""; # PP - will remember state name passed +my $store_state=''; # PP - will remember state name passed logInit(); +Info("Aftere LogInit"); my $command = $ARGV[0]||''; if ( $command eq 'version' ) { @@ -53,28 +54,27 @@ if ( $command eq 'version' ) { my $state; -my $dbh; - +my $dbh = zmDbConnect(); +Info("Command: $command"); if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ ) { if ( $command ) { - $dbh = zmDbConnect(); # Check to see if it's a valid run state my $sql = 'SELECT * FROM States WHERE Name=?'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $command ) - or Fatal( "Can't execute: ".$sth->errstr() ); + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($command) + or Fatal("Can't execute: ".$sth->errstr()); if ( $state = $sth->fetchrow_hashref() ) { - $state->{Name} = $command; + #$state->{Name} = $command; $state->{Definitions} = []; - foreach( split( /,/, $state->{Definition} ) ) { - my ( $id, $function, $enabled ) = split( /:/, $_ ); + foreach( split(',', $state->{Definition}) ) { + my ( $id, $function, $enabled ) = split(':', $_); push( @{$state->{Definitions}}, { Id=>$id, Function=>$function, Enabled=>$enabled } ); } - $store_state=$command; # PP - Remember the name that was passed to search in DB - $command = 'state'; + $store_state = $command; # PP - Remember the name that was passed to search in DB + $command = 'state'; } else { $command = undef; } @@ -82,64 +82,66 @@ if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ ) if ( !$command ) { pod2usage(-exitstatus => -1); } -} -$dbh = zmDbConnect() if ! $dbh; +} # end if not one of the usual commands + # PP - Sane state check +Debug("StartisActiveSSantiyCheck"); isActiveSanityCheck(); +Debug("Done isActiveSSantiyCheck"); # Move to the right place -chdir( $Config{ZM_PATH_WEB} ) - or Fatal( "Can't chdir to '".$Config{ZM_PATH_WEB}."': $!" ); +chdir($Config{ZM_PATH_WEB}) + or Fatal("Can't chdir to '$Config{ZM_PATH_WEB}': $!"); - my $dbg_id = ''; +my $dbg_id = ''; - Info( "Command: $command\n" ); +Info("Command: $command"); - my $retval = 0; +my $retval = 0; - if ( $command eq 'state' ) { - Info( "Updating DB: $state->{Name}\n" ); - my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=? ORDER BY Id ASC' : 'SELECT * FROM Monitors ORDER BY Id ASC'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: () ) - or Fatal( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) { - foreach my $definition ( @{$state->{Definitions}} ) { - if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) { - $monitor->{NewFunction} = $definition->{Function}; - $monitor->{NewEnabled} = $definition->{Enabled}; - } - } -#next if ( !$monitor->{NewFunction} ); - $monitor->{NewFunction} = 'None' - if ( !$monitor->{NewFunction} ); - $monitor->{NewEnabled} = 0 - if ( !$monitor->{NewEnabled} ); - if ( $monitor->{Function} ne $monitor->{NewFunction} - || $monitor->{Enabled} ne $monitor->{NewEnabled} - ) { - my $sql = 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $monitor->{NewFunction}, $monitor->{NewEnabled}, $monitor->{Id} ) - or Fatal( "Can't execute: ".$sth->errstr() ); +if ( $command eq 'state' ) { + Info("Updating DB: $state->{Name}"); + my $sql = 'SELECT * FROM Monitors' . ($Config{ZM_SERVER_ID} ? ' WHERE ServerId=?' : '' ) .' ORDER BY Id ASC'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: ()) + or Fatal("Can't execute: ".$sth->errstr()); + while( my $monitor = $sth->fetchrow_hashref() ) { + foreach my $definition ( @{$state->{Definitions}} ) { + if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) { + $monitor->{NewFunction} = $definition->{Function}; + $monitor->{NewEnabled} = $definition->{Enabled}; } } - $sth->finish(); + #next if ( !$monitor->{NewFunction} ); + $monitor->{NewFunction} = 'None' + if ( !$monitor->{NewFunction} ); + $monitor->{NewEnabled} = 0 + if ( !$monitor->{NewEnabled} ); + if ( $monitor->{Function} ne $monitor->{NewFunction} + || $monitor->{Enabled} ne $monitor->{NewEnabled} + ) { + my $sql = 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($monitor->{NewFunction}, $monitor->{NewEnabled}, $monitor->{Id}) + or Fatal("Can't execute: ".$sth->errstr()); + } # end if change of function or enablement + } # end foreach monitor + $sth->finish(); -# PP - Now mark a specific state as active - resetStates(); - Info ("Marking $store_state as Enabled"); - $sql = "UPDATE States SET IsActive = '1' WHERE Name = ?"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( $store_state ) - or Fatal( "Can't execute: ".$sth->errstr() ); + # PP - Now mark a specific state as active + resetStates(); + Info("Marking $store_state as Enabled"); + $sql = 'UPDATE States SET IsActive = 1 WHERE Name = ?'; + $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + $res = $sth->execute($store_state) + or Fatal("Can't execute: ".$sth->errstr()); -# PP - zero out other states isActive - $command = 'restart'; - } + # PP - zero out other states isActive + $command = 'restart'; +} # end if command = state # Check if we are running systemd and if we have been called by the system if ( $command =~ /^(start|stop|restart)$/ ) { @@ -154,6 +156,7 @@ if ( $command =~ /^(start|stop|restart)$/ ) { if ( $command =~ /^(?:stop|restart)$/ ) { my $status = runCommand('zmdc.pl check'); + Debug("zmdc.pl check = $status"); if ( $status eq 'running' ) { runCommand('zmdc.pl shutdown'); @@ -163,20 +166,19 @@ if ( $command =~ /^(?:stop|restart)$/ ) { } } -#runCommand( "zmupdate.pl -f" ); - if ( $command =~ /^(?:start|restart)$/ ) { my $status = runCommand('zmdc.pl check'); + Debug("zmdc.pl check = $status"); if ( $status eq 'stopped' ) { if ( $Config{ZM_DYN_DB_VERSION} and ( $Config{ZM_DYN_DB_VERSION} ne ZM_VERSION ) ) { - Fatal( 'Version mismatch, system is version '.ZM_VERSION + Fatal('Version mismatch, system is version '.ZM_VERSION .', database is '.$Config{ZM_DYN_DB_VERSION} .', please run zmupdate.pl to update.' ); - exit( -1 ); + exit(-1); } # Recreate the temporary directory if it's been wiped @@ -196,8 +198,8 @@ if ( $command =~ /^(?:start|restart)$/ ) { my @values; if ( $Config{ZM_SERVER_ID} ) { require ZoneMinder::Server; - Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}\n"); - $Server = new ZoneMinder::Server( $Config{ZM_SERVER_ID} ); + Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}"); + $Server = new ZoneMinder::Server($Config{ZM_SERVER_ID}); $sql = 'SELECT * FROM Monitors WHERE ServerId=?'; @values = ( $Config{ZM_SERVER_ID} ); } else { @@ -206,46 +208,49 @@ if ( $command =~ /^(?:start|restart)$/ ) { } { - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @values ) - or Fatal( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) { - if ( $monitor->{Function} ne 'None' && $monitor->{Type} ne 'WebSite' ) { - if ( $monitor->{Type} eq 'Local' ) { - runCommand( "zmdc.pl start zmc -d $monitor->{Device}" ); - } else { - runCommand( "zmdc.pl start zmc -m $monitor->{Id}" ); - } - if ( $monitor->{Function} ne 'Monitor' ) { - runCommand( "zmdc.pl start zma -m $monitor->{Id}" ); - } - if ( $Config{ZM_OPT_CONTROL} ) { - if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) { - if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) { - runCommand( "zmdc.pl start zmtrack.pl -m $monitor->{Id}" ); - } + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute(@values) + or Fatal("Can't execute: ".$sth->errstr()); + while( my $monitor = $sth->fetchrow_hashref() ) { + if ( $monitor->{Function} ne 'None' && $monitor->{Type} ne 'WebSite' ) { + if ( $monitor->{Type} eq 'Local' ) { + runCommand("zmdc.pl start zmc -d $monitor->{Device}"); + } else { + runCommand("zmdc.pl start zmc -m $monitor->{Id}"); } - } - } - } - $sth->finish(); + if ( $monitor->{Function} ne 'Monitor' ) { + runCommand("zmdc.pl start zma -m $monitor->{Id}"); + } + if ( $Config{ZM_OPT_CONTROL} ) { + if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) { + if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) { + runCommand( "zmdc.pl start zmtrack.pl -m $monitor->{Id}" ); + } else { + Warning(' Monitor is set to track motion, but does not have motion detection enabled.'); + } # end if Has motion enabled + } # end if track motion + } # end if ZM_OPT_CONTROL + } # end if function is not none or Website + } # end foreach monitor + $sth->finish(); } + { - my $sql = 'SELECT Id FROM Filters WHERE Background=1'; - my $sth = $dbh->prepare_cached($sql) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); - if ( $sth->rows ) { - while( my $filter = $sth->fetchrow_hashref() ) { -# This is now started unconditionally - runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id}"); + my $sql = 'SELECT Id FROM Filters WHERE Background=1'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() + or Fatal("Can't execute: ".$sth->errstr()); + if ( $sth->rows ) { + while( my $filter = $sth->fetchrow_hashref() ) { + # This is now started unconditionally + runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id} --daemon"); + } + } else { + runCommand('zmdc.pl start zmfilter.pl'); } - } else { - runCommand('zmdc.pl start zmfilter.pl'); - } - $sth->finish(); + $sth->finish(); } if ( $Config{ZM_RUN_AUDIT} ) { @@ -283,32 +288,32 @@ if ( $command =~ /^(?:start|restart)$/ ) { } else { $retval = 1; } -} +} # end if command is start or restart if ( $command eq 'status' ) { my $status = runCommand('zmdc.pl check'); - print( STDOUT $status."\n" ); + print(STDOUT $status."\n"); } elsif ( $command eq 'logrot' ) { runCommand('zmdc.pl logrot'); } -exit( $retval ); +exit($retval); # PP - Make sure isActive is on and only one sub isActiveSanityCheck { - Info ('Sanity checking States table...'); + Info('Sanity checking States table...'); $dbh = zmDbConnect() if ! $dbh; # PP - First, make sure default exists and there is only one - my $sql = "SELECT Name FROM States WHERE Name='default'"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $sql = q`SELECT Name FROM States WHERE Name='default'`; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); + or Fatal("Can't execute: ".$sth->errstr()); - if ($sth->rows != 1) { + if ( $sth->rows != 1 ) { # PP - no row, or too many rows. Either case is an error Info( 'Fixing States table - either no default state or duplicate default states' ); $sql = "DELETE FROM States WHERE Name='default'"; @@ -316,69 +321,53 @@ sub isActiveSanityCheck { or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute() or Fatal( "Can't execute: ".$sth->errstr() ); - $sql = "INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $sql = q`"INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`; + $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); + or Fatal("Can't execute: ".$sth->errstr()); } - # PP - Now make sure no two states have IsActive=1 - $sql = "SELECT Name FROM States WHERE IsActive = '1'"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $sql = 'SELECT Name FROM States WHERE IsActive = 1'; + $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); + or Fatal("Can't execute: ".$sth->errstr()); if ( $sth->rows != 1 ) { - Info( 'Fixing States table so only one run state is active' ); + Info('Fixing States table so only one run state is active'); resetStates(); - $sql = "UPDATE States SET IsActive='1' WHERE Name='default'"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $sql = q`UPDATE States SET IsActive=1 WHERE Name='default'`; + $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); + or Fatal("Can't execute: ".$sth->errstr()); } -} - +} # end sub isActiveSanityCheck # PP - zeroes out isActive for all states sub resetStates { $dbh = zmDbConnect() if ! $dbh; - my $sql = "UPDATE States SET IsActive='0'"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $sql = 'UPDATE States SET IsActive=0'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); - + or Fatal("Can't execute: ".$sth->errstr()); } sub systemdRunning { - my $result = 0; - my $output = qx(ps -o comm="" -p 1); - chomp( $output ); - - if ( $output =~ /systemd/ ) { - $result = 1; - } - - return $result; + return scalar ( $output =~ /systemd/ ); } sub calledBysystem { - my $result = 0; my $ppid = getppid(); my $output = qx(ps -o comm="" -p $ppid); - chomp( $output ); + #chomp( $output ); - if ($output =~ /^(?:systemd|init)$/) { - $result = 1; - } - - return $result; + return ($output =~ /^(?:systemd|init)$/); } sub verifyFolder { @@ -386,24 +375,22 @@ sub verifyFolder { # Recreate the temporary directory if it's been wiped if ( !-e $folder ) { - Debug( "Recreating directory '$folder'" ); - mkdir( $folder, 0774 ) + Debug("Recreating directory '$folder'"); + mkdir($folder, 0774) or Fatal( "Can't create missing temporary directory '$folder': $!" ); - my ( $runName ) = getpwuid( $> ); + my ( $runName ) = getpwuid($>); if ( $runName ne $Config{ZM_WEB_USER} ) { # Not running as web user, so should be root in which case # chown the directory - my ( $webName, $webPass, $webUid, $webGid ) = getpwnam( $Config{ZM_WEB_USER} ) - or Fatal( "Can't get user details for web user '" - .$Config{ZM_WEB_USER}."': $!" + my ( $webName, $webPass, $webUid, $webGid ) = getpwnam($Config{ZM_WEB_USER}) + or Fatal("Can't get details for web user '$Config{ZM_WEB_USER}': $!"); + chown($webUid, $webGid, $folder) + or Fatal("Can't change ownership of '$folder' to '" + .$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!" ); - chown( $webUid, $webGid, "$folder" ) - or Fatal( "Can't change ownership of directory '$folder' to '" - .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" - ); - } - } -} + } # end if runName ne ZM_WEB_USER + } # end if folder doesn't exist +} # end sub verifyFolder 1; __END__ diff --git a/scripts/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index c23240891..592ccc295 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -33,6 +33,7 @@ use LWP::UserAgent; use Sys::MemInfo qw(totalmem); use Sys::CPU qw(cpu_count); use POSIX qw(strftime uname); +use JSON::MaybeXS; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; @@ -87,7 +88,7 @@ while( 1 ) { # We should keep *BSD systems in mind when calling system commands my %telemetry; $telemetry{uuid} = getUUID($dbh); - $telemetry{ip} = getIP(); + ($telemetry{city}, $telemetry{region}, $telemetry{country}, $telemetry{latitude}, $telemetry{longitude}) = getGeo(); $telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() ); $telemetry{monitor_count} = countQuery($dbh,'Monitors'); $telemetry{event_count} = countQuery($dbh,'Events'); @@ -203,22 +204,25 @@ sub getUUID { return $uuid; } -# Retrieves the local server's external IP address -sub getIP { - my $ipaddr = '0.0.0.0'; +# Retrieve this server's general location information from a GeoIP database +sub getGeo { + my $unknown = 'Unknown'; + my $endpoint = 'https://ipinfo.io/geo'; my $ua = LWP::UserAgent->new; - my $server_endpoint = 'https://wiki.zoneminder.com/ip.php'; - - my $req = HTTP::Request->new(GET => $server_endpoint); + my $req = HTTP::Request->new(GET => $endpoint); my $resp = $ua->request($req); + my $resp_msg = $resp->decoded_content; + my $resp_code = $resp->code; if ($resp->is_success) { - $ipaddr = $resp->decoded_content; + my $content = decode_json( $resp_msg ); + (my $latitude, my $longitude) = split /,/, $content->{loc}; + return ($content->{city}, $content->{region}, $content->{country}, $latitude, $longitude); + } else { + Warning("Geoip data retrieval returned HTTP POST error code: $resp_code"); + Debug("Geoip data retrieval failure response message: $resp_msg"); + return ($unknown, $unknown, $unknown, $unknown); } - - Debug("Found external ip address of: $ipaddr"); - - return $ipaddr; } # As the name implies, just your average mysql count query diff --git a/scripts/zmvideo.pl.in b/scripts/zmvideo.pl.in index 1f1b33acc..95a395b4d 100644 --- a/scripts/zmvideo.pl.in +++ b/scripts/zmvideo.pl.in @@ -173,6 +173,12 @@ my $cwd = getcwd; my $video_name; my @event_ids; + +# Fail if the path to a valid ffmpeg binary is not set +if ( ! -x $Config{ZM_PATH_FFMPEG} ) { + Fatal("Ffmpeg binary not found or not executable. Verify ZM_PATH_FFMPEG points to ffmpeg, avconv, or a compatible binary."); +} + if ( $event_id ) { @event_ids = ( $event_id ); diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 0865bc6e4..6d63be98b 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -123,9 +123,9 @@ void process_configfile( char* configFile) { if ( *line_ptr == '\0' || *line_ptr == '#' ) continue; - // Remove trailing white space + // Remove trailing white space and trailing quotes char *temp_ptr = line_ptr+strlen(line_ptr)-1; - while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) { + while ( *temp_ptr == ' ' || *temp_ptr == '\t' || *temp_ptr == '\'' || *temp_ptr == '\"') { *temp_ptr-- = '\0'; temp_ptr--; } @@ -147,8 +147,9 @@ void process_configfile( char* configFile) { temp_ptr--; } while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); - // Remove leading white space from the value part + // Remove leading white space and leading quotes from the value part white_len = strspn( val_ptr, " \t" ); + white_len += strspn( val_ptr, "\'\"" ); val_ptr += white_len; if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index bc039a2f5..713b9728d 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -289,7 +289,7 @@ bool Event::WriteFrameImage(Image *image, struct timeval timestamp, const char * int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default bool rc; -Debug(3, "Writing image to %s", event_file ); +Debug(3, "Writing image to %s", event_file); if ( !config.timestamp_on_capture ) { // stash the image we plan to use in another pointer regardless if timestamped. @@ -594,11 +594,12 @@ Debug(3, "Writing video"); max_score = score; if ( alarm_image ) { - snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames); - - Debug(1, "Writing analysis frame %d", frames); if ( monitor->GetOptSaveJPEGs() & 2 ) { - WriteFrameImage(alarm_image, timestamp, event_file, true); + snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames); + Debug(1, "Writing analysis frame %d", frames); + if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) { + Error("Failed to write analysis frame image"); + } } } } diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 591bef464..2622f5dfa 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -685,14 +685,14 @@ int FfmpegCamera::Close() { //Function to handle capture and store int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) { - if ( ! mCanCapture ) { + if ( !mCanCapture ) { return -1; } int ret; static char errbuf[AV_ERROR_MAX_STRING_SIZE]; int frameComplete = false; - while ( ! frameComplete ) { + while ( !frameComplete ) { av_init_packet(&packet); ret = av_read_frame(mFormatContext, &packet); @@ -874,18 +874,18 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event } #if HAVE_AVUTIL_HWCONTEXT_H if ( hwaccel ) { - ret = avcodec_receive_frame( mVideoCodecContext, hwFrame ); + ret = avcodec_receive_frame(mVideoCodecContext, hwFrame); if ( ret < 0 ) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + Error("Unable to send packet at frame %d: %s, continuing", frameCount, errbuf); + zm_av_packet_unref(&packet); continue; } ret = av_hwframe_transfer_data(mRawFrame, hwFrame, 0); if (ret < 0) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + Error("Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf); + zm_av_packet_unref(&packet); continue; } } else { diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 409736ca8..b91e88505 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -503,17 +503,17 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co ); char *syslogStart = logPtr; - va_start( argPtr, fstring ); + va_start(argPtr, fstring); if ( hex ) { - unsigned char *data = va_arg( argPtr, unsigned char * ); - int len = va_arg( argPtr, int ); + unsigned char *data = va_arg(argPtr, unsigned char *); + int len = va_arg(argPtr, int); int i; - logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), "%d:", len ); + logPtr += snprintf(logPtr, sizeof(logString)-(logPtr-logString), "%d:", len); for ( i = 0; i < len; i++ ) { - logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), " %02x", data[i] ); + logPtr += snprintf(logPtr, sizeof(logString)-(logPtr-logString), " %02x", data[i]); } } else { - logPtr += vsnprintf( logPtr, sizeof(logString)-(logPtr-logString), fstring, argPtr ); + logPtr += vsnprintf(logPtr, sizeof(logString)-(logPtr-logString), fstring, argPtr); } va_end(argPtr); char *syslogEnd = logPtr; @@ -540,7 +540,13 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co if ( ! db_mutex.trylock() ) { mysql_real_escape_string( &dbconn, escapedString, syslogStart, strlen(syslogStart) ); - snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line ); + snprintf(sql, sizeof(sql), + "INSERT INTO Logs " + "( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line )" + " VALUES " + "( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", + timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line + ); if ( mysql_query(&dbconn, sql) ) { Level tempDatabaseLevel = mDatabaseLevel; databaseLevel(NOLOG); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 20f9c9421..58292ef52 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1234,11 +1234,11 @@ bool Monitor::CheckSignal( const Image *image ) { bool Monitor::Analyse() { if ( shared_data->last_read_index == shared_data->last_write_index ) { // I wonder how often this happens. Maybe if this happens we should sleep or something? - return( false ); + return false; } struct timeval now; - gettimeofday( &now, NULL ); + gettimeofday(&now, NULL); if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { if ( now.tv_sec != last_fps_time ) { @@ -1547,7 +1547,7 @@ bool Monitor::Analyse() { if ( score ) { if ( state == IDLE || state == TAPE || state == PREALARM ) { if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count) ) { - Info("%s: %03d - Gone into alarm state %u > %u", + Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u", name, image_count, Event::PreAlarmCount(), alarm_frame_count); shared_data->state = state = ALARM; if ( signal_change || (function != MOCORD && state != ALERT) ) { @@ -1576,6 +1576,9 @@ bool Monitor::Analyse() { else pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; + Debug(4,"Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d) % %d", + pre_index, index, image_buffer_count, pre_event_count, image_buffer_count); + // Seek forward the next filled slot in to the buffer (oldest data) // from the current position while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index 0acd4e709..25a963a17 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -55,6 +55,10 @@ RemoteCamera::~RemoteCamera() { freeaddrinfo(hp); hp = NULL; } + if ( mAuthenticator ) { + delete mAuthenticator; + mAuthenticator = NULL; + } } void RemoteCamera::Initialise() { diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 9b91ff65c..81aa13c27 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -27,15 +27,14 @@ namespace zm { Authenticator::Authenticator( const std::string &username, const std::string &password) : - fCnonce( "0a4f113b" ), + fCnonce("0a4f113b"), fUsername(username), fPassword(password) { #ifdef HAVE_GCRYPT_H // Special initialisation for libgcrypt - if ( !gcry_check_version( GCRYPT_VERSION ) ) - { - Fatal( "Unable to initialise libgcrypt" ); + if ( !gcry_check_version(GCRYPT_VERSION) ) { + Fatal("Unable to initialise libgcrypt"); } gcry_control( GCRYCTL_DISABLE_SECMEM, 0 ); gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 ); @@ -64,36 +63,34 @@ void Authenticator::authHandleHeader(std::string headerData) size_t digest_match_len = strlen(digest_match); // Check if basic auth - if (strncasecmp(headerData.c_str(),basic_match,strlen(basic_match)) == 0) - { + if ( strncasecmp(headerData.c_str(),basic_match,strlen(basic_match)) == 0 ) { fAuthMethod = AUTH_BASIC; - Debug( 2, "Set authMethod to Basic"); + Debug(2, "Set authMethod to Basic"); } // Check if digest auth - else if (strncasecmp( headerData.c_str(),digest_match,digest_match_len ) == 0) - { + else if (strncasecmp( headerData.c_str(),digest_match,digest_match_len ) == 0) { fAuthMethod = AUTH_DIGEST; Debug( 2, "Set authMethod to Digest"); StringVector subparts = split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ","); // subparts are key="value" - for ( size_t i = 0; i < subparts.size(); i++ ) - { - StringVector kvPair = split( trimSpaces( subparts[i] ), "=" ); - std::string key = trimSpaces( kvPair[0] ); - if (key == "realm") { - fRealm = trimSet( kvPair[1], "\""); + for ( size_t i = 0; i < subparts.size(); i++ ) { + StringVector kvPair = split(trimSpaces(subparts[i]), "="); + std::string key = trimSpaces(kvPair[0]); + if ( key == "realm" ) { + fRealm = trimSet(kvPair[1], "\""); continue; } - if (key == "nonce") { - fNonce = trimSet( kvPair[1], "\""); + if ( key == "nonce" ) { + fNonce = trimSet(kvPair[1], "\""); continue; } - if (key == "qop") { - fQop = trimSet( kvPair[1], "\""); + if ( key == "qop" ) { + fQop = trimSet(kvPair[1], "\""); continue; } } - Debug( 2, "Auth data completed. User: %s, realm: %s, nonce: %s, qop: %s", username().c_str(), fRealm.c_str(), fNonce.c_str(), fQop.c_str() ); + Debug(2, "Auth data completed. User: %s, realm: %s, nonce: %s, qop: %s", + username().c_str(), fRealm.c_str(), fNonce.c_str(), fQop.c_str()); } } @@ -103,12 +100,9 @@ std::string Authenticator::quote( const std::string &src ) { std::string Authenticator::getAuthHeader(std::string method, std::string uri) { std::string result = "Authorization: "; - if (fAuthMethod == AUTH_BASIC) - { + if (fAuthMethod == AUTH_BASIC) { result += "Basic " + base64Encode( username() + ":" + password() ); - } - else if (fAuthMethod == AUTH_DIGEST) - { + } else if (fAuthMethod == AUTH_DIGEST) { result += std::string("Digest ") + "username=\"" + quote(username()) + "\", realm=\"" + quote(realm()) + "\", " + "nonce=\"" + quote(nonce()) + "\", uri=\"" + quote(uri) + "\""; @@ -153,8 +147,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), ha1Data.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len ); #endif - for ( unsigned int j = 0; j < md5len; j++ ) - { + for ( unsigned int j = 0; j < md5len; j++ ) { sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] ); } md5HexBuf[md5len*2]='\0'; @@ -169,8 +162,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), ha2Data.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len ); #endif - for ( unsigned int j = 0; j < md5len; j++ ) - { + for ( unsigned int j = 0; j < md5len; j++ ) { sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); } md5HexBuf[md5len*2]='\0'; @@ -191,22 +183,21 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), digestData.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len ); #endif - for ( unsigned int j = 0; j < md5len; j++ ) - { + for ( unsigned int j = 0; j < md5len; j++ ) { sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); } md5HexBuf[md5len*2]='\0'; return md5HexBuf; #else // HAVE_DECL_MD5 - Error( "You need to build with gnutls or openssl installed to use digest authentication" ); - return( 0 ); + Error("You need to build with gnutls or openssl installed to use digest authentication"); + return 0; #endif // HAVE_DECL_MD5 } void Authenticator::checkAuthResponse(std::string &response) { std::string authLine; - StringVector lines = split( response, "\r\n" ); + StringVector lines = split(response, "\r\n"); const char* authenticate_match = "WWW-Authenticate:"; size_t authenticate_match_len = strlen(authenticate_match); diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index b62ad41fe..401366b2d 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -236,7 +236,9 @@ bool Zone::CheckAlarms(const Image *delta_image) { if ( pixel_diff_count && alarm_pixels ) pixel_diff = pixel_diff_count/alarm_pixels; - Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff); + + Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", + alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff); if ( alarm_pixels ) { if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) { @@ -312,7 +314,8 @@ bool Zone::CheckAlarms(const Image *delta_image) { if ( config.record_diag_images ) diff_image->WriteJpeg(diag_path); - Debug(5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels); + Debug(5, "Got %d filtered pixels, need %d -> %d", + alarm_filter_pixels, min_filter_pixels, max_filter_pixels); if ( alarm_filter_pixels ) { if ( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { @@ -328,7 +331,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { return false; } - score = (100*alarm_filter_pixels)/(polygon.Area()); + score = (100*alarm_filter_pixels)/polygon.Area(); if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug(5, "Current score is %d", score); @@ -438,7 +441,8 @@ bool Zone::CheckAlarms(const Image *delta_image) { alarm_blobs--; - Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs", bss->tag, bsm->tag, x, y, alarm_blobs); + Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs", + bss->tag, bsm->tag, x, y, alarm_blobs); // Clear out the old blob bss->tag = 0; @@ -476,7 +480,11 @@ bool Zone::CheckAlarms(const Image *delta_image) { BlobStats *bs = &blob_stats[i]; // See if we can recycle one first, only if it's at least two rows up if ( bs->count && bs->hi_y < (int)(y-1) ) { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { + if ( + (min_blob_pixels && bs->count < min_blob_pixels) + || + (max_blob_pixels && bs->count > max_blob_pixels) + ) { if ( config.create_analysis_images || config.record_diag_images ) { for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); diff --git a/src/zmc.cpp b/src/zmc.cpp index df116eaea..77b88ddc6 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -366,5 +366,5 @@ int main(int argc, char *argv[]) { logTerm(); zmDbClose(); - return result; + return zm_terminate ? 0 : result; } diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 93694d3fb..3ed227a44 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -263,7 +263,7 @@ execpackpack () { packpack/packpack $parms | grep -Ev '^(-- Installing:|-- Up-to-date:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])' else # Travis never ceases to amaze. For the case of arm emulation, Travis fails the build due to too little output over a 10 minute period. Facepalm. - packpack/packpack $parms | grep -Ev '^(-- Installing:|-- Up-to-date:|Skip blib|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])' + packpack/packpack $parms | grep -Ev '^(-- Installing:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])' fi else packpack/packpack $parms diff --git a/version b/version index 6701dcb1c..d56f906c5 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.45 +1.31.47 diff --git a/web/ajax/log.php b/web/ajax/log.php index 14fc1d772..375b53a05 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -23,7 +23,7 @@ switch ( $_REQUEST['task'] ) { if ( !isset($levels[$_POST['level']]) ) Panic("Unexpected logger level '".$_POST['level']."'"); $level = $levels[$_POST['level']]; - Logger::fetch()->logPrint( $level, $string, $file, $line ); + Logger::fetch()->logPrint($level, $string, $file, $line); } ajaxResponse(); break; @@ -45,22 +45,22 @@ switch ( $_REQUEST['task'] ) { $limit = 100; if ( isset($_REQUEST['limit']) ) { - if ( ( !is_integer( $_REQUEST['limit'] ) and !ctype_digit($_REQUEST['limit']) ) ) { - Error('Invalid value for limit ' . $_REQUEST['limit'] ); + if ( ( !is_integer($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { + Error('Invalid value for limit ' . $_REQUEST['limit']); } else { $limit = $_REQUEST['limit']; } } $sortField = 'TimeKey'; if ( isset($_REQUEST['sortField']) ) { - if ( ! in_array( $_REQUEST['sortField'], $filterFields ) and ( $_REQUEST['sortField'] != 'TimeKey' ) ) { - Error("Invalid sort field " . $_REQUEST['sortField'] ); + if ( !in_array($_REQUEST['sortField'], $filterFields) and ( $_REQUEST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_REQUEST['sortField']); } else { $sortField = $_REQUEST['sortField']; } } - $sortOrder = (isset($_REQUEST['sortOrder']) and $_REQUEST['sortOrder']) == 'asc' ? 'asc':'desc'; - $filter = isset($_REQUEST['filter'])?$_REQUEST['filter']:array(); + $sortOrder = (isset($_REQUEST['sortOrder']) and ($_REQUEST['sortOrder'] == 'asc')) ? 'asc' : 'desc'; + $filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : array(); $total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total'); $sql = 'SELECT * FROM Logs'; @@ -89,15 +89,18 @@ switch ( $_REQUEST['task'] ) { } $options = array(); if ( count($where) ) - $sql.= ' WHERE '.join( ' AND ', $where ); + $sql.= ' WHERE '.join(' AND ', $where); $sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; $logs = array(); foreach ( dbFetchAll($sql, NULL, $values) as $log ) { - $log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']); + + $log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])); + #Warning("TimeKey: " . $log['TimeKey'] . 'Intval:'.intval($log['TimeKey']).' DateTime:'.$log['DateTime']); + #$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']); $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; - $log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message'] ); + $log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']); foreach( $filterFields as $field ) { - if ( ! isset( $options[$field] ) ) + if ( !isset($options[$field]) ) $options[$field] = array(); $value = $log[$field]; @@ -119,7 +122,7 @@ switch ( $_REQUEST['task'] ) { ajaxResponse( array( 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG)?strftime(DATE_FMT_CONSOLE_LONG):date(DATE_FMT_CONSOLE_LONG), 'total' => $total, - 'available' => isset($available)?$available:$total, + 'available' => isset($available) ? $available : $total, 'logs' => $logs, 'state' => logState(), 'options' => $options @@ -210,8 +213,8 @@ switch ( $_REQUEST['task'] ) { if ( !($exportFP = fopen( $exportPath, "w" )) ) Fatal("Unable to open log export file $exportPath"); $logs = array(); - foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { - $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); + foreach ( dbFetchAll($sql, NULL, $values) as $log ) { + $log['DateTime'] = preg_replace('/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey']); $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } @@ -347,7 +350,7 @@ switch ( $_REQUEST['task'] ) { ' ); break; } - $exportExt = "xml"; + $exportExt = 'xml'; break; } fclose( $exportFP ); @@ -363,10 +366,10 @@ switch ( $_REQUEST['task'] ) { ajaxError('Insufficient permissions to download logs'); if ( empty($_REQUEST['key']) ) - Fatal( "No log export key given" ); + Fatal('No log export key given'); $exportKey = $_REQUEST['key']; if ( empty($_REQUEST['format']) ) - Fatal( "No log export format given" ); + Fatal('No log export format given'); $format = $_REQUEST['format']; switch( $format ) { @@ -389,17 +392,17 @@ switch ( $_REQUEST['task'] ) { $exportFile = "zm-log.$exportExt"; $exportPath = ZM_PATH_SWAP."/zm-log-$exportKey.$exportExt"; - header( "Pragma: public" ); - header( "Expires: 0" ); - header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" ); - header( "Cache-Control: private", false ); // required by certain browsers - header( "Content-Description: File Transfer" ); - header( 'Content-Disposition: attachment; filename="'.$exportFile.'"' ); - header( "Content-Transfer-Encoding: binary" ); - header( "Content-Type: application/force-download" ); - header( "Content-Length: ".filesize($exportPath) ); - readfile( $exportPath ); - exit( 0 ); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false ); // required by certain browsers + header('Content-Description: File Transfer'); + header('Content-Disposition: attachment; filename="'.$exportFile.'"' ); + header('Content-Transfer-Encoding: binary'); + header('Content-Type: application/force-download'); + header('Content-Length: '.filesize($exportPath)); + readfile($exportPath); + exit(0); break; } } diff --git a/web/api/app/Controller/AppController.php b/web/api/app/Controller/AppController.php index 7be1cd72a..692769ef0 100644 --- a/web/api/app/Controller/AppController.php +++ b/web/api/app/Controller/AppController.php @@ -27,114 +27,80 @@ App::uses('CrudControllerTrait', 'Crud.Lib'); * Add your application-wide methods in the class below, your controllers * will inherit them. * - * @package app.Controller - * @link http://book.cakephp.org/2.0/en/controllers.html#the-app-controller + * @package app.Controller + * @link http://book.cakephp.org/2.0/en/controllers.html#the-app-controller */ class AppController extends Controller { - use CrudControllerTrait; + use CrudControllerTrait; - public $components = [ - 'Session', // We are going to use SessionHelper to check PHP session vars - 'RequestHandler', - 'Crud.Crud' => [ - 'actions' => [ - 'index' => 'Crud.Index', - 'add' => 'Crud.Add', - 'edit' => 'Crud.Edit', - 'view' => 'Crud.View', - 'keyvalue' => 'Crud.List', - 'category' => 'Crud.Category' - ], - 'listeners' => ['Api', 'ApiTransformation'] - #], + public $components = [ + 'Session', // We are going to use SessionHelper to check PHP session vars + 'RequestHandler', + 'Crud.Crud' => [ + 'actions' => [ + 'index' => 'Crud.Index', + 'add' => 'Crud.Add', + 'edit' => 'Crud.Edit', + 'view' => 'Crud.View', + 'keyvalue' => 'Crud.List', + 'category' => 'Crud.Category' + ], + 'listeners' => ['Api', 'ApiTransformation'] + #], #'DebugKit.Toolbar' => [ # 'bootstrap' => true, 'routes' => true ] - ]; + ]; - // Global beforeFilter function - //Zoneminder sets the username session variable - // to the logged in user. If this variable is set - // then you are logged in - // its pretty simple to extend this to also check - // for role and deny API access in future - // Also checking to do this only if ZM_OPT_USE_AUTH is on - public function beforeFilter() { - $this->loadModel('Config'); - - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_API')); - $config = $this->Config->find('first', $options); - $zmOptApi = $config['Config']['Value']; - - if ($zmOptApi !='1') { + // Global beforeFilter function + //Zoneminder sets the username session variable + // to the logged in user. If this variable is set + // then you are logged in + // its pretty simple to extend this to also check + // for role and deny API access in future + // Also checking to do this only if ZM_OPT_USE_AUTH is on + public function beforeFilter() { + if ( ! ZM_OPT_USE_API ) { throw new UnauthorizedException(__('API Disabled')); return; - } - - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')); - $config = $this->Config->find('first', $options); - $zmOptAuth = $config['Config']['Value']; + } - if ( $zmOptAuth == '1' ) { - require_once "../../../includes/auth.php"; + # For use throughout the app. If not logged in, this will be null. + global $user; + $user = $this->Session->read('user'); + + if ( ZM_OPT_USE_AUTH ) { + require_once '../../../includes/auth.php'; - $this->loadModel('User'); - if ( isset($_REQUEST['user']) and isset($_REQUEST['pass']) ) { - $user = $this->User->find('first', array ('conditions' => array ( - 'User.Username' => $_REQUEST['user'], - 'User.Password' => $_REQUEST['pass'], - )) ); - if ( ! $user ) { - throw new UnauthorizedException(__('User not found')); + $mUser = $this->request->query('user') ? $this->request->query('user') : $this->request->data('user'); + $mPassword = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass'); + $mAuth = $this->request->query('auth') ? $this->request->query('auth') : $this->request->data('auth'); + + if ( $mUser and $mPassword ) { + $user = userLogin($mUser, $mPassword); + if ( !$user ) { + throw new UnauthorizedException(__('User not found or incorrect password')); + return; + } + } else if ( $mAuth ) { + $user = getAuthUser($mAuth); + if ( !$user ) { + throw new UnauthorizedException(__('Invalid Auth Key')); return; - } else { - $this->Session->Write( 'user.Username', $user['User']['Username'] ); - $this->Session->Write( 'user.Enabled', $user['User']['Enabled'] ); } } - - if ( isset($_REQUEST['auth']) ) { - - $user = getAuthUser($_REQUEST['auth']); - if ( ! $user ) { - throw new UnauthorizedException(__('User not found')); + // We need to reject methods that are not authenticated + // besides login and logout + if ( strcasecmp($this->params->action, 'logout') ) { + if ( !( $user and $user['Username'] ) ) { + throw new UnauthorizedException(__('Not Authenticated')); + return; + } else if ( !( $user and $user['Enabled'] ) ) { + throw new UnauthorizedException(__('User is not enabled')); return; - } else { - if ( ! $this->Session->Write('user.Username', $user['Username']) ) - $this->log("Error writing session var user.Username"); - if ( ! $this->Session->Write('user.Enabled', $user['Enabled']) ) - $this->log("Error writing session var user.Enabled"); } - } # end if REQUEST['auth'] - - if ( ! $this->Session->read('user.Username') ) { - throw new UnauthorizedException(__('Not Authenticated')); - return; - } else if ( ! $this->Session->read('user.Enabled') ) { - throw new UnauthorizedException(__('User is not enabled')); - return; - } - - $options = array ('conditions' => array ('User.Username' => $this->Session->Read('user.Username'))); - $userMonitors = $this->User->find('first', $options); - $this->Session->Write('allowedMonitors',$userMonitors['User']['MonitorIds']); - $this->Session->Write('streamPermission',$userMonitors['User']['Stream']); - $this->Session->Write('eventPermission',$userMonitors['User']['Events']); - $this->Session->Write('controlPermission',$userMonitors['User']['Control']); - $this->Session->Write('systemPermission',$userMonitors['User']['System']); - $this->Session->Write('monitorPermission',$userMonitors['User']['Monitors']); - } else { - // if auth is not on, you can do everything - //$userMonitors = $this->User->find('first', $options); - $this->Session->Write('allowedMonitors',''); - $this->Session->Write('streamPermission','View'); - $this->Session->Write('eventPermission','Edit'); - $this->Session->Write('controlPermission','Edit'); - $this->Session->Write('systemPermission','Edit'); - $this->Session->Write('monitorPermission','Edit'); - } - - + } # end if ! login or logout + } # end if ZM_OPT_AUTH + } # end function beforeFilter() - } diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index fd0c4ef98..1d9456cc4 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -1,5 +1,6 @@ Session->Read('eventPermission'); - if ($canView =='None') { + global $user; + $canView = (!$user) || ($user['Events'] != 'None'); + if ( !$canView ) { throw new UnauthorizedException(__('Insufficient Privileges')); return; } } -/** - * index method - * - * @return void - * This also creates a thumbnail for each event. - */ - public function index() { - $this->Event->recursive = -1; - - $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); + /** + * index method + * + * @return void + * This also creates a thumbnail for each event. + */ + public function index() { + $this->Event->recursive = -1; - if (!empty($allowedMonitors)) { + global $user; + $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null; + + if ( $allowedMonitors ) { $mon_options = array('Event.MonitorId' => $allowedMonitors); } else { - $mon_options=''; + $mon_options = ''; } - if ($this->request->params['named']) { - //$this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); + if ( $this->request->params['named'] ) { + //$this->FilterComponent = $this->Components->load('Filter'); + //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); $conditions = $this->request->params['named']; - } else { - $conditions = array(); - } + } else { + $conditions = array(); + } $settings = array( - // https://github.com/ZoneMinder/ZoneMinder/issues/995 - // 'limit' => $limit['ZM_WEB_EVENTS_PER_PAGE'], - // 25 events per page which is what the above - // default is, is way too low for an API - // changing this to 100 so we don't kill ZM - // with many event APIs. In future, we can - // make a nice ZM_API_ITEMS_PER_PAGE for all pagination - // API - - 'limit' => '100', - 'order' => array('StartTime'), - 'paramType' => 'querystring', + // https://github.com/ZoneMinder/ZoneMinder/issues/995 + // 'limit' => $limit['ZM_WEB_EVENTS_PER_PAGE'], + // 25 events per page which is what the above + // default is, is way too low for an API + // changing this to 100 so we don't kill ZM + // with many event APIs. In future, we can + // make a nice ZM_API_ITEMS_PER_PAGE for all pagination + // API + + 'limit' => '100', + 'order' => array('StartTime'), + 'paramType' => 'querystring', ); - if ( isset( $conditions['GroupId'] ) ) { + if ( isset($conditions['GroupId']) ) { $settings['joins'] = array( array( 'table' => 'Groups_Monitors', @@ -75,45 +78,46 @@ class EventsController extends AppController { } $settings['conditions'] = array($conditions, $mon_options); - // How many events to return - $this->loadModel('Config'); - $limit = $this->Config->find('list', array( - 'conditions' => array('Name' => 'ZM_WEB_EVENTS_PER_PAGE'), - 'fields' => array('Name', 'Value') - )); - $this->Paginator->settings = $settings; - $events = $this->Paginator->paginate('Event'); + // How many events to return + $this->loadModel('Config'); + $limit = $this->Config->find('list', array( + 'conditions' => array('Name' => 'ZM_WEB_EVENTS_PER_PAGE'), + 'fields' => array('Name', 'Value') + )); + $this->Paginator->settings = $settings; + $events = $this->Paginator->paginate('Event'); - // For each event, get the frameID which has the largest score - foreach ($events as $key => $value) { - $maxScoreFrameId = $this->getMaxScoreAlarmFrameId($value['Event']['Id']); - $events[$key]['Event']['MaxScoreFrameId'] = $maxScoreFrameId; - } + // For each event, get the frameID which has the largest score + foreach ( $events as $key => $value ) { + $maxScoreFrameId = $this->getMaxScoreAlarmFrameId($value['Event']['Id']); + $events[$key]['Event']['MaxScoreFrameId'] = $maxScoreFrameId; + } - $this->set(compact('events')); - } + $this->set(compact('events')); + } // end public function index() -/** - * view method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function view($id = null) { + /** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { $this->loadModel('Config'); $this->Event->recursive = 1; - if (!$this->Event->exists($id)) { + if ( !$this->Event->exists($id) ) { throw new NotFoundException(__('Invalid event')); } - $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); + global $user; + $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null; - if (!empty($allowedMonitors)) { + if ( $allowedMonitors ) { $mon_options = array('Event.MonitorId' => $allowedMonitors); } else { - $mon_options=''; + $mon_options = ''; } $options = array('conditions' => array(array('Event.' . $this->Event->primaryKey => $id), $mon_options)); @@ -149,14 +153,16 @@ class EventsController extends AppController { */ public function add() { - if ($this->Session->Read('eventPermission') != 'Edit') { + global $user; + $canEdit = (!$user) || ($user['Events'] == 'Edit'); + if ( !$canEdit ) { throw new UnauthorizedException(__('Insufficient privileges')); return; } - if ($this->request->is('post')) { + if ( $this->request->is('post') ) { $this->Event->create(); - if ($this->Event->save($this->request->data)) { + if ( $this->Event->save($this->request->data) ) { return $this->flash(__('The event has been saved.'), array('action' => 'index')); } } @@ -173,18 +179,20 @@ class EventsController extends AppController { */ public function edit($id = null) { - if ($this->Session->Read('eventPermission') != 'Edit') { + global $user; + $canEdit = (!$user) || ($user['Events'] == 'Edit'); + if ( !$canEdit ) { throw new UnauthorizedException(__('Insufficient privileges')); return; } $this->Event->id = $id; - if (!$this->Event->exists($id)) { + if ( !$this->Event->exists($id) ) { throw new NotFoundException(__('Invalid event')); } - if ($this->Event->save($this->request->data)) { + if ( $this->Event->save($this->request->data) ) { $message = 'Saved'; } else { $message = 'Error'; @@ -204,16 +212,18 @@ class EventsController extends AppController { * @return void */ public function delete($id = null) { - if ($this->Session->Read('eventPermission') != 'Edit') { + global $user; + $canEdit = (!$user) || ($user['Events'] == 'Edit'); + if ( !$canEdit ) { throw new UnauthorizedException(__('Insufficient privileges')); return; } $this->Event->id = $id; - if (!$this->Event->exists()) { + if ( !$this->Event->exists() ) { throw new NotFoundException(__('Invalid event')); } $this->request->allowMethod('post', 'delete'); - if ($this->Event->delete()) { + if ( $this->Event->delete() ) { //$this->loadModel('Frame'); //$this->Event->Frame->delete(); return $this->flash(__('The event has been deleted.'), array('action' => 'index')); @@ -228,7 +238,7 @@ class EventsController extends AppController { foreach ($this->params['named'] as $param_name => $value) { // Transform params into mysql - if (preg_match("/interval/i", $value, $matches)) { + if ( preg_match('/interval/i', $value, $matches) ) { $condition = array("$param_name >= (date_sub(now(), $value))"); } else { $condition = array($param_name => $value); @@ -254,12 +264,12 @@ class EventsController extends AppController { $this->Event->recursive = -1; $results = array(); - $moreconditions =""; + $moreconditions = ''; foreach ($this->request->params['named'] as $name => $param) { - $moreconditions = $moreconditions . " AND ".$name.$param; - } + $moreconditions = $moreconditions . ' AND '.$name.$param; + } - $query = $this->Event->query("select MonitorId, COUNT(*) AS Count from Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); + $query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); foreach ($query as $result) { $results[$result['Events']['MonitorId']] = $result[0]['Count']; @@ -275,7 +285,7 @@ class EventsController extends AppController { public function createThumbnail($id = null) { $this->Event->recursive = -1; - if (!$this->Event->exists($id)) { + if ( !$this->Event->exists($id) ) { throw new NotFoundException(__('Invalid event')); } @@ -285,13 +295,13 @@ class EventsController extends AppController { // Find the max Frame for this Event. Error out otherwise. $this->loadModel('Frame'); - if (! $frame = $this->Frame->find('first', array( + if ( !( $frame = $this->Frame->find('first', array( 'conditions' => array( 'EventId' => $event['Event']['Id'], 'Score' => $event['Event']['MaxScore'] ) - ))) { - throw new NotFoundException(__("Can not find Frame for Event " . $event['Event']['Id'])); + ))) ) { + throw new NotFoundException(__('Can not find Frame for Event ' . $event['Event']['Id'])); } $this->loadModel('Config'); @@ -304,14 +314,15 @@ class EventsController extends AppController { $config = $this->Config->find('list', array( 'conditions' => array('OR' => array( - 'Name' => array('ZM_WEB_LIST_THUMB_WIDTH', - 'ZM_WEB_LIST_THUMB_HEIGHT', - 'ZM_EVENT_IMAGE_DIGITS', - 'ZM_DIR_IMAGES', - $thumbs, - 'ZM_DIR_EVENTS' - ) - )), + 'Name' => array( + 'ZM_WEB_LIST_THUMB_WIDTH', + 'ZM_WEB_LIST_THUMB_HEIGHT', + 'ZM_EVENT_IMAGE_DIGITS', + 'ZM_DIR_IMAGES', + $thumbs, + 'ZM_DIR_EVENTS' + ) + )), 'fields' => array('Name', 'Value') )); $config['ZM_WEB_SCALE_THUMBS'] = $config[$thumbs]; @@ -335,12 +346,12 @@ class EventsController extends AppController { $thumbData['Width'] = (int)$thumbWidth; $thumbData['Height'] = (int)$thumbHeight; - return( $thumbData ); + return $thumbData; } public function archive($id = null) { $this->Event->recursive = -1; - if (!$this->Event->exists($id)) { + if ( !$this->Event->exists($id) ) { throw new NotFoundException(__('Invalid event')); } @@ -365,7 +376,7 @@ class EventsController extends AppController { public function getMaxScoreAlarmFrameId($id = null) { $this->Event->recursive = -1; - if (!$this->Event->exists($id)) { + if ( !$this->Event->exists($id) ) { throw new NotFoundException(__('Invalid event')); } @@ -382,7 +393,7 @@ class EventsController extends AppController { 'Score' => $event['Event']['MaxScore'] ) ))) { - throw new NotFoundException(__("Can not find Frame for Event " . $event['Event']['Id'])); + throw new NotFoundException(__('Can not find Frame for Event ' . $event['Event']['Id'])); } return $frame['Frame']['Id']; } diff --git a/web/api/app/Controller/HostController.php b/web/api/app/Controller/HostController.php index 0313bd97a..6dd9e5211 100644 --- a/web/api/app/Controller/HostController.php +++ b/web/api/app/Controller/HostController.php @@ -2,133 +2,164 @@ App::uses('AppController', 'Controller'); class HostController extends AppController { - - public $components = array('RequestHandler'); - public function daemonCheck($daemon=false, $args=false) { + public $components = array('RequestHandler', 'Session'); + + public function daemonCheck($daemon=false, $args=false) { $string = Configure::read('ZM_PATH_BIN').'/zmdc.pl check'; if ( $daemon ) { - $string .= " $daemon"; - if ( $args ) - $string .= " $args"; + $string .= " $daemon"; + if ( $args ) + $string .= " $args"; } $result = exec($string); $result = preg_match('/running/', $result); - $this->set(array( - 'result' => $result, - '_serialize' => array('result') - )); - } - - function getLoad() { - $load = sys_getloadavg(); - - $this->set(array( - 'load' => $load, - '_serialize' => array('load') - )); - } - - function getCredentials() { - // ignore debug warnings from other functions - $this->view='Json'; - $credentials = ""; - $appendPassword = 0; - - $this->loadModel('Config'); - $isZmAuth = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')))['Config']['Value']; - - if ($isZmAuth) { - $zmAuthRelay = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')))['Config']['Value']; - if ($zmAuthRelay == 'hashed') { - $zmAuthHashIps= $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value']; - $credentials = 'auth='.generateAuthHash($zmAuthHashIps); - } - elseif ($zmAuthRelay == 'plain') { - // user will need to append the store password here - $credentials = "user=".$this->Session->read('user.Username')."&pass="; - $appendPassword = 1; - } - elseif ($zmAuthRelay == 'none') { - $credentials = "user=".$this->Session->read('user.Username'); - } - } $this->set(array( - 'credentials'=> $credentials, - 'append_password'=>$appendPassword, + 'result' => $result, + '_serialize' => array('result') + )); + } + + function getLoad() { + $load = sys_getloadavg(); + + $this->set(array( + 'load' => $load, + '_serialize' => array('load') + )); + } + + function login() { + + $cred = $this->_getCredentials(); + $ver = $this->_getVersion(); + $this->set(array( + 'credentials' => $cred[0], + 'append_password'=>$cred[1], + 'version' => $ver[0], + 'apiversion' => $ver[1], + '_serialize' => array('credentials', + 'append_password', + 'version', + 'apiversion' + ))); + } // end function login() + + // clears out session + function logout() { + global $user; + $this->Session->Write('user', null); + + $this->set(array( + 'result' => 'ok', + '_serialize' => array('result') + )); + + } // end function logout() + + private function _getCredentials() { + $credentials = ''; + $appendPassword = 0; + $this->loadModel('Config'); + $isZmAuth = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')))['Config']['Value']; + + if ( $isZmAuth ) { + require_once "../../../includes/auth.php"; # in the event we directly call getCredentials.json + $this->Session->read('user'); # this is needed for command line/curl to recognize a session + $zmAuthRelay = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')))['Config']['Value']; + if ( $zmAuthRelay == 'hashed' ) { + $zmAuthHashIps= $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value']; + $credentials = 'auth='.generateAuthHash($zmAuthHashIps); + } else if ( $zmAuthRelay == 'plain' ) { + // user will need to append the store password here + $credentials = 'user='.$this->Session->read('user.Username').'&pass='; + $appendPassword = 1; + } else if ( $zmAuthRelay == 'none' ) { + $credentials = 'user='.$this->Session->read('user.Username'); + } + } + return array($credentials, $appendPassword); + } // end function _getCredentials + + function getCredentials() { + // ignore debug warnings from other functions + $this->view='Json'; + $val = $this->_getCredentials(); + $this->set(array( + 'credentials'=> $val[0], + 'append_password'=>$val[1], '_serialize' => array('credentials', 'append_password') ) ); - } + } - - // If $mid is set, only return disk usage for that monitor + // If $mid is set, only return disk usage for that monitor // Else, return an array of total disk usage, and per-monitor // usage. - function getDiskPercent($mid = null) { - $this->loadModel('Config'); - $this->loadModel('Monitor'); + function getDiskPercent($mid = null) { + $this->loadModel('Config'); + $this->loadModel('Monitor'); - // If $mid is passed, see if it is valid - if ($mid) { - if (!$this->Monitor->exists($mid)) { - throw new NotFoundException(__('Invalid monitor')); - } - } + // If $mid is passed, see if it is valid + if ($mid) { + if (!$this->Monitor->exists($mid)) { + throw new NotFoundException(__('Invalid monitor')); + } + } - $zm_dir_events = $this->Config->find('list', array( - 'conditions' => array('Name' => 'ZM_DIR_EVENTS'), - 'fields' => array('Name', 'Value') - )); - $zm_dir_events = $zm_dir_events['ZM_DIR_EVENTS' ]; + $zm_dir_events = $this->Config->find('list', array( + 'conditions' => array('Name' => 'ZM_DIR_EVENTS'), + 'fields' => array('Name', 'Value') + )); + $zm_dir_events = $zm_dir_events['ZM_DIR_EVENTS' ]; - // Test to see if $zm_dir_events is relative or absolute - if ('/' === "" || strrpos($zm_dir_events, '/', -strlen($zm_dir_events)) !== TRUE) { - // relative - so add the full path - $zm_dir_events = Configure::read('ZM_PATH_WEB') . '/' . $zm_dir_events; - } + // Test to see if $zm_dir_events is relative or absolute + if ('/' === "" || strrpos($zm_dir_events, '/', -strlen($zm_dir_events)) !== TRUE) { + // relative - so add the full path + $zm_dir_events = Configure::read('ZM_PATH_WEB') . '/' . $zm_dir_events; + } - if ($mid) { - // Get disk usage for $mid - $usage = shell_exec ("du -sh0 $zm_dir_events/$mid | awk '{print $1}'"); - } else { - $monitors = $this->Monitor->find('all', array( - 'fields' => array('Id', 'Name', 'WebColour') - )); - $usage = array(); + if ($mid) { + // Get disk usage for $mid + $usage = shell_exec ("du -sh0 $zm_dir_events/$mid | awk '{print $1}'"); + } else { + $monitors = $this->Monitor->find('all', array( + 'fields' => array('Id', 'Name', 'WebColour') + )); + $usage = array(); - // Add each monitor's usage to array - foreach ($monitors as $key => $value) { - $id = $value['Monitor']['Id']; - $name = $value['Monitor']['Name']; - $color = $value['Monitor']['WebColour']; + // Add each monitor's usage to array + foreach ($monitors as $key => $value) { + $id = $value['Monitor']['Id']; + $name = $value['Monitor']['Name']; + $color = $value['Monitor']['WebColour']; - $space = shell_exec ("du -s0 $zm_dir_events/$id | awk '{print $1}'"); - if ($space == null) { - $space = 0; - } - $space = $space/1024/1024; + $space = shell_exec ("du -s0 $zm_dir_events/$id | awk '{print $1}'"); + if ($space == null) { + $space = 0; + } + $space = $space/1024/1024; - $usage[$name] = array( - 'space' => rtrim($space), - 'color' => $color - ); - } + $usage[$name] = array( + 'space' => rtrim($space), + 'color' => $color + ); + } - // Add total usage to array - $space = shell_exec( "df $zm_dir_events |tail -n1 | awk '{print $3 }'"); - $space = $space/1024/1024; - $usage['Total'] = array( - 'space' => rtrim($space), - 'color' => '#F7464A' - ); - } + // Add total usage to array + $space = shell_exec( "df $zm_dir_events |tail -n1 | awk '{print $3 }'"); + $space = $space/1024/1024; + $usage['Total'] = array( + 'space' => rtrim($space), + 'color' => '#F7464A' + ); + } - $this->set(array( - 'usage' => $usage, - '_serialize' => array('usage') - )); - } + $this->set(array( + 'usage' => $usage, + '_serialize' => array('usage') + )); + } function getTimeZone() { //http://php.net/manual/en/function.date-default-timezone-get.php @@ -139,18 +170,18 @@ class HostController extends AppController { )); } - function getVersion() { - //throw new UnauthorizedException(__('API Disabled')); - $version = Configure::read('ZM_VERSION'); - // not going to use the ZM_API_VERSION - // requires recompilation and dependency on ZM upgrade - //$apiversion = Configure::read('ZM_API_VERSION'); - $apiversion = '1.0'; + private function _getVersion() { + $version = Configure::read('ZM_VERSION'); + $apiversion = '1.0'; + return array($version, $apiversion); + } - $this->set(array( - 'version' => $version, - 'apiversion' => $apiversion, - '_serialize' => array('version', 'apiversion') - )); - } + function getVersion() { + $val = $this->_getVersion(); + $this->set(array( + 'version' => $val[0], + 'apiversion' => $val[1], + '_serialize' => array('version', 'apiversion') + )); + } } diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 5801c1e9e..5ce4bb476 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -18,10 +18,13 @@ class MonitorsController extends AppController { public function beforeRender() { $this->set($this->Monitor->enumValues()); } + public function beforeFilter() { parent::beforeFilter(); - $canView = $this->Session->Read('monitorPermission'); - if ($canView == 'None') { + global $user; + # We already tested for auth in appController, so we just need to test for specific permission + $canView = (!$user) || ($user['Monitors'] != 'None'); + if ( !$canView ) { throw new UnauthorizedException(__('Insufficient Privileges')); return; } @@ -35,7 +38,7 @@ class MonitorsController extends AppController { public function index() { $this->Monitor->recursive = 0; - if ($this->request->params['named']) { + if ( $this->request->params['named'] ) { $this->FilterComponent = $this->Components->load('Filter'); //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); $conditions = $this->request->params['named']; @@ -43,13 +46,14 @@ class MonitorsController extends AppController { $conditions = array(); } - $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); - if (!empty($allowedMonitors)) { + global $user; + $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null; + if ( $allowedMonitors ) { $conditions['Monitor.Id' ] = $allowedMonitors; } $find_array = array('conditions'=>$conditions,'contain'=>array('Group')); - if ( isset( $conditions['GroupId'] ) ) { + if ( isset($conditions['GroupId']) ) { $find_array['joins'] = array( array( 'table' => 'Groups_Monitors', @@ -84,11 +88,12 @@ class MonitorsController extends AppController { */ public function view($id = null) { $this->Monitor->recursive = 0; - if (!$this->Monitor->exists($id)) { + if ( !$this->Monitor->exists($id) ) { throw new NotFoundException(__('Invalid monitor')); } - $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); - if (!empty($allowedMonitors)) { + global $user; + $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null; + if ( $allowedMonitors ) { $restricted = array('Monitor.' . $this->Monitor->primaryKey => $allowedMonitors); } else { $restricted = ''; @@ -114,13 +119,15 @@ class MonitorsController extends AppController { public function add() { if ( $this->request->is('post') ) { - if ( $this->Session->Read('systemPermission') != 'Edit' ) { - throw new UnauthorizedException(__('Insufficient privileges')); + global $user; + $canAdd = (!$user) || ($user['System'] == 'Edit' ); + if ( !$canAdd ) { + throw new UnauthorizedException(__('Insufficient privileges')); return; } $this->Monitor->create(); - if ($this->Monitor->save($this->request->data)) { + if ( $this->Monitor->save($this->request->data) ) { $this->daemonControl($this->Monitor->id, 'start'); //return $this->flash(__('The monitor has been saved.'), array('action' => 'index')); $message = 'Saved'; @@ -144,10 +151,12 @@ class MonitorsController extends AppController { public function edit($id = null) { $this->Monitor->id = $id; - if (!$this->Monitor->exists($id)) { + if ( !$this->Monitor->exists($id) ) { throw new NotFoundException(__('Invalid monitor')); } - if ($this->Session->Read('monitorPermission') != 'Edit') { + global $user; + $canEdit = (!$user) || ($user['Monitors'] == 'Edit'); + if ( !$canEdit ) { throw new UnauthorizedException(__('Insufficient privileges')); return; } @@ -163,9 +172,17 @@ class MonitorsController extends AppController { // - restart or stop this monitor after change $func = $Monitor['Function']; // We don't pass the request data as the monitor object because it may be a subset of the full monitor array - $this->daemonControl( $this->Monitor->id, 'stop' ); - if ( ( $func != 'None' ) and ( (!defined('ZM_SERVER_ID')) or ($Monitor['ServerId']==ZM_SERVER_ID) ) ) { - $this->daemonControl( $this->Monitor->id, 'start' ); + $this->daemonControl($this->Monitor->id, 'stop'); + if ( + ( $func != 'None' ) + and + ( + (!defined('ZM_SERVER_ID')) + or + ($Monitor['ServerId']==ZM_SERVER_ID) + ) + ) { + $this->daemonControl($this->Monitor->id, 'start'); } } else { $message = 'Error ' . print_r($this->Monitor->invalidFields(), true); @@ -187,10 +204,10 @@ class MonitorsController extends AppController { */ public function delete($id = null) { $this->Monitor->id = $id; - if (!$this->Monitor->exists()) { + if ( !$this->Monitor->exists() ) { throw new NotFoundException(__('Invalid monitor')); } - if ($this->Session->Read('systemPermission') != 'Edit') { + if ( $this->Session->Read('systemPermission') != 'Edit' ) { throw new UnauthorizedException(__('Insufficient privileges')); return; } @@ -198,7 +215,7 @@ class MonitorsController extends AppController { $this->daemonControl($this->Monitor->id, 'stop'); - if ($this->Monitor->delete()) { + if ( $this->Monitor->delete() ) { return $this->flash(__('The monitor has been deleted.'), array('action' => 'index')); } else { return $this->flash(__('The monitor could not be deleted. Please, try again.'), array('action' => 'index')); @@ -206,7 +223,7 @@ class MonitorsController extends AppController { } public function sourceTypes() { - $sourceTypes = $this->Monitor->query("describe Monitors Type;"); + $sourceTypes = $this->Monitor->query('describe Monitors Type;'); preg_match('/^enum\((.*)\)$/', $sourceTypes[0]['COLUMNS']['Type'], $matches); foreach( explode(',', $matches[1]) as $value ) { @@ -226,7 +243,7 @@ class MonitorsController extends AppController { public function alarm() { $id = $this->request->params['named']['id']; $cmd = strtolower($this->request->params['named']['command']); - if (!$this->Monitor->exists($id)) { + if ( !$this->Monitor->exists($id) ) { throw new NotFoundException(__('Invalid monitor')); } if ( $cmd != 'on' && $cmd != 'off' && $cmd != 'status' ) { @@ -252,19 +269,18 @@ class MonitorsController extends AppController { // form auth key based on auth credentials $this->loadModel('Config'); $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')); - $config = $this->Config->find('first', $options); + $config = $this->Config->find('first', $options); $zmOptAuth = $config['Config']['Value']; - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')); - $config = $this->Config->find('first', $options); + $config = $this->Config->find('first', $options); $zmAuthRelay = $config['Config']['Value']; - $auth=''; + $auth = ''; if ( $zmOptAuth ) { if ( $zmAuthRelay == 'hashed' ) { $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_SECRET')); - $config = $this->Config->find('first', $options); + $config = $this->Config->find('first', $options); $zmAuthHashSecret = $config['Config']['Value']; $time = localtime(); @@ -293,7 +309,7 @@ class MonitorsController extends AppController { $id = $this->request->params['named']['id']; $daemon = $this->request->params['named']['daemon']; - if (!$this->Monitor->exists($id)) { + if ( !$this->Monitor->exists($id) ) { throw new NotFoundException(__('Invalid monitor')); } @@ -306,7 +322,7 @@ class MonitorsController extends AppController { $monitor = Set::extract('/Monitor/.', $monitor); // Pass -d for local, otherwise -m - if ($monitor[0]['Type'] == 'Local') { + if ( $monitor[0]['Type'] == 'Local' ) { $args = '-d '. $monitor[0]['Device']; } else { $args = '-m '. $monitor[0]['Id']; @@ -315,7 +331,7 @@ class MonitorsController extends AppController { // Build the command, and execute it $zm_path_bin = Configure::read('ZM_PATH_BIN'); $command = escapeshellcmd("$zm_path_bin/zmdc.pl status $daemon $args"); - $status = exec( $command ); + $status = exec($command); // If 'not' is present, the daemon is not running, so return false // https://github.com/ZoneMinder/ZoneMinder/issues/799#issuecomment-108996075 @@ -351,9 +367,9 @@ class MonitorsController extends AppController { $zm_path_bin = Configure::read('ZM_PATH_BIN'); - foreach ($daemons as $daemon) { + foreach ( $daemons as $daemon ) { $args = ''; - if ( $daemon == 'zmc' and $monitor['Type'] == 'Local') { + if ( $daemon == 'zmc' and $monitor['Type'] == 'Local' ) { $args = '-d ' . $monitor['Device']; } else { $args = '-m ' . $id; @@ -363,5 +379,4 @@ class MonitorsController extends AppController { $status = exec( $shellcmd ); } } - } // end class MonitorsController diff --git a/web/api/app/Controller/ServersController.php b/web/api/app/Controller/ServersController.php index 88a5bec90..c30de038f 100644 --- a/web/api/app/Controller/ServersController.php +++ b/web/api/app/Controller/ServersController.php @@ -8,7 +8,6 @@ App::uses('AppController', 'Controller'); */ class ServersController extends AppController { - /** * Components * @@ -16,18 +15,16 @@ class ServersController extends AppController { */ public $components = array('Paginator', 'RequestHandler'); - -public function beforeFilter() { - parent::beforeFilter(); - $canView = $this->Session->Read('streamPermission'); - if ($canView =='None') { - throw new UnauthorizedException(__('Insufficient Privileges')); - return; + public function beforeFilter() { + parent::beforeFilter(); + global $user; + $canView = (!$user) || ($user['System'] != 'None'); + if ( !$canView ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } } -} - - /** * index method * @@ -36,7 +33,7 @@ public function beforeFilter() { public function index() { $this->Server->recursive = 0; - $options=''; + $options = ''; $servers = $this->Server->find('all',$options); $this->set(array( 'servers' => $servers, @@ -76,16 +73,17 @@ public function beforeFilter() { * @return void */ public function add() { - if ($this->request->is('post')) { + if ( $this->request->is('post') ) { - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); return; } $this->Server->create(); - if ($this->Server->save($this->request->data)) { + if ( $this->Server->save($this->request->data) ) { # Might be nice to send it a start request #$this->daemonControl($this->Server->id, 'start', $this->request->data); return $this->flash(__('The server has been saved.'), array('action' => 'index')); @@ -103,15 +101,17 @@ public function beforeFilter() { public function edit($id = null) { $this->Server->id = $id; - if (!$this->Server->exists($id)) { - throw new NotFoundException(__('Invalid server')); - } - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); return; } - if ($this->Server->save($this->request->data)) { + + if ( !$this->Server->exists($id) ) { + throw new NotFoundException(__('Invalid server')); + } + if ( $this->Server->save($this->request->data) ) { $message = 'Saved'; } else { $message = 'Error'; @@ -133,20 +133,22 @@ public function beforeFilter() { * @return void */ public function delete($id = null) { - $this->Server->id = $id; - if (!$this->Server->exists()) { - throw new NotFoundException(__('Invalid server')); - } - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); return; } + + $this->Server->id = $id; + if ( !$this->Server->exists() ) { + throw new NotFoundException(__('Invalid server')); + } $this->request->allowMethod('post', 'delete'); #$this->daemonControl($this->Server->id, 'stop'); - if ($this->Server->delete()) { + if ( $this->Server->delete() ) { return $this->flash(__('The server has been deleted.'), array('action' => 'index')); } else { return $this->flash(__('The server could not be deleted. Please, try again.'), array('action' => 'index')); diff --git a/web/api/app/Controller/StatesController.php b/web/api/app/Controller/StatesController.php index 051837b27..29201d2c1 100644 --- a/web/api/app/Controller/StatesController.php +++ b/web/api/app/Controller/StatesController.php @@ -12,30 +12,28 @@ class StatesController extends AppController { public $components = array('RequestHandler'); public function beforeFilter() { - parent::beforeFilter(); - $canView = $this->Session->Read('systemPermission'); - if ($canView =='None') - { - throw new UnauthorizedException(__('Insufficient Privileges')); - return; - } - + parent::beforeFilter(); + global $user; + $canView = (!$user) || ($user['System'] != 'None'); + if ( !$canView ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } } - /** * index method * * @return void */ - public function index() { - $this->State->recursive = 0; - $states = $this->State->find('all'); - $this->set(array( - 'states' => $states, - '_serialize' => array('states') - )); - } +public function index() { + $this->State->recursive = 0; + $states = $this->State->find('all'); + $this->set(array( + 'states' => $states, + '_serialize' => array('states') + )); +} /** * view method @@ -44,35 +42,35 @@ public function beforeFilter() { * @param string $id * @return void */ - public function view($id = null) { - if (!$this->State->exists($id)) { - throw new NotFoundException(__('Invalid state')); - } - $options = array('conditions' => array('State.' . $this->State->primaryKey => $id)); - $this->set('state', $this->State->find('first', $options)); - } +public function view($id = null) { + if ( !$this->State->exists($id) ) { + throw new NotFoundException(__('Invalid state')); + } + $options = array('conditions' => array('State.' . $this->State->primaryKey => $id)); + $this->set('state', $this->State->find('first', $options)); +} /** * add method * * @return void */ - public function add() { - - if ($this->request->is('post')) { +public function add() { - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } + if ($this->request->is('post')) { - $this->State->create(); - if ($this->State->save($this->request->data)) { - return $this->flash(__('The state has been saved.'), array('action' => 'index')); - } - } - } + if ($this->Session->Read('systemPermission') != 'Edit') + { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + $this->State->create(); + if ($this->State->save($this->request->data)) { + return $this->flash(__('The state has been saved.'), array('action' => 'index')); + } + } +} /** * edit method @@ -81,26 +79,27 @@ public function beforeFilter() { * @param string $id * @return void */ - public function edit($id = null) { - if (!$this->State->exists($id)) { - throw new NotFoundException(__('Invalid state')); - } +public function edit($id = null) { + if (!$this->State->exists($id)) { + throw new NotFoundException(__('Invalid state')); + } - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } - if ($this->request->is(array('post', 'put'))) { - if ($this->State->save($this->request->data)) { - return $this->flash(__('The state has been saved.'), array('action' => 'index')); - } - } else { - $options = array('conditions' => array('State.' . $this->State->primaryKey => $id)); - $this->request->data = $this->State->find('first', $options); - } - } + if ( $this->request->is(array('post', 'put')) ) { + if ( $this->State->save($this->request->data) ) { + return $this->flash(__('The state has been saved.'), array('action' => 'index')); + } + } else { + $options = array('conditions' => array('State.' . $this->State->primaryKey => $id)); + $this->request->data = $this->State->find('first', $options); + } +} /** * delete method @@ -109,48 +108,50 @@ public function beforeFilter() { * @param string $id * @return void */ - public function delete($id = null) { - $this->State->id = $id; - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } +public function delete($id = null) { + $this->State->id = $id; + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } - if (!$this->State->exists()) { - throw new NotFoundException(__('Invalid state')); - } - $this->request->allowMethod('post', 'delete'); - if ($this->State->delete()) { - return $this->flash(__('The state has been deleted.'), array('action' => 'index')); - } else { - return $this->flash(__('The state could not be deleted. Please, try again.'), array('action' => 'index')); - } - } + if (!$this->State->exists()) { + throw new NotFoundException(__('Invalid state')); + } + $this->request->allowMethod('post', 'delete'); + if ($this->State->delete()) { + return $this->flash(__('The state has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The state could not be deleted. Please, try again.'), array('action' => 'index')); + } +} - public function change() { - if ($this->Session->Read('systemPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } +public function change() { + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } - $newState = $this->request->params['pass'][0]; - $blah = $this->packageControl($newState); + $newState = $this->request->params['pass'][0]; + $blah = $this->packageControl($newState); - $this->set(array( - 'blah' => $blah, - '_serialize' => array('blah') - )); - } + $this->set(array( + 'blah' => $blah, + '_serialize' => array('blah') + )); +} - public function packageControl( $command ) { - $zm_path_bin = Configure::read('ZM_PATH_BIN'); - $string = $zm_path_bin.'/zmpkg.pl '.escapeshellarg( $command ); - $status = exec( $string ); +public function packageControl( $command ) { + $zm_path_bin = Configure::read('ZM_PATH_BIN'); + $string = $zm_path_bin.'/zmpkg.pl '.escapeshellarg( $command ); + $status = exec( $string ); - return $status; - } + return $status; +} } diff --git a/web/api/app/Controller/UsersController.php b/web/api/app/Controller/UsersController.php new file mode 100644 index 000000000..6b691cee1 --- /dev/null +++ b/web/api/app/Controller/UsersController.php @@ -0,0 +1,172 @@ +User->recursive = 0; + + $users = $this->Paginator->paginate('User'); + + $this->set(compact('users')); + } + +/** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { + $this->loadModel('Config'); + $configs = $this->Config->find('list', array( + 'fields' => array('Name', 'Value'), + 'conditions' => array('Name' => array('ZM_DIR_EVENTS')) + )); + + $this->User->recursive = 1; + if (!$this->User->exists($id)) { + throw new NotFoundException(__('Invalid user')); + } + $options = array('conditions' => array('User.' . $this->User->primaryKey => $id)); + $user = $this->User->find('first', $options); + + $this->set(array( + 'user' => $user, + '_serialize' => array('user') + )); + } + +/** + * add method + * + * @return void + */ + public function add() { + if ($this->request->is('post')) { + $this->User->create(); + if ($this->User->save($this->request->data)) { + return $this->flash(__('The user has been saved.'), array('action' => 'index')); + } + $this->Session->setFlash( + __('The user could not be saved. Please, try again.') + ); + } + } + +/** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + $this->User->id = $id; + + if (!$this->User->exists($id)) { + throw new NotFoundException(__('Invalid user')); + } + + if ($this->request->is('post') || $this->request->is('put')) { + if ($this->User->save($this->request->data)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + } else { + $this->request->data = $this->User->read(null, $id); + unset($this->request->data['User']['password']); + } + + $this->set(array( + 'message' => $message, + '_serialize' => array('message') + )); + } + +/** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + $this->User->id = $id; + if (!$this->User->exists()) { + throw new NotFoundException(__('Invalid user')); + } + $this->request->allowMethod('post', 'delete'); + if ($this->User->delete()) { + $message = 'The user has been deleted.'; + } else { + $message = 'The user could not be deleted. Please, try again.'; + } + $this->set(array( + 'message' => $message, + '_serialize' => array('message') + )); + } + + public function beforeFilter() { + parent::beforeFilter(); + + $this->loadModel('Config'); + $configs = $this->Config->find('list', array( + 'fields' => array('Name', 'Value'), + 'conditions' => array('Name' => array('ZM_OPT_USE_AUTH')) + )); + if ( $configs['ZM_OPT_USE_AUTH'] ) { + $this->Auth->allow('add','logout'); + } else { + $this->Auth->allow(); + } + } + + public function login() { + $this->loadModel('Config'); + $configs = $this->Config->find('list', array( + 'fields' => array('Name', 'Value'), + 'conditions' => array('Name' => array('ZM_OPT_USE_AUTH')) + )); + + if ( ! $configs['ZM_OPT_USE_AUTH'] ) { + $this->set(array( + 'message' => 'Login is not required.', + '_serialize' => array('message') + )); + return; + } + + if ($this->request->is('post')) { + if ($this->Auth->login()) { + return $this->redirect($this->Auth->redirectUrl()); + } + $this->Session->setFlash(__('Invalid username or password, try again')); + } + } + + public function logout() { + return $this->redirect($this->Auth->logout()); + } + +} diff --git a/web/api/app/Controller/ZonePresetsController.php b/web/api/app/Controller/ZonePresetsController.php index b89a6c75d..e26732eb8 100644 --- a/web/api/app/Controller/ZonePresetsController.php +++ b/web/api/app/Controller/ZonePresetsController.php @@ -8,92 +8,93 @@ App::uses('AppController', 'Controller'); */ class ZonePresetsController extends AppController { -/** - * Components - * - * @var array - */ - public $components = array('RequestHandler'); + /** + * Components + * + * @var array + */ + public $components = array('RequestHandler'); -/** - * index method - * - * @return void - */ - public function index() { - $zonePresets = $this->ZonePreset->find('all'); - $this->set(array( - 'zonePresets' => $zonePresets, - '_serialize' => array('zonePresets') - )); - } + /** + * index method + * + * @return void + */ + public function index() { + $zonePresets = $this->ZonePreset->find('all'); + $this->set(array( + 'zonePresets' => $zonePresets, + '_serialize' => array('zonePresets') + )); + } -/** - * view method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function view($id = null) { - if (!$this->ZonePreset->exists($id)) { - throw new NotFoundException(__('Invalid zone preset')); - } - $options = array('conditions' => array('ZonePreset.' . $this->ZonePreset->primaryKey => $id)); - $this->set('zonePreset', $this->ZonePreset->find('first', $options)); - } + /** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { + if ( !$this->ZonePreset->exists($id) ) { + throw new NotFoundException(__('Invalid zone preset')); + } + $options = array('conditions' => array('ZonePreset.' . $this->ZonePreset->primaryKey => $id)); + $this->set('zonePreset', $this->ZonePreset->find('first', $options)); + } -/** - * add method - * - * @return void - */ - public function add() { - if ($this->request->is('post')) { - $this->ZonePreset->create(); - if ($this->ZonePreset->save($this->request->data)) { - return $this->flash(__('The zone preset has been saved.'), array('action' => 'index')); - } - } - } + /** + * add method + * + * @return void + */ + public function add() { + if ( $this->request->is('post') ) { + $this->ZonePreset->create(); + if ( $this->ZonePreset->save($this->request->data) ) { + return $this->flash(__('The zone preset has been saved.'), array('action' => 'index')); + } + } + } -/** - * edit method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function edit($id = null) { - if (!$this->ZonePreset->exists($id)) { - throw new NotFoundException(__('Invalid zone preset')); - } - if ($this->request->is(array('post', 'put'))) { - if ($this->ZonePreset->save($this->request->data)) { - return $this->flash(__('The zone preset has been saved.'), array('action' => 'index')); - } - } else { - $options = array('conditions' => array('ZonePreset.' . $this->ZonePreset->primaryKey => $id)); - $this->request->data = $this->ZonePreset->find('first', $options); - } - } + /** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + if ( !$this->ZonePreset->exists($id) ) { + throw new NotFoundException(__('Invalid zone preset')); + } + if ( $this->request->is(array('post', 'put')) ) { + if ( $this->ZonePreset->save($this->request->data) ) { + return $this->flash(__('The zone preset has been saved.'), array('action' => 'index')); + } + } else { + $options = array('conditions' => array('ZonePreset.' . $this->ZonePreset->primaryKey => $id)); + $this->request->data = $this->ZonePreset->find('first', $options); + } + } -/** - * delete method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function delete($id = null) { - $this->ZonePreset->id = $id; - if (!$this->ZonePreset->exists()) { - throw new NotFoundException(__('Invalid zone preset')); - } - $this->request->allowMethod('post', 'delete'); - if ($this->ZonePreset->delete()) { - return $this->flash(__('The zone preset has been deleted.'), array('action' => 'index')); - } else { - return $this->flash(__('The zone preset could not be deleted. Please, try again.'), array('action' => 'index')); - } - }} + /** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + $this->ZonePreset->id = $id; + if ( !$this->ZonePreset->exists() ) { + throw new NotFoundException(__('Invalid zone preset')); + } + $this->request->allowMethod('post', 'delete'); + if ( $this->ZonePreset->delete() ) { + return $this->flash(__('The zone preset has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The zone preset could not be deleted. Please, try again.'), array('action' => 'index')); + } + } +} // end class ZonePresetsController diff --git a/web/api/app/Controller/ZonesController.php b/web/api/app/Controller/ZonesController.php index 5fbadc2ed..74a87f353 100644 --- a/web/api/app/Controller/ZonesController.php +++ b/web/api/app/Controller/ZonesController.php @@ -7,148 +7,163 @@ App::uses('AppController', 'Controller'); */ class ZonesController extends AppController { -/** - * Components - * - * @var array - */ -public $components = array('RequestHandler'); + /** + * Components + * + * @var array + */ + public $components = array('RequestHandler'); -public function beforeFilter() { - parent::beforeFilter(); - $canView = $this->Session->Read('monitorPermission'); - if ($canView =='None') - { - throw new UnauthorizedException(__('Insufficient Privileges')); - return; - } + public function beforeFilter() { + parent::beforeFilter(); -} + global $user; + $canView = (!$user) || $user['Monitors'] != 'None'; + if ( !$canView ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + } -// Find all zones which belong to a MonitorId -public function forMonitor($id = null) { + // Find all zones which belong to a MonitorId + public function forMonitor($id = null) { $this->loadModel('Monitor'); - if (!$this->Monitor->exists($id)) { - throw new NotFoundException(__('Invalid monitor')); + if ( !$this->Monitor->exists($id) ) { + throw new NotFoundException(__('Invalid monitor')); } $this->Zone->recursive = -1; $zones = $this->Zone->find('all', array( - 'conditions' => array('MonitorId' => $id) + 'conditions' => array('MonitorId' => $id) )); $this->set(array( - 'zones' => $zones, - '_serialize' => array('zones') + 'zones' => $zones, + '_serialize' => array('zones') )); -} -public function index() { + } + public function index() { $this->Zone->recursive = -1; - - $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); - if (!empty($allowedMonitors)) - { - $mon_options = array('Zones.MonitorId' => $allowedMonitors); - } - else - { - $mon_options=''; + + global $user; + $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'],NULL, PREG_SPLIT_NO_EMPTY) : null; + if ( $allowedMonitors ) { + $mon_options = array('Zones.MonitorId' => $allowedMonitors); + } else { + $mon_options = ''; } $zones = $this->Zone->find('all',$mon_options); $this->set(array( - 'zones' => $zones, - '_serialize' => array('zones') + 'zones' => $zones, + '_serialize' => array('zones') )); -} -/** - * add method - * - * @return void - */ - public function add() { - if ($this->request->is('post')) { - $this->Zone->create(); - if ($this->Zone->save($this->request->data)) { - return $this->flash(__('The zone has been saved.'), array('action' => 'index')); - } - } - $monitors = $this->Zone->Monitor->find('list'); - $this->set(compact('monitors')); + } + + /** + * add method + * + * @return void + */ + public function add() { + if ( $this->request->is('post') ) { + + global $user; + $canEdit = (!$user) || $user['Monitors'] == 'Edit'; + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + + $this->Zone->create(); + if ( $this->Zone->save($this->request->data) ) { + return $this->flash(__('The zone has been saved.'), array('action' => 'index')); + } + } + $monitors = $this->Zone->Monitor->find('list'); + $this->set(compact('monitors')); + } + + /** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + $this->Zone->id = $id; + + if ( !$this->Zone->exists($id) ) { + throw new NotFoundException(__('Invalid zone')); + } + if ( $this->request->is(array('post', 'put')) ) { + global $user; + $canEdit = (!$user) || $user['Monitors'] == 'Edit'; + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + if ( $this->Zone->save($this->request->data) ) { + return $this->flash(__('The zone has been saved.'), array('action' => 'index')); + } + } else { + $options = array('conditions' => array('Zone.' . $this->Zone->primaryKey => $id)); + $this->request->data = $this->Zone->find('first', $options); + } + $monitors = $this->Zone->Monitor->find('list'); + $this->set(compact('monitors')); + } + + /** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + $this->Zone->id = $id; + if ( !$this->Zone->exists() ) { + throw new NotFoundException(__('Invalid zone')); + } + $this->request->allowMethod('post', 'delete'); + global $user; + $canEdit = (!$user) || $user['Monitors'] == 'Edit'; + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + if ( $this->Zone->delete() ) { + return $this->flash(__('The zone has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The zone could not be deleted. Please, try again.'), array('action' => 'index')); + } + } + + public function createZoneImage($id = null) { + $this->loadModel('Monitor'); + $this->Monitor->id = $id; + if ( !$this->Monitor->exists() ) { + throw new NotFoundException(__('Invalid zone')); } -/** - * edit method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function edit($id = null) { - $this->Zone->id = $id; + $this->loadModel('Config'); + $zm_dir_images = $this->Config->find('list', array( + 'conditions' => array('Name' => 'ZM_DIR_IMAGES'), + 'fields' => array('Name', 'Value') + )); - if (!$this->Zone->exists($id)) { - throw new NotFoundException(__('Invalid zone')); - } - if ($this->request->is(array('post', 'put'))) { - if ($this->Zone->save($this->request->data)) { - return $this->flash(__('The zone has been saved.'), array('action' => 'index')); - } - } else { - $options = array('conditions' => array('Zone.' . $this->Zone->primaryKey => $id)); - $this->request->data = $this->Zone->find('first', $options); - } - $monitors = $this->Zone->Monitor->find('list'); - $this->set(compact('monitors')); - } + $zm_dir_images = $zm_dir_images['ZM_DIR_IMAGES']; + $zm_path_web = Configure::read('ZM_PATH_WEB'); + $zm_path_bin = Configure::read('ZM_PATH_BIN'); + $images_path = "$zm_path_web/$zm_dir_images"; -/** - * delete method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function delete($id = null) { - $this->Zone->id = $id; - if (!$this->Zone->exists()) { - throw new NotFoundException(__('Invalid zone')); - } - $this->request->allowMethod('post', 'delete'); - if ($this->Zone->delete()) { - return $this->flash(__('The zone has been deleted.'), array('action' => 'index')); - } else { - return $this->flash(__('The zone could not be deleted. Please, try again.'), array('action' => 'index')); - } - } + chdir($images_path); + $command = escapeshellcmd("$zm_path_bin/zmu -z -m $id"); + system($command, $status); - - public function createZoneImage( $id = null ) { - $this->loadModel('Monitor'); - $this->Monitor->id = $id; - if (!$this->Monitor->exists()) { - throw new NotFoundException(__('Invalid zone')); - } - - - $this->loadModel('Config'); - $zm_dir_images = $this->Config->find('list', array( - 'conditions' => array('Name' => 'ZM_DIR_IMAGES'), - 'fields' => array('Name', 'Value') - )); - - $zm_dir_images = $zm_dir_images['ZM_DIR_IMAGES']; - $zm_path_web = Configure::read('ZM_PATH_WEB'); - $zm_path_bin = Configure::read('ZM_PATH_BIN'); - $images_path = "$zm_path_web/$zm_dir_images"; - - chdir($images_path); - - $command = escapeshellcmd("$zm_path_bin/zmu -z -m $id"); - system( $command, $status ); - - $this->set(array( - 'status' => $status, - '_serialize' => array('status') - )); - - } -} + $this->set(array( + 'status' => $status, + '_serialize' => array('status') + )); + } +} // end class diff --git a/web/api/app/Model/User.php b/web/api/app/Model/User.php index 8ef18d131..9137ebfc5 100644 --- a/web/api/app/Model/User.php +++ b/web/api/app/Model/User.php @@ -1,12 +1,28 @@ array( + 'required' => array( + 'rule' => array('notEmpty'), + 'message' => 'A username is required' + ) + ), + 'Password' => array( + 'required' => array( + 'rule' => array('notEmpty'), + 'message' => 'A password is required' + ) + ) + ); + /** * Use table * @@ -26,6 +42,48 @@ class User extends AppModel { * * @var string */ - public $displayField = 'Name'; + public $displayField = 'Username'; + + + //The Associations below have been created with all possible keys, those that are not needed can be removed + +/** + * belongsTo associations + * + * @var array + */ + public $belongsTo = array( + /*'Monitor' => array( + 'className' => 'Monitor', + 'foreignKey' => 'MonitorId', + 'conditions' => '', + 'fields' => '', + 'order' => '' + ) +*/ + ); + +/** + * hasMany associations + * + * @var array + */ + public $hasMany = array( +/* + 'Frame' => array( + 'className' => 'Frame', + 'foreignKey' => 'UserId', + 'dependent' => true, + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ) +*/ + ); } diff --git a/web/api/app/View/Users/add.ctp b/web/api/app/View/Users/add.ctp new file mode 100644 index 000000000..fc374d5ca --- /dev/null +++ b/web/api/app/View/Users/add.ctp @@ -0,0 +1,15 @@ + +
+Form->create('User'); ?> +
+ + Form->input('username'); + echo $this->Form->input('password'); + echo $this->Form->input('role', array( + 'options' => array('admin' => 'Admin', 'author' => 'Author') + )); + ?> +
+Form->end(__('Submit')); ?> +
+ diff --git a/web/api/app/View/Users/json/index.ctp b/web/api/app/View/Users/json/index.ctp new file mode 100644 index 000000000..b425392b0 --- /dev/null +++ b/web/api/app/View/Users/json/index.ctp @@ -0,0 +1,5 @@ +Paginator->params(); + echo json_encode($array); +?> diff --git a/web/api/app/View/Users/json/view.ctp b/web/api/app/View/Users/json/view.ctp new file mode 100644 index 000000000..35fe5faa9 --- /dev/null +++ b/web/api/app/View/Users/json/view.ctp @@ -0,0 +1 @@ +echo json_encode($user); diff --git a/web/api/app/View/Users/login.ctp b/web/api/app/View/Users/login.ctp new file mode 100644 index 000000000..335ad3108 --- /dev/null +++ b/web/api/app/View/Users/login.ctp @@ -0,0 +1,13 @@ +
+Session->flash('auth'); ?> +Form->create('User'); ?> +
+ + + + Form->input('username'); + echo $this->Form->input('password'); + ?> +
+Form->end(__('Login')); ?> +
diff --git a/web/api/app/View/Users/xml/index.ctp b/web/api/app/View/Users/xml/index.ctp new file mode 100644 index 000000000..af960238f --- /dev/null +++ b/web/api/app/View/Users/xml/index.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $events)); +echo $xml->asXML(); diff --git a/web/api/app/View/Users/xml/view.ctp b/web/api/app/View/Users/xml/view.ctp new file mode 100644 index 000000000..7f64e422f --- /dev/null +++ b/web/api/app/View/Users/xml/view.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $event)); +echo $xml->asXML(); diff --git a/web/includes/Control.php b/web/includes/Control.php index de5f221ff..74beeca05 100644 --- a/web/includes/Control.php +++ b/web/includes/Control.php @@ -12,6 +12,9 @@ private $defaults = array( 'CanMoveRel' => 0, 'CanMoveCon' => 0, 'CanPan' => 0, + 'CanReset' => 0, + 'CanSleep' => 0, + 'CanWake' => 0, 'MinPanRange' => NULL, 'MaxPanRange' => NULL, 'MinPanStep' => NULL, @@ -103,7 +106,7 @@ private $defaults = array( if ( $IdOrRow ) { $row = NULL; if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { - $row = dbFetchOne( 'SELECT * FROM Control WHERE Id=?', NULL, array( $IdOrRow ) ); + $row = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Control record for Id=" . $IdOrRow ); } diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index bc9d92f8d..13cad6d1e 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -9,17 +9,19 @@ private $defaults = array( 'Name' => '', 'StorageId' => 0, 'ServerId' => 0, + 'Type' => 'Ffmpeg', 'Function' => 'None', 'Enabled' => 1, + 'LinkedMonitors' => null, 'Width' => null, 'Height' => null, 'Orientation' => null, 'AnalysisFPSLimit' => null, 'ZoneCount' => 0, 'Triggers' => null, - 'Type' => 'Ffmpeg', 'MaxFPS' => null, 'AlarmMaxFPS' => null, + 'Refresh' => null, ); private $status_fields = array( 'AnalysisFPS' => null, diff --git a/web/includes/Storage.php b/web/includes/Storage.php index d3f79c0d9..70f2f831d 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -140,20 +140,31 @@ class Storage { return $usage; } public function disk_total_space() { - if ( ! array_key_exists('disk_total_space', $this) ) { - $this->{'disk_total_space'} = disk_total_space($this->Path()); + if ( !array_key_exists('disk_total_space', $this) ) { + $path = $this->Path(); + if ( file_exists($path) ) { + $this->{'disk_total_space'} = disk_total_space($path); + } else { + Error("Path $path does not exist."); + $this->{'disk_total_space'} = 0; + } } return $this->{'disk_total_space'}; } public function disk_used_space() { # This isn't a function like this in php, so we have to add up the space used in each event. - if ( (! array_key_exists('disk_used_space', $this)) or (!$this->{'disk_used_space'}) ) { + if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) { if ( $this->{'Type'} == 's3fs' ) { $this->{'disk_used_space'} = $this->disk_event_space(); } else { $path = $this->Path(); - $this->{'disk_used_space'} = disk_total_space($path) - disk_free_space($path); + if ( file_exists($path) ) { + $this->{'disk_used_space'} = disk_total_space($path) - disk_free_space($path); + } else { + Error("Path $path does not exist."); + $this->{'disk_used_space'} = 0; + } } } return $this->{'disk_used_space'}; diff --git a/web/includes/actions.php b/web/includes/actions.php index f776e080c..a38515f3d 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -114,23 +114,23 @@ if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 're // as it produces the same error as when you don't answer a recaptcha if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) { if (!in_array('invalid-input-secret',$responseData['error-codes'])) { - Error ('reCaptcha authentication failed'); + Error('reCaptcha authentication failed'); userLogout(); $view='login'; $refreshParent = true; + return; } else { //Let them login but show an error echo ''; - Error ('Invalid recaptcha secret detected'); + Error('Invalid recaptcha secret detected'); } } } // end if success==false - } // end if using reCaptcha - $username = validStr( $_REQUEST['username'] ); + $username = validStr($_REQUEST['username']); $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; - userLogin( $username, $password ); + userLogin($username, $password); $refreshParent = true; $view = 'console'; $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; diff --git a/web/includes/auth.php b/web/includes/auth.php index b14323103..9cf8d37c1 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -19,7 +19,7 @@ // function userLogin($username, $password='', $passwordHashed=false) { - global $user, $cookies; + global $user; $sql = 'SELECT * FROM Users WHERE Enabled=1'; $sql_values = NULL; @@ -34,7 +34,12 @@ function userLogin($username, $password='', $passwordHashed=false) { $sql .= ' AND Username=?'; $sql_values = array($username); } - session_start(); + $close_session = 0; + if ( !is_session_started() ) { + Logger::Debug("Starting session in userLogin"); + session_start(); + $close_session = 1; + } $_SESSION['username'] = $username; if ( ZM_AUTH_RELAY == 'plain' ) { // Need to save this in session @@ -54,7 +59,9 @@ function userLogin($username, $password='', $passwordHashed=false) { $_SESSION['loginFailed'] = true; unset($user); } - session_write_close(); + if ( $close_session ) + session_write_close(); + return isset($user) ? $user: null; } # end function userLogin function userLogout() { @@ -121,7 +128,11 @@ function generateAuthHash($useRemoteAddr, $force=false) { #Logger::Debug("Generated using hour:".$local_time[2] . ' mday:' . $local_time[3] . ' month:'.$local_time[4] . ' year: ' . $local_time[5] ); $auth = md5($authKey); if ( !$force ) { - session_start(); + $close_session = 0; + if ( !is_session_started() ) { + session_start(); + $close_session = 1; + } $_SESSION['AuthHash'] = $auth; $_SESSION['AuthHashGeneratedAt'] = $time; session_write_close(); @@ -155,4 +166,17 @@ function canEdit($area, $mid=false) { return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) )); } +function is_session_started() { + if ( php_sapi_name() !== 'cli' ) { + if ( version_compare(phpversion(), '5.4.0', '>=') ) { + return session_status() === PHP_SESSION_ACTIVE ? TRUE : FALSE; + } else { + return session_id() === '' ? FALSE : TRUE; + } + } else { + Warning("php_sapi_name === 'cli'"); + } + return FALSE; +} + ?> diff --git a/web/includes/config.php.in b/web/includes/config.php.in index c6aa93c63..a2a89b224 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -212,7 +212,7 @@ function process_configfile($configFile) { continue; elseif ( preg_match( '/^\s*#/', $str )) continue; - elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches )) + elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches )) $configvals[$matches[1]] = $matches[2]; } fclose( $cfg ); diff --git a/web/includes/functions.php b/web/includes/functions.php index 0600d4f60..09a86c03c 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -432,20 +432,38 @@ function htmlSelect( $name, $contents, $values, $behaviours=false ) { } } - return "'; + return "'; } -function htmlOptions( $contents, $values ) { - $html = ''; - foreach ( $contents as $value=>$text ) { - if ( is_array( $text ) ) - $text = $text['Name']; - else if ( is_object( $text ) ) - $text = $text->Name(); - $selected = is_array( $values ) ? in_array( $value, $values ) : !strcmp($value, $values); - $html .= ""; +function htmlOptions($contents, $values) { + $options_html = ''; + + foreach ( $contents as $value=>$option ) { + $disabled = 0; + $text = ''; + if ( is_array($option) ) { + + if ( isset($option['Name']) ) + $text = $option['Name']; + else if ( isset($option['text']) ) + $text = $option['text']; + + if ( isset($option['disabled']) ) { + $disabled = $option['disabled']; + Error("Setting to disabled"); + } + } else if ( is_object($option) ) { + $text = $option->Name(); + } else { + $text = $option; + } + $selected = is_array($values) ? in_array($value, $values) : !strcmp($value, $values); + $options_html .= ""; } - return $html; + return $options_html; } function truncText( $text, $length, $deslash=1 ) { @@ -2064,7 +2082,7 @@ function cache_bust( $file ) { $dirname = preg_replace( '/\//', '_', $parts['dirname'] ); $cacheFile = $dirname.'_'.$parts['filename'].'-'.$css.'-'.filemtime($file).'.'.$parts['extension']; if ( file_exists(ZM_DIR_CACHE.'/'.$cacheFile) or symlink(ZM_PATH_WEB.'/'.$file, ZM_DIR_CACHE.'/'.$cacheFile) ) { - return '/zm/cache/'.$cacheFile; + return 'cache/'.$cacheFile; } else { Warning("Failed linking $file to $cacheFile"); } diff --git a/web/index.php b/web/index.php index 4cc7cf75c..7f823f1fd 100644 --- a/web/index.php +++ b/web/index.php @@ -51,7 +51,12 @@ require_once( 'includes/Event.php' ); require_once( 'includes/Group.php' ); require_once( 'includes/Monitor.php' ); -if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) { + +if ( + (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') + or + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) +) { $protocol = 'https'; } else { $protocol = 'http'; diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index b945c9bba..06b02c3a4 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -663,6 +663,7 @@ $SLANG = array( 'ShowFilterWindow' => 'Show Filter Window', 'ShowTimeline' => 'Show Timeline', 'SignalCheckColour' => 'Signal Check Colour', + 'SignalCheckPoints' => 'Signal Check Points', 'Size' => 'Size', 'SkinDescription' => 'Change the skin for this session', 'CSSDescription' => 'Change the css for this session', @@ -697,6 +698,7 @@ $SLANG = array( 'Stills' => 'Stills', 'Stopped' => 'Stopped', 'Stop' => 'Stop', + 'StorageArea' => 'Storage Area', 'StorageScheme' => 'Scheme', 'StreamReplayBuffer' => 'Stream Replay Image Buffer', 'Stream' => 'Stream', diff --git a/web/skins/classic/js/base.js b/web/skins/classic/js/base.js index b19ca29a0..9eb93ee77 100644 --- a/web/skins/classic/js/base.js +++ b/web/skins/classic/js/base.js @@ -41,7 +41,7 @@ var popupSizes = { 'filter': { 'width': 900, 'height': 700 }, 'frame': { 'addWidth': 32, 'minWidth': 384, 'addHeight': 200 }, 'frames': { 'width': 600, 'height': 600 }, - 'function': { 'width': 350, 'height': 160 }, + 'function': { 'width': 350, 'height': 260 }, 'group': { 'width': 760, 'height': 600 }, 'groups': { 'width': 540, 'height': 420 }, 'image': { 'addWidth': 48, 'addHeight': 80 }, @@ -54,7 +54,7 @@ var popupSizes = { 'monitorselect':{ 'width': 160, 'height': 200 }, 'montage': { 'width': -1, 'height': -1 }, 'onvifprobe': { 'width': 700, 'height': 550 }, - 'optionhelp': { 'width': 400, 'height': 320 }, + 'optionhelp': { 'width': 400, 'height': 400 }, 'options': { 'width': 1000, 'height': 660 }, 'preset': { 'width': 300, 'height': 220 }, 'server': { 'width': 600, 'height': 405 }, diff --git a/web/skins/classic/js/chosen/bower.json b/web/skins/classic/js/chosen/bower.json deleted file mode 100644 index 273110bd7..000000000 --- a/web/skins/classic/js/chosen/bower.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "chosen", - "description": "Chosen is a JavaScript plugin that makes select boxes user-friendly. It is currently available in both jQuery and Prototype flavors.", - "keywords": [ - "select", - "multiselect", - "dropdown", - "form", - "input", - "ui" - ], - "homepage": "https://harvesthq.github.io/chosen/", - "license": "https://github.com/harvesthq/chosen/blob/master/LICENSE.md", - "authors": [ - { - "name": "Patrick Filler", - "url": "https://github.com/pfiller" - }, - { - "name": "Christophe Coevoet", - "url": "https://github.com/stof" - }, - { - "name": "Ken Earley", - "url": "https://github.com/kenearley" - }, - { - "name": "Koen Punt", - "url": "https://github.com/koenpunt" - } - ], - "dependencies": {}, - "main": [ - "chosen.jquery.js", - "chosen.css" - ], - "ignore": [], - "repository": { - "type": "git", - "url": "https://github.com/harvesthq/chosen.git" - } -} diff --git a/web/skins/classic/js/chosen/chosen.proto.js b/web/skins/classic/js/chosen/chosen.proto.js deleted file mode 100644 index ea517ad4f..000000000 --- a/web/skins/classic/js/chosen/chosen.proto.js +++ /dev/null @@ -1,1389 +0,0 @@ -/*! -Chosen, a Select Box Enhancer for jQuery and Prototype -by Patrick Filler for Harvest, http://getharvest.com - -Version 1.8.2 -Full source at https://github.com/harvesthq/chosen -Copyright (c) 2011-2017 Harvest http://getharvest.com - -MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md -This file is generated by `grunt build`, do not edit it by hand. -*/ - -(function() { - var AbstractChosen, SelectParser, - bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - SelectParser = (function() { - function SelectParser() { - this.options_index = 0; - this.parsed = []; - } - - SelectParser.prototype.add_node = function(child) { - if (child.nodeName.toUpperCase() === "OPTGROUP") { - return this.add_group(child); - } else { - return this.add_option(child); - } - }; - - SelectParser.prototype.add_group = function(group) { - var group_position, i, len, option, ref, results1; - group_position = this.parsed.length; - this.parsed.push({ - array_index: group_position, - group: true, - label: group.label, - title: group.title ? group.title : void 0, - children: 0, - disabled: group.disabled, - classes: group.className - }); - ref = group.childNodes; - results1 = []; - for (i = 0, len = ref.length; i < len; i++) { - option = ref[i]; - results1.push(this.add_option(option, group_position, group.disabled)); - } - return results1; - }; - - SelectParser.prototype.add_option = function(option, group_position, group_disabled) { - if (option.nodeName.toUpperCase() === "OPTION") { - if (option.text !== "") { - if (group_position != null) { - this.parsed[group_position].children += 1; - } - this.parsed.push({ - array_index: this.parsed.length, - options_index: this.options_index, - value: option.value, - text: option.text, - html: option.innerHTML, - title: option.title ? option.title : void 0, - selected: option.selected, - disabled: group_disabled === true ? group_disabled : option.disabled, - group_array_index: group_position, - group_label: group_position != null ? this.parsed[group_position].label : null, - classes: option.className, - style: option.style.cssText - }); - } else { - this.parsed.push({ - array_index: this.parsed.length, - options_index: this.options_index, - empty: true - }); - } - return this.options_index += 1; - } - }; - - return SelectParser; - - })(); - - SelectParser.select_to_array = function(select) { - var child, i, len, parser, ref; - parser = new SelectParser(); - ref = select.childNodes; - for (i = 0, len = ref.length; i < len; i++) { - child = ref[i]; - parser.add_node(child); - } - return parser.parsed; - }; - - AbstractChosen = (function() { - function AbstractChosen(form_field, options1) { - this.form_field = form_field; - this.options = options1 != null ? options1 : {}; - this.label_click_handler = bind(this.label_click_handler, this); - if (!AbstractChosen.browser_is_supported()) { - return; - } - this.is_multiple = this.form_field.multiple; - this.set_default_text(); - this.set_default_values(); - this.setup(); - this.set_up_html(); - this.register_observers(); - this.on_ready(); - } - - AbstractChosen.prototype.set_default_values = function() { - this.click_test_action = (function(_this) { - return function(evt) { - return _this.test_active_click(evt); - }; - })(this); - this.activate_action = (function(_this) { - return function(evt) { - return _this.activate_field(evt); - }; - })(this); - this.active_field = false; - this.mouse_on_container = false; - this.results_showing = false; - this.result_highlighted = null; - this.is_rtl = this.options.rtl || /\bchosen-rtl\b/.test(this.form_field.className); - this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false; - this.disable_search_threshold = this.options.disable_search_threshold || 0; - this.disable_search = this.options.disable_search || false; - this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true; - this.group_search = this.options.group_search != null ? this.options.group_search : true; - this.search_contains = this.options.search_contains || false; - this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true; - this.max_selected_options = this.options.max_selected_options || Infinity; - this.inherit_select_classes = this.options.inherit_select_classes || false; - this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true; - this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true; - this.include_group_label_in_selected = this.options.include_group_label_in_selected || false; - this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY; - this.case_sensitive_search = this.options.case_sensitive_search || false; - return this.hide_results_on_select = this.options.hide_results_on_select != null ? this.options.hide_results_on_select : true; - }; - - AbstractChosen.prototype.set_default_text = function() { - if (this.form_field.getAttribute("data-placeholder")) { - this.default_text = this.form_field.getAttribute("data-placeholder"); - } else if (this.is_multiple) { - this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text; - } else { - this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text; - } - this.default_text = this.escape_html(this.default_text); - return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text; - }; - - AbstractChosen.prototype.choice_label = function(item) { - if (this.include_group_label_in_selected && (item.group_label != null)) { - return "" + item.group_label + "" + item.html; - } else { - return item.html; - } - }; - - AbstractChosen.prototype.mouse_enter = function() { - return this.mouse_on_container = true; - }; - - AbstractChosen.prototype.mouse_leave = function() { - return this.mouse_on_container = false; - }; - - AbstractChosen.prototype.input_focus = function(evt) { - if (this.is_multiple) { - if (!this.active_field) { - return setTimeout(((function(_this) { - return function() { - return _this.container_mousedown(); - }; - })(this)), 50); - } - } else { - if (!this.active_field) { - return this.activate_field(); - } - } - }; - - AbstractChosen.prototype.input_blur = function(evt) { - if (!this.mouse_on_container) { - this.active_field = false; - return setTimeout(((function(_this) { - return function() { - return _this.blur_test(); - }; - })(this)), 100); - } - }; - - AbstractChosen.prototype.label_click_handler = function(evt) { - if (this.is_multiple) { - return this.container_mousedown(evt); - } else { - return this.activate_field(); - } - }; - - AbstractChosen.prototype.results_option_build = function(options) { - var content, data, data_content, i, len, ref, shown_results; - content = ''; - shown_results = 0; - ref = this.results_data; - for (i = 0, len = ref.length; i < len; i++) { - data = ref[i]; - data_content = ''; - if (data.group) { - data_content = this.result_add_group(data); - } else { - data_content = this.result_add_option(data); - } - if (data_content !== '') { - shown_results++; - content += data_content; - } - if (options != null ? options.first : void 0) { - if (data.selected && this.is_multiple) { - this.choice_build(data); - } else if (data.selected && !this.is_multiple) { - this.single_set_selected_text(this.choice_label(data)); - } - } - if (shown_results >= this.max_shown_results) { - break; - } - } - return content; - }; - - AbstractChosen.prototype.result_add_option = function(option) { - var classes, option_el; - if (!option.search_match) { - return ''; - } - if (!this.include_option_in_results(option)) { - return ''; - } - classes = []; - if (!option.disabled && !(option.selected && this.is_multiple)) { - classes.push("active-result"); - } - if (option.disabled && !(option.selected && this.is_multiple)) { - classes.push("disabled-result"); - } - if (option.selected) { - classes.push("result-selected"); - } - if (option.group_array_index != null) { - classes.push("group-option"); - } - if (option.classes !== "") { - classes.push(option.classes); - } - option_el = document.createElement("li"); - option_el.className = classes.join(" "); - option_el.style.cssText = option.style; - option_el.setAttribute("data-option-array-index", option.array_index); - option_el.innerHTML = option.highlighted_html || option.html; - if (option.title) { - option_el.title = option.title; - } - return this.outerHTML(option_el); - }; - - AbstractChosen.prototype.result_add_group = function(group) { - var classes, group_el; - if (!(group.search_match || group.group_match)) { - return ''; - } - if (!(group.active_options > 0)) { - return ''; - } - classes = []; - classes.push("group-result"); - if (group.classes) { - classes.push(group.classes); - } - group_el = document.createElement("li"); - group_el.className = classes.join(" "); - group_el.innerHTML = group.highlighted_html || this.escape_html(group.label); - if (group.title) { - group_el.title = group.title; - } - return this.outerHTML(group_el); - }; - - AbstractChosen.prototype.results_update_field = function() { - this.set_default_text(); - if (!this.is_multiple) { - this.results_reset_cleanup(); - } - this.result_clear_highlight(); - this.results_build(); - if (this.results_showing) { - return this.winnow_results(); - } - }; - - AbstractChosen.prototype.reset_single_select_options = function() { - var i, len, ref, result, results1; - ref = this.results_data; - results1 = []; - for (i = 0, len = ref.length; i < len; i++) { - result = ref[i]; - if (result.selected) { - results1.push(result.selected = false); - } else { - results1.push(void 0); - } - } - return results1; - }; - - AbstractChosen.prototype.results_toggle = function() { - if (this.results_showing) { - return this.results_hide(); - } else { - return this.results_show(); - } - }; - - AbstractChosen.prototype.results_search = function(evt) { - if (this.results_showing) { - return this.winnow_results(); - } else { - return this.results_show(); - } - }; - - AbstractChosen.prototype.winnow_results = function() { - var escapedQuery, fix, i, len, option, prefix, query, ref, regex, results, results_group, search_match, startpos, suffix, text; - this.no_results_clear(); - results = 0; - query = this.get_search_text(); - escapedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); - regex = this.get_search_regex(escapedQuery); - ref = this.results_data; - for (i = 0, len = ref.length; i < len; i++) { - option = ref[i]; - option.search_match = false; - results_group = null; - search_match = null; - option.highlighted_html = ''; - if (this.include_option_in_results(option)) { - if (option.group) { - option.group_match = false; - option.active_options = 0; - } - if ((option.group_array_index != null) && this.results_data[option.group_array_index]) { - results_group = this.results_data[option.group_array_index]; - if (results_group.active_options === 0 && results_group.search_match) { - results += 1; - } - results_group.active_options += 1; - } - text = option.group ? option.label : option.text; - if (!(option.group && !this.group_search)) { - search_match = this.search_string_match(text, regex); - option.search_match = search_match != null; - if (option.search_match && !option.group) { - results += 1; - } - if (option.search_match) { - if (query.length) { - startpos = search_match.index; - prefix = text.slice(0, startpos); - fix = text.slice(startpos, startpos + query.length); - suffix = text.slice(startpos + query.length); - option.highlighted_html = (this.escape_html(prefix)) + "" + (this.escape_html(fix)) + "" + (this.escape_html(suffix)); - } - if (results_group != null) { - results_group.group_match = true; - } - } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) { - option.search_match = true; - } - } - } - } - this.result_clear_highlight(); - if (results < 1 && query.length) { - this.update_results_content(""); - return this.no_results(query); - } else { - this.update_results_content(this.results_option_build()); - return this.winnow_results_set_highlight(); - } - }; - - AbstractChosen.prototype.get_search_regex = function(escaped_search_string) { - var regex_flag, regex_string; - regex_string = this.search_contains ? escaped_search_string : "(^|\\s|\\b)" + escaped_search_string + "[^\\s]*"; - if (!(this.enable_split_word_search || this.search_contains)) { - regex_string = "^" + regex_string; - } - regex_flag = this.case_sensitive_search ? "" : "i"; - return new RegExp(regex_string, regex_flag); - }; - - AbstractChosen.prototype.search_string_match = function(search_string, regex) { - var match; - match = regex.exec(search_string); - if (!this.search_contains && (match != null ? match[1] : void 0)) { - match.index += 1; - } - return match; - }; - - AbstractChosen.prototype.choices_count = function() { - var i, len, option, ref; - if (this.selected_option_count != null) { - return this.selected_option_count; - } - this.selected_option_count = 0; - ref = this.form_field.options; - for (i = 0, len = ref.length; i < len; i++) { - option = ref[i]; - if (option.selected) { - this.selected_option_count += 1; - } - } - return this.selected_option_count; - }; - - AbstractChosen.prototype.choices_click = function(evt) { - evt.preventDefault(); - this.activate_field(); - if (!(this.results_showing || this.is_disabled)) { - return this.results_show(); - } - }; - - AbstractChosen.prototype.keydown_checker = function(evt) { - var ref, stroke; - stroke = (ref = evt.which) != null ? ref : evt.keyCode; - this.search_field_scale(); - if (stroke !== 8 && this.pending_backstroke) { - this.clear_backstroke(); - } - switch (stroke) { - case 8: - this.backstroke_length = this.get_search_field_value().length; - break; - case 9: - if (this.results_showing && !this.is_multiple) { - this.result_select(evt); - } - this.mouse_on_container = false; - break; - case 13: - if (this.results_showing) { - evt.preventDefault(); - } - break; - case 27: - if (this.results_showing) { - evt.preventDefault(); - } - break; - case 32: - if (this.disable_search) { - evt.preventDefault(); - } - break; - case 38: - evt.preventDefault(); - this.keyup_arrow(); - break; - case 40: - evt.preventDefault(); - this.keydown_arrow(); - break; - } - }; - - AbstractChosen.prototype.keyup_checker = function(evt) { - var ref, stroke; - stroke = (ref = evt.which) != null ? ref : evt.keyCode; - this.search_field_scale(); - switch (stroke) { - case 8: - if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) { - this.keydown_backstroke(); - } else if (!this.pending_backstroke) { - this.result_clear_highlight(); - this.results_search(); - } - break; - case 13: - evt.preventDefault(); - if (this.results_showing) { - this.result_select(evt); - } - break; - case 27: - if (this.results_showing) { - this.results_hide(); - } - break; - case 9: - case 16: - case 17: - case 18: - case 38: - case 40: - case 91: - break; - default: - this.results_search(); - break; - } - }; - - AbstractChosen.prototype.clipboard_event_checker = function(evt) { - if (this.is_disabled) { - return; - } - return setTimeout(((function(_this) { - return function() { - return _this.results_search(); - }; - })(this)), 50); - }; - - AbstractChosen.prototype.container_width = function() { - if (this.options.width != null) { - return this.options.width; - } else { - return this.form_field.offsetWidth + "px"; - } - }; - - AbstractChosen.prototype.include_option_in_results = function(option) { - if (this.is_multiple && (!this.display_selected_options && option.selected)) { - return false; - } - if (!this.display_disabled_options && option.disabled) { - return false; - } - if (option.empty) { - return false; - } - return true; - }; - - AbstractChosen.prototype.search_results_touchstart = function(evt) { - this.touch_started = true; - return this.search_results_mouseover(evt); - }; - - AbstractChosen.prototype.search_results_touchmove = function(evt) { - this.touch_started = false; - return this.search_results_mouseout(evt); - }; - - AbstractChosen.prototype.search_results_touchend = function(evt) { - if (this.touch_started) { - return this.search_results_mouseup(evt); - } - }; - - AbstractChosen.prototype.outerHTML = function(element) { - var tmp; - if (element.outerHTML) { - return element.outerHTML; - } - tmp = document.createElement("div"); - tmp.appendChild(element); - return tmp.innerHTML; - }; - - AbstractChosen.prototype.get_single_html = function() { - return "\n " + this.default_text + "\n
\n
\n
\n
\n \n
\n
    \n
    "; - }; - - AbstractChosen.prototype.get_multi_html = function() { - return "
      \n
    • \n \n
    • \n
    \n
    \n
      \n
      "; - }; - - AbstractChosen.prototype.get_no_results_html = function(terms) { - return "
    • \n " + this.results_none_found + " " + (this.escape_html(terms)) + "\n
    • "; - }; - - AbstractChosen.browser_is_supported = function() { - if ("Microsoft Internet Explorer" === window.navigator.appName) { - return document.documentMode >= 8; - } - if (/iP(od|hone)/i.test(window.navigator.userAgent) || /IEMobile/i.test(window.navigator.userAgent) || /Windows Phone/i.test(window.navigator.userAgent) || /BlackBerry/i.test(window.navigator.userAgent) || /BB10/i.test(window.navigator.userAgent) || /Android.*Mobile/i.test(window.navigator.userAgent)) { - return false; - } - return true; - }; - - AbstractChosen.default_multiple_text = "Select Some Options"; - - AbstractChosen.default_single_text = "Select an Option"; - - AbstractChosen.default_no_result_text = "No results match"; - - return AbstractChosen; - - })(); - - this.Chosen = (function(superClass) { - var triggerHtmlEvent; - - extend(Chosen, superClass); - - function Chosen() { - return Chosen.__super__.constructor.apply(this, arguments); - } - - Chosen.prototype.setup = function() { - return this.current_selectedIndex = this.form_field.selectedIndex; - }; - - Chosen.prototype.set_up_html = function() { - var container_classes, container_props; - container_classes = ["chosen-container"]; - container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single")); - if (this.inherit_select_classes && this.form_field.className) { - container_classes.push(this.form_field.className); - } - if (this.is_rtl) { - container_classes.push("chosen-rtl"); - } - container_props = { - 'class': container_classes.join(' '), - 'title': this.form_field.title - }; - if (this.form_field.id.length) { - container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen"; - } - this.container = new Element('div', container_props); - this.container.setStyle({ - width: this.container_width() - }); - if (this.is_multiple) { - this.container.update(this.get_multi_html()); - } else { - this.container.update(this.get_single_html()); - } - this.form_field.hide().insert({ - after: this.container - }); - this.dropdown = this.container.down('div.chosen-drop'); - this.search_field = this.container.down('input'); - this.search_results = this.container.down('ul.chosen-results'); - this.search_field_scale(); - this.search_no_results = this.container.down('li.no-results'); - if (this.is_multiple) { - this.search_choices = this.container.down('ul.chosen-choices'); - this.search_container = this.container.down('li.search-field'); - } else { - this.search_container = this.container.down('div.chosen-search'); - this.selected_item = this.container.down('.chosen-single'); - } - this.results_build(); - this.set_tab_index(); - return this.set_label_behavior(); - }; - - Chosen.prototype.on_ready = function() { - return this.form_field.fire("chosen:ready", { - chosen: this - }); - }; - - Chosen.prototype.register_observers = function() { - this.container.observe("touchstart", (function(_this) { - return function(evt) { - return _this.container_mousedown(evt); - }; - })(this)); - this.container.observe("touchend", (function(_this) { - return function(evt) { - return _this.container_mouseup(evt); - }; - })(this)); - this.container.observe("mousedown", (function(_this) { - return function(evt) { - return _this.container_mousedown(evt); - }; - })(this)); - this.container.observe("mouseup", (function(_this) { - return function(evt) { - return _this.container_mouseup(evt); - }; - })(this)); - this.container.observe("mouseenter", (function(_this) { - return function(evt) { - return _this.mouse_enter(evt); - }; - })(this)); - this.container.observe("mouseleave", (function(_this) { - return function(evt) { - return _this.mouse_leave(evt); - }; - })(this)); - this.search_results.observe("mouseup", (function(_this) { - return function(evt) { - return _this.search_results_mouseup(evt); - }; - })(this)); - this.search_results.observe("mouseover", (function(_this) { - return function(evt) { - return _this.search_results_mouseover(evt); - }; - })(this)); - this.search_results.observe("mouseout", (function(_this) { - return function(evt) { - return _this.search_results_mouseout(evt); - }; - })(this)); - this.search_results.observe("mousewheel", (function(_this) { - return function(evt) { - return _this.search_results_mousewheel(evt); - }; - })(this)); - this.search_results.observe("DOMMouseScroll", (function(_this) { - return function(evt) { - return _this.search_results_mousewheel(evt); - }; - })(this)); - this.search_results.observe("touchstart", (function(_this) { - return function(evt) { - return _this.search_results_touchstart(evt); - }; - })(this)); - this.search_results.observe("touchmove", (function(_this) { - return function(evt) { - return _this.search_results_touchmove(evt); - }; - })(this)); - this.search_results.observe("touchend", (function(_this) { - return function(evt) { - return _this.search_results_touchend(evt); - }; - })(this)); - this.form_field.observe("chosen:updated", (function(_this) { - return function(evt) { - return _this.results_update_field(evt); - }; - })(this)); - this.form_field.observe("chosen:activate", (function(_this) { - return function(evt) { - return _this.activate_field(evt); - }; - })(this)); - this.form_field.observe("chosen:open", (function(_this) { - return function(evt) { - return _this.container_mousedown(evt); - }; - })(this)); - this.form_field.observe("chosen:close", (function(_this) { - return function(evt) { - return _this.close_field(evt); - }; - })(this)); - this.search_field.observe("blur", (function(_this) { - return function(evt) { - return _this.input_blur(evt); - }; - })(this)); - this.search_field.observe("keyup", (function(_this) { - return function(evt) { - return _this.keyup_checker(evt); - }; - })(this)); - this.search_field.observe("keydown", (function(_this) { - return function(evt) { - return _this.keydown_checker(evt); - }; - })(this)); - this.search_field.observe("focus", (function(_this) { - return function(evt) { - return _this.input_focus(evt); - }; - })(this)); - this.search_field.observe("cut", (function(_this) { - return function(evt) { - return _this.clipboard_event_checker(evt); - }; - })(this)); - this.search_field.observe("paste", (function(_this) { - return function(evt) { - return _this.clipboard_event_checker(evt); - }; - })(this)); - if (this.is_multiple) { - return this.search_choices.observe("click", (function(_this) { - return function(evt) { - return _this.choices_click(evt); - }; - })(this)); - } else { - return this.container.observe("click", (function(_this) { - return function(evt) { - return evt.preventDefault(); - }; - })(this)); - } - }; - - Chosen.prototype.destroy = function() { - var event, i, len, ref; - this.container.ownerDocument.stopObserving("click", this.click_test_action); - ref = ['chosen:updated', 'chosen:activate', 'chosen:open', 'chosen:close']; - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; - this.form_field.stopObserving(event); - } - this.container.stopObserving(); - this.search_results.stopObserving(); - this.search_field.stopObserving(); - if (this.form_field_label != null) { - this.form_field_label.stopObserving(); - } - if (this.is_multiple) { - this.search_choices.stopObserving(); - this.container.select(".search-choice-close").each(function(choice) { - return choice.stopObserving(); - }); - } else { - this.selected_item.stopObserving(); - } - if (this.search_field.tabIndex) { - this.form_field.tabIndex = this.search_field.tabIndex; - } - this.container.remove(); - return this.form_field.show(); - }; - - Chosen.prototype.search_field_disabled = function() { - var ref; - this.is_disabled = this.form_field.disabled || ((ref = this.form_field.up('fieldset')) != null ? ref.disabled : void 0) || false; - if (this.is_disabled) { - this.container.addClassName('chosen-disabled'); - } else { - this.container.removeClassName('chosen-disabled'); - } - this.search_field.disabled = this.is_disabled; - if (!this.is_multiple) { - this.selected_item.stopObserving('focus', this.activate_field); - } - if (this.is_disabled) { - return this.close_field(); - } else if (!this.is_multiple) { - return this.selected_item.observe('focus', this.activate_field); - } - }; - - Chosen.prototype.container_mousedown = function(evt) { - var ref; - if (this.is_disabled) { - return; - } - if (evt && ((ref = evt.type) === 'mousedown' || ref === 'touchstart') && !this.results_showing) { - evt.preventDefault(); - } - if (!((evt != null) && evt.target.hasClassName("search-choice-close"))) { - if (!this.active_field) { - if (this.is_multiple) { - this.search_field.clear(); - } - this.container.ownerDocument.observe("click", this.click_test_action); - this.results_show(); - } else if (!this.is_multiple && evt && (evt.target === this.selected_item || evt.target.up("a.chosen-single"))) { - this.results_toggle(); - } - return this.activate_field(); - } - }; - - Chosen.prototype.container_mouseup = function(evt) { - if (evt.target.nodeName === "ABBR" && !this.is_disabled) { - return this.results_reset(evt); - } - }; - - Chosen.prototype.search_results_mousewheel = function(evt) { - var delta; - delta = evt.deltaY || -evt.wheelDelta || evt.detail; - if (delta != null) { - evt.preventDefault(); - if (evt.type === 'DOMMouseScroll') { - delta = delta * 40; - } - return this.search_results.scrollTop = delta + this.search_results.scrollTop; - } - }; - - Chosen.prototype.blur_test = function(evt) { - if (!this.active_field && this.container.hasClassName("chosen-container-active")) { - return this.close_field(); - } - }; - - Chosen.prototype.close_field = function() { - this.container.ownerDocument.stopObserving("click", this.click_test_action); - this.active_field = false; - this.results_hide(); - this.container.removeClassName("chosen-container-active"); - this.clear_backstroke(); - this.show_search_field_default(); - this.search_field_scale(); - return this.search_field.blur(); - }; - - Chosen.prototype.activate_field = function() { - if (this.is_disabled) { - return; - } - this.container.addClassName("chosen-container-active"); - this.active_field = true; - this.search_field.value = this.get_search_field_value(); - return this.search_field.focus(); - }; - - Chosen.prototype.test_active_click = function(evt) { - if (evt.target.up('.chosen-container') === this.container) { - return this.active_field = true; - } else { - return this.close_field(); - } - }; - - Chosen.prototype.results_build = function() { - this.parsing = true; - this.selected_option_count = null; - this.results_data = SelectParser.select_to_array(this.form_field); - if (this.is_multiple) { - this.search_choices.select("li.search-choice").invoke("remove"); - } else if (!this.is_multiple) { - this.single_set_selected_text(); - if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) { - this.search_field.readOnly = true; - this.container.addClassName("chosen-container-single-nosearch"); - } else { - this.search_field.readOnly = false; - this.container.removeClassName("chosen-container-single-nosearch"); - } - } - this.update_results_content(this.results_option_build({ - first: true - })); - this.search_field_disabled(); - this.show_search_field_default(); - this.search_field_scale(); - return this.parsing = false; - }; - - Chosen.prototype.result_do_highlight = function(el) { - var high_bottom, high_top, maxHeight, visible_bottom, visible_top; - this.result_clear_highlight(); - this.result_highlight = el; - this.result_highlight.addClassName("highlighted"); - maxHeight = parseInt(this.search_results.getStyle('maxHeight'), 10); - visible_top = this.search_results.scrollTop; - visible_bottom = maxHeight + visible_top; - high_top = this.result_highlight.positionedOffset().top; - high_bottom = high_top + this.result_highlight.getHeight(); - if (high_bottom >= visible_bottom) { - return this.search_results.scrollTop = (high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0; - } else if (high_top < visible_top) { - return this.search_results.scrollTop = high_top; - } - }; - - Chosen.prototype.result_clear_highlight = function() { - if (this.result_highlight) { - this.result_highlight.removeClassName('highlighted'); - } - return this.result_highlight = null; - }; - - Chosen.prototype.results_show = function() { - if (this.is_multiple && this.max_selected_options <= this.choices_count()) { - this.form_field.fire("chosen:maxselected", { - chosen: this - }); - return false; - } - this.container.addClassName("chosen-with-drop"); - this.results_showing = true; - this.search_field.focus(); - this.search_field.value = this.get_search_field_value(); - this.winnow_results(); - return this.form_field.fire("chosen:showing_dropdown", { - chosen: this - }); - }; - - Chosen.prototype.update_results_content = function(content) { - return this.search_results.update(content); - }; - - Chosen.prototype.results_hide = function() { - if (this.results_showing) { - this.result_clear_highlight(); - this.container.removeClassName("chosen-with-drop"); - this.form_field.fire("chosen:hiding_dropdown", { - chosen: this - }); - } - return this.results_showing = false; - }; - - Chosen.prototype.set_tab_index = function(el) { - var ti; - if (this.form_field.tabIndex) { - ti = this.form_field.tabIndex; - this.form_field.tabIndex = -1; - return this.search_field.tabIndex = ti; - } - }; - - Chosen.prototype.set_label_behavior = function() { - this.form_field_label = this.form_field.up("label"); - if (this.form_field_label == null) { - this.form_field_label = $$("label[for='" + this.form_field.id + "']").first(); - } - if (this.form_field_label != null) { - return this.form_field_label.observe("click", this.label_click_handler); - } - }; - - Chosen.prototype.show_search_field_default = function() { - if (this.is_multiple && this.choices_count() < 1 && !this.active_field) { - this.search_field.value = this.default_text; - return this.search_field.addClassName("default"); - } else { - this.search_field.value = ""; - return this.search_field.removeClassName("default"); - } - }; - - Chosen.prototype.search_results_mouseup = function(evt) { - var target; - target = evt.target.hasClassName("active-result") ? evt.target : evt.target.up(".active-result"); - if (target) { - this.result_highlight = target; - this.result_select(evt); - return this.search_field.focus(); - } - }; - - Chosen.prototype.search_results_mouseover = function(evt) { - var target; - target = evt.target.hasClassName("active-result") ? evt.target : evt.target.up(".active-result"); - if (target) { - return this.result_do_highlight(target); - } - }; - - Chosen.prototype.search_results_mouseout = function(evt) { - if (evt.target.hasClassName('active-result') || evt.target.up('.active-result')) { - return this.result_clear_highlight(); - } - }; - - Chosen.prototype.choice_build = function(item) { - var choice, close_link; - choice = new Element('li', { - "class": "search-choice" - }).update("" + (this.choice_label(item)) + ""); - if (item.disabled) { - choice.addClassName('search-choice-disabled'); - } else { - close_link = new Element('a', { - href: '#', - "class": 'search-choice-close', - rel: item.array_index - }); - close_link.observe("click", (function(_this) { - return function(evt) { - return _this.choice_destroy_link_click(evt); - }; - })(this)); - choice.insert(close_link); - } - return this.search_container.insert({ - before: choice - }); - }; - - Chosen.prototype.choice_destroy_link_click = function(evt) { - evt.preventDefault(); - evt.stopPropagation(); - if (!this.is_disabled) { - return this.choice_destroy(evt.target); - } - }; - - Chosen.prototype.choice_destroy = function(link) { - if (this.result_deselect(link.readAttribute("rel"))) { - if (this.active_field) { - this.search_field.focus(); - } else { - this.show_search_field_default(); - } - if (this.is_multiple && this.choices_count() > 0 && this.get_search_field_value().length < 1) { - this.results_hide(); - } - link.up('li').remove(); - return this.search_field_scale(); - } - }; - - Chosen.prototype.results_reset = function() { - this.reset_single_select_options(); - this.form_field.options[0].selected = true; - this.single_set_selected_text(); - this.show_search_field_default(); - this.results_reset_cleanup(); - this.trigger_form_field_change(); - if (this.active_field) { - return this.results_hide(); - } - }; - - Chosen.prototype.results_reset_cleanup = function() { - var deselect_trigger; - this.current_selectedIndex = this.form_field.selectedIndex; - deselect_trigger = this.selected_item.down("abbr"); - if (deselect_trigger) { - return deselect_trigger.remove(); - } - }; - - Chosen.prototype.result_select = function(evt) { - var high, item; - if (this.result_highlight) { - high = this.result_highlight; - this.result_clear_highlight(); - if (this.is_multiple && this.max_selected_options <= this.choices_count()) { - this.form_field.fire("chosen:maxselected", { - chosen: this - }); - return false; - } - if (this.is_multiple) { - high.removeClassName("active-result"); - } else { - this.reset_single_select_options(); - } - high.addClassName("result-selected"); - item = this.results_data[high.getAttribute("data-option-array-index")]; - item.selected = true; - this.form_field.options[item.options_index].selected = true; - this.selected_option_count = null; - this.search_field.value = ""; - if (this.is_multiple) { - this.choice_build(item); - } else { - this.single_set_selected_text(this.choice_label(item)); - } - if (this.is_multiple && (!this.hide_results_on_select || (evt.metaKey || evt.ctrlKey))) { - this.winnow_results(); - } else { - this.results_hide(); - this.show_search_field_default(); - } - if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) { - this.trigger_form_field_change(); - } - this.current_selectedIndex = this.form_field.selectedIndex; - evt.preventDefault(); - return this.search_field_scale(); - } - }; - - Chosen.prototype.single_set_selected_text = function(text) { - if (text == null) { - text = this.default_text; - } - if (text === this.default_text) { - this.selected_item.addClassName("chosen-default"); - } else { - this.single_deselect_control_build(); - this.selected_item.removeClassName("chosen-default"); - } - return this.selected_item.down("span").update(text); - }; - - Chosen.prototype.result_deselect = function(pos) { - var result_data; - result_data = this.results_data[pos]; - if (!this.form_field.options[result_data.options_index].disabled) { - result_data.selected = false; - this.form_field.options[result_data.options_index].selected = false; - this.selected_option_count = null; - this.result_clear_highlight(); - if (this.results_showing) { - this.winnow_results(); - } - this.trigger_form_field_change(); - this.search_field_scale(); - return true; - } else { - return false; - } - }; - - Chosen.prototype.single_deselect_control_build = function() { - if (!this.allow_single_deselect) { - return; - } - if (!this.selected_item.down("abbr")) { - this.selected_item.down("span").insert({ - after: "" - }); - } - return this.selected_item.addClassName("chosen-single-with-deselect"); - }; - - Chosen.prototype.get_search_field_value = function() { - return this.search_field.value; - }; - - Chosen.prototype.get_search_text = function() { - return this.get_search_field_value().strip(); - }; - - Chosen.prototype.escape_html = function(text) { - return text.escapeHTML(); - }; - - Chosen.prototype.winnow_results_set_highlight = function() { - var do_high; - if (!this.is_multiple) { - do_high = this.search_results.down(".result-selected.active-result"); - } - if (do_high == null) { - do_high = this.search_results.down(".active-result"); - } - if (do_high != null) { - return this.result_do_highlight(do_high); - } - }; - - Chosen.prototype.no_results = function(terms) { - this.search_results.insert(this.get_no_results_html(terms)); - return this.form_field.fire("chosen:no_results", { - chosen: this - }); - }; - - Chosen.prototype.no_results_clear = function() { - var nr, results1; - nr = null; - results1 = []; - while (nr = this.search_results.down(".no-results")) { - results1.push(nr.remove()); - } - return results1; - }; - - Chosen.prototype.keydown_arrow = function() { - var next_sib; - if (this.results_showing && this.result_highlight) { - next_sib = this.result_highlight.next('.active-result'); - if (next_sib) { - return this.result_do_highlight(next_sib); - } - } else { - return this.results_show(); - } - }; - - Chosen.prototype.keyup_arrow = function() { - var actives, prevs, sibs; - if (!this.results_showing && !this.is_multiple) { - return this.results_show(); - } else if (this.result_highlight) { - sibs = this.result_highlight.previousSiblings(); - actives = this.search_results.select("li.active-result"); - prevs = sibs.intersect(actives); - if (prevs.length) { - return this.result_do_highlight(prevs.first()); - } else { - if (this.choices_count() > 0) { - this.results_hide(); - } - return this.result_clear_highlight(); - } - } - }; - - Chosen.prototype.keydown_backstroke = function() { - var next_available_destroy; - if (this.pending_backstroke) { - this.choice_destroy(this.pending_backstroke.down("a")); - return this.clear_backstroke(); - } else { - next_available_destroy = this.search_container.siblings().last(); - if (next_available_destroy && next_available_destroy.hasClassName("search-choice") && !next_available_destroy.hasClassName("search-choice-disabled")) { - this.pending_backstroke = next_available_destroy; - if (this.pending_backstroke) { - this.pending_backstroke.addClassName("search-choice-focus"); - } - if (this.single_backstroke_delete) { - return this.keydown_backstroke(); - } else { - return this.pending_backstroke.addClassName("search-choice-focus"); - } - } - } - }; - - Chosen.prototype.clear_backstroke = function() { - if (this.pending_backstroke) { - this.pending_backstroke.removeClassName("search-choice-focus"); - } - return this.pending_backstroke = null; - }; - - Chosen.prototype.search_field_scale = function() { - var container_width, div, i, len, style, style_block, styles, width; - if (!this.is_multiple) { - return; - } - style_block = { - position: 'absolute', - left: '-1000px', - top: '-1000px', - display: 'none', - whiteSpace: 'pre' - }; - styles = ['fontSize', 'fontStyle', 'fontWeight', 'fontFamily', 'lineHeight', 'textTransform', 'letterSpacing']; - for (i = 0, len = styles.length; i < len; i++) { - style = styles[i]; - style_block[style] = this.search_field.getStyle(style); - } - div = new Element('div').update(this.escape_html(this.get_search_field_value())); - div.setStyle(style_block); - document.body.appendChild(div); - width = div.measure('width') + 25; - div.remove(); - if (container_width = this.container.getWidth()) { - width = Math.min(container_width - 10, width); - } - return this.search_field.setStyle({ - width: width + 'px' - }); - }; - - Chosen.prototype.trigger_form_field_change = function() { - triggerHtmlEvent(this.form_field, 'input'); - return triggerHtmlEvent(this.form_field, 'change'); - }; - - triggerHtmlEvent = function(element, eventType) { - var error, evt; - if (element.dispatchEvent) { - try { - evt = new Event(eventType, { - bubbles: true, - cancelable: true - }); - } catch (error) { - evt = document.createEvent('HTMLEvents'); - evt.initEvent(eventType, true, true); - } - return element.dispatchEvent(evt); - } else { - return element.fireEvent("on" + eventType, document.createEventObject()); - } - }; - - return Chosen; - - })(AbstractChosen); - -}).call(this); diff --git a/web/skins/classic/js/chosen/chosen.proto.min.js b/web/skins/classic/js/chosen/chosen.proto.min.js deleted file mode 100644 index f299bc21b..000000000 --- a/web/skins/classic/js/chosen/chosen.proto.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* Chosen v1.8.2 | (c) 2011-2017 by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */ - -(function(){var e,t,s=function(e,t){return function(){return e.apply(t,arguments)}},i=function(e,t){function s(){this.constructor=e}for(var i in t)r.call(t,i)&&(e[i]=t[i]);return s.prototype=t.prototype,e.prototype=new s,e.__super__=t.prototype,e},r={}.hasOwnProperty;(t=function(){function e(){this.options_index=0,this.parsed=[]}return e.prototype.add_node=function(e){return"OPTGROUP"===e.nodeName.toUpperCase()?this.add_group(e):this.add_option(e)},e.prototype.add_group=function(e){var t,s,i,r,n,o;for(t=this.parsed.length,this.parsed.push({array_index:t,group:!0,label:e.label,title:e.title?e.title:void 0,children:0,disabled:e.disabled,classes:e.className}),o=[],s=0,i=(n=e.childNodes).length;s"+e.group_label+""+e.html:e.html},e.prototype.mouse_enter=function(){return this.mouse_on_container=!0},e.prototype.mouse_leave=function(){return this.mouse_on_container=!1},e.prototype.input_focus=function(e){if(this.is_multiple){if(!this.active_field)return setTimeout(function(e){return function(){return e.container_mousedown()}}(this),50)}else if(!this.active_field)return this.activate_field()},e.prototype.input_blur=function(e){if(!this.mouse_on_container)return this.active_field=!1,setTimeout(function(e){return function(){return e.blur_test()}}(this),100)},e.prototype.label_click_handler=function(e){return this.is_multiple?this.container_mousedown(e):this.activate_field()},e.prototype.results_option_build=function(e){var t,s,i,r,n,o,l;for(t="",l=0,r=0,n=(o=this.results_data).length;r=this.max_shown_results));r++);return t},e.prototype.result_add_option=function(e){var t,s;return e.search_match&&this.include_option_in_results(e)?(t=[],e.disabled||e.selected&&this.is_multiple||t.push("active-result"),!e.disabled||e.selected&&this.is_multiple||t.push("disabled-result"),e.selected&&t.push("result-selected"),null!=e.group_array_index&&t.push("group-option"),""!==e.classes&&t.push(e.classes),s=document.createElement("li"),s.className=t.join(" "),s.style.cssText=e.style,s.setAttribute("data-option-array-index",e.array_index),s.innerHTML=e.highlighted_html||e.html,e.title&&(s.title=e.title),this.outerHTML(s)):""},e.prototype.result_add_group=function(e){var t,s;return(e.search_match||e.group_match)&&e.active_options>0?((t=[]).push("group-result"),e.classes&&t.push(e.classes),s=document.createElement("li"),s.className=t.join(" "),s.innerHTML=e.highlighted_html||this.escape_html(e.label),e.title&&(s.title=e.title),this.outerHTML(s)):""},e.prototype.results_update_field=function(){if(this.set_default_text(),this.is_multiple||this.results_reset_cleanup(),this.result_clear_highlight(),this.results_build(),this.results_showing)return this.winnow_results()},e.prototype.reset_single_select_options=function(){var e,t,s,i,r;for(r=[],e=0,t=(s=this.results_data).length;e"+this.escape_html(t)+""+this.escape_html(d)),null!=a&&(a.group_match=!0)):null!=r.group_array_index&&this.results_data[r.group_array_index].search_match&&(r.search_match=!0)));return this.result_clear_highlight(),c<1&&o.length?(this.update_results_content(""),this.no_results(o)):(this.update_results_content(this.results_option_build()),this.winnow_results_set_highlight())},e.prototype.get_search_regex=function(e){var t,s;return s=this.search_contains?e:"(^|\\s|\\b)"+e+"[^\\s]*",this.enable_split_word_search||this.search_contains||(s="^"+s),t=this.case_sensitive_search?"":"i",new RegExp(s,t)},e.prototype.search_string_match=function(e,t){var s;return s=t.exec(e),!this.search_contains&&(null!=s?s[1]:void 0)&&(s.index+=1),s},e.prototype.choices_count=function(){var e,t,s;if(null!=this.selected_option_count)return this.selected_option_count;for(this.selected_option_count=0,e=0,t=(s=this.form_field.options).length;e0?this.keydown_backstroke():this.pending_backstroke||(this.result_clear_highlight(),this.results_search());break;case 13:e.preventDefault(),this.results_showing&&this.result_select(e);break;case 27:this.results_showing&&this.results_hide();break;case 9:case 16:case 17:case 18:case 38:case 40:case 91:break;default:this.results_search()}},e.prototype.clipboard_event_checker=function(e){if(!this.is_disabled)return setTimeout(function(e){return function(){return e.results_search()}}(this),50)},e.prototype.container_width=function(){return null!=this.options.width?this.options.width:this.form_field.offsetWidth+"px"},e.prototype.include_option_in_results=function(e){return!(this.is_multiple&&!this.display_selected_options&&e.selected)&&(!(!this.display_disabled_options&&e.disabled)&&!e.empty)},e.prototype.search_results_touchstart=function(e){return this.touch_started=!0,this.search_results_mouseover(e)},e.prototype.search_results_touchmove=function(e){return this.touch_started=!1,this.search_results_mouseout(e)},e.prototype.search_results_touchend=function(e){if(this.touch_started)return this.search_results_mouseup(e)},e.prototype.outerHTML=function(e){var t;return e.outerHTML?e.outerHTML:((t=document.createElement("div")).appendChild(e),t.innerHTML)},e.prototype.get_single_html=function(){return'\n '+this.default_text+'\n
      \n
      \n
      \n \n
        \n
        '},e.prototype.get_multi_html=function(){return'
          \n
        • \n \n
        • \n
        \n
        \n
          \n
          '},e.prototype.get_no_results_html=function(e){return'
        • \n '+this.results_none_found+" "+this.escape_html(e)+"\n
        • "},e.browser_is_supported=function(){return"Microsoft Internet Explorer"===window.navigator.appName?document.documentMode>=8:!(/iP(od|hone)/i.test(window.navigator.userAgent)||/IEMobile/i.test(window.navigator.userAgent)||/Windows Phone/i.test(window.navigator.userAgent)||/BlackBerry/i.test(window.navigator.userAgent)||/BB10/i.test(window.navigator.userAgent)||/Android.*Mobile/i.test(window.navigator.userAgent))},e.default_multiple_text="Select Some Options",e.default_single_text="Select an Option",e.default_no_result_text="No results match",e}(),this.Chosen=function(s){function r(){return r.__super__.constructor.apply(this,arguments)}var n;return i(r,e),r.prototype.setup=function(){return this.current_selectedIndex=this.form_field.selectedIndex},r.prototype.set_up_html=function(){var e,t;return(e=["chosen-container"]).push("chosen-container-"+(this.is_multiple?"multi":"single")),this.inherit_select_classes&&this.form_field.className&&e.push(this.form_field.className),this.is_rtl&&e.push("chosen-rtl"),t={class:e.join(" "),title:this.form_field.title},this.form_field.id.length&&(t.id=this.form_field.id.replace(/[^\w]/g,"_")+"_chosen"),this.container=new Element("div",t),this.container.setStyle({width:this.container_width()}),this.is_multiple?this.container.update(this.get_multi_html()):this.container.update(this.get_single_html()),this.form_field.hide().insert({after:this.container}),this.dropdown=this.container.down("div.chosen-drop"),this.search_field=this.container.down("input"),this.search_results=this.container.down("ul.chosen-results"),this.search_field_scale(),this.search_no_results=this.container.down("li.no-results"),this.is_multiple?(this.search_choices=this.container.down("ul.chosen-choices"),this.search_container=this.container.down("li.search-field")):(this.search_container=this.container.down("div.chosen-search"),this.selected_item=this.container.down(".chosen-single")),this.results_build(),this.set_tab_index(),this.set_label_behavior()},r.prototype.on_ready=function(){return this.form_field.fire("chosen:ready",{chosen:this})},r.prototype.register_observers=function(){return this.container.observe("touchstart",function(e){return function(t){return e.container_mousedown(t)}}(this)),this.container.observe("touchend",function(e){return function(t){return e.container_mouseup(t)}}(this)),this.container.observe("mousedown",function(e){return function(t){return e.container_mousedown(t)}}(this)),this.container.observe("mouseup",function(e){return function(t){return e.container_mouseup(t)}}(this)),this.container.observe("mouseenter",function(e){return function(t){return e.mouse_enter(t)}}(this)),this.container.observe("mouseleave",function(e){return function(t){return e.mouse_leave(t)}}(this)),this.search_results.observe("mouseup",function(e){return function(t){return e.search_results_mouseup(t)}}(this)),this.search_results.observe("mouseover",function(e){return function(t){return e.search_results_mouseover(t)}}(this)),this.search_results.observe("mouseout",function(e){return function(t){return e.search_results_mouseout(t)}}(this)),this.search_results.observe("mousewheel",function(e){return function(t){return e.search_results_mousewheel(t)}}(this)),this.search_results.observe("DOMMouseScroll",function(e){return function(t){return e.search_results_mousewheel(t)}}(this)),this.search_results.observe("touchstart",function(e){return function(t){return e.search_results_touchstart(t)}}(this)),this.search_results.observe("touchmove",function(e){return function(t){return e.search_results_touchmove(t)}}(this)),this.search_results.observe("touchend",function(e){return function(t){return e.search_results_touchend(t)}}(this)),this.form_field.observe("chosen:updated",function(e){return function(t){return e.results_update_field(t)}}(this)),this.form_field.observe("chosen:activate",function(e){return function(t){return e.activate_field(t)}}(this)),this.form_field.observe("chosen:open",function(e){return function(t){return e.container_mousedown(t)}}(this)),this.form_field.observe("chosen:close",function(e){return function(t){return e.close_field(t)}}(this)),this.search_field.observe("blur",function(e){return function(t){return e.input_blur(t)}}(this)),this.search_field.observe("keyup",function(e){return function(t){return e.keyup_checker(t)}}(this)),this.search_field.observe("keydown",function(e){return function(t){return e.keydown_checker(t)}}(this)),this.search_field.observe("focus",function(e){return function(t){return e.input_focus(t)}}(this)),this.search_field.observe("cut",function(e){return function(t){return e.clipboard_event_checker(t)}}(this)),this.search_field.observe("paste",function(e){return function(t){return e.clipboard_event_checker(t)}}(this)),this.is_multiple?this.search_choices.observe("click",function(e){return function(t){return e.choices_click(t)}}(this)):this.container.observe("click",function(e){return e.preventDefault()})},r.prototype.destroy=function(){var e,t,s,i;for(this.container.ownerDocument.stopObserving("click",this.click_test_action),t=0,s=(i=["chosen:updated","chosen:activate","chosen:open","chosen:close"]).length;t=r?this.search_results.scrollTop=t-i>0?t-i:0:s"+this.choice_label(e)+""),e.disabled?t.addClassName("search-choice-disabled"):((s=new Element("a",{href:"#",class:"search-choice-close",rel:e.array_index})).observe("click",function(e){return function(t){return e.choice_destroy_link_click(t)}}(this)),t.insert(s)),this.search_container.insert({before:t})},r.prototype.choice_destroy_link_click=function(e){if(e.preventDefault(),e.stopPropagation(),!this.is_disabled)return this.choice_destroy(e.target)},r.prototype.choice_destroy=function(e){if(this.result_deselect(e.readAttribute("rel")))return this.active_field?this.search_field.focus():this.show_search_field_default(),this.is_multiple&&this.choices_count()>0&&this.get_search_field_value().length<1&&this.results_hide(),e.up("li").remove(),this.search_field_scale()},r.prototype.results_reset=function(){if(this.reset_single_select_options(),this.form_field.options[0].selected=!0,this.single_set_selected_text(),this.show_search_field_default(),this.results_reset_cleanup(),this.trigger_form_field_change(),this.active_field)return this.results_hide()},r.prototype.results_reset_cleanup=function(){var e;if(this.current_selectedIndex=this.form_field.selectedIndex,e=this.selected_item.down("abbr"))return e.remove()},r.prototype.result_select=function(e){var t,s;if(this.result_highlight)return t=this.result_highlight,this.result_clear_highlight(),this.is_multiple&&this.max_selected_options<=this.choices_count()?(this.form_field.fire("chosen:maxselected",{chosen:this}),!1):(this.is_multiple?t.removeClassName("active-result"):this.reset_single_select_options(),t.addClassName("result-selected"),s=this.results_data[t.getAttribute("data-option-array-index")],s.selected=!0,this.form_field.options[s.options_index].selected=!0,this.selected_option_count=null,this.search_field.value="",this.is_multiple?this.choice_build(s):this.single_set_selected_text(this.choice_label(s)),this.is_multiple&&(!this.hide_results_on_select||e.metaKey||e.ctrlKey)?this.winnow_results():(this.results_hide(),this.show_search_field_default()),(this.is_multiple||this.form_field.selectedIndex!==this.current_selectedIndex)&&this.trigger_form_field_change(),this.current_selectedIndex=this.form_field.selectedIndex,e.preventDefault(),this.search_field_scale())},r.prototype.single_set_selected_text=function(e){return null==e&&(e=this.default_text),e===this.default_text?this.selected_item.addClassName("chosen-default"):(this.single_deselect_control_build(),this.selected_item.removeClassName("chosen-default")),this.selected_item.down("span").update(e)},r.prototype.result_deselect=function(e){var t;return t=this.results_data[e],!this.form_field.options[t.options_index].disabled&&(t.selected=!1,this.form_field.options[t.options_index].selected=!1,this.selected_option_count=null,this.result_clear_highlight(),this.results_showing&&this.winnow_results(),this.trigger_form_field_change(),this.search_field_scale(),!0)},r.prototype.single_deselect_control_build=function(){if(this.allow_single_deselect)return this.selected_item.down("abbr")||this.selected_item.down("span").insert({after:''}),this.selected_item.addClassName("chosen-single-with-deselect")},r.prototype.get_search_field_value=function(){return this.search_field.value},r.prototype.get_search_text=function(){return this.get_search_field_value().strip()},r.prototype.escape_html=function(e){return e.escapeHTML()},r.prototype.winnow_results_set_highlight=function(){var e;if(this.is_multiple||(e=this.search_results.down(".result-selected.active-result")),null==e&&(e=this.search_results.down(".active-result")),null!=e)return this.result_do_highlight(e)},r.prototype.no_results=function(e){return this.search_results.insert(this.get_no_results_html(e)),this.form_field.fire("chosen:no_results",{chosen:this})},r.prototype.no_results_clear=function(){var e,t;for(e=null,t=[];e=this.search_results.down(".no-results");)t.push(e.remove());return t},r.prototype.keydown_arrow=function(){var e;return this.results_showing&&this.result_highlight?(e=this.result_highlight.next(".active-result"))?this.result_do_highlight(e):void 0:this.results_show()},r.prototype.keyup_arrow=function(){var e,t,s;return this.results_showing||this.is_multiple?this.result_highlight?(s=this.result_highlight.previousSiblings(),e=this.search_results.select("li.active-result"),(t=s.intersect(e)).length?this.result_do_highlight(t.first()):(this.choices_count()>0&&this.results_hide(),this.result_clear_highlight())):void 0:this.results_show()},r.prototype.keydown_backstroke=function(){var e;return this.pending_backstroke?(this.choice_destroy(this.pending_backstroke.down("a")),this.clear_backstroke()):(e=this.search_container.siblings().last())&&e.hasClassName("search-choice")&&!e.hasClassName("search-choice-disabled")?(this.pending_backstroke=e,this.pending_backstroke&&this.pending_backstroke.addClassName("search-choice-focus"),this.single_backstroke_delete?this.keydown_backstroke():this.pending_backstroke.addClassName("search-choice-focus")):void 0},r.prototype.clear_backstroke=function(){return this.pending_backstroke&&this.pending_backstroke.removeClassName("search-choice-focus"),this.pending_backstroke=null},r.prototype.search_field_scale=function(){var e,t,s,i,r,n,o,l;if(this.is_multiple){for(n={position:"absolute",left:"-1000px",top:"-1000px",display:"none",whiteSpace:"pre"},s=0,i=(o=["fontSize","fontStyle","fontWeight","fontFamily","lineHeight","textTransform","letterSpacing"]).length;s code[class*="language-"], -pre[class*="language-"] { - background: #272822; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #f8f8f2; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag { - color: #f92672; -} - -.token.boolean, -.token.number{ - color: #ae81ff; -} - -.token.selector, -.token.attr-name, -.token.string { - color: #a6e22e; -} - - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string { - color: #f8f8f2; -} - -.token.atrule, -.token.attr-value -{ - color: #e6db74; -} - - -.token.keyword{ -color: #66d9ef; -} - -.token.regex, -.token.important { - color: #fd971f; -} - -.token.important { - font-weight: bold; -} - -.token.entity { - cursor: help; -} diff --git a/web/skins/classic/js/chosen/docsupport/prism.js b/web/skins/classic/js/chosen/docsupport/prism.js deleted file mode 100644 index 7ed4fa737..000000000 --- a/web/skins/classic/js/chosen/docsupport/prism.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Prism: Lightweight, robust, elegant syntax highlighting - * MIT license http://www.opensource.org/licenses/mit-license.php/ - * @author Lea Verou http://lea.verou.me - */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; -Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; -Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*{))/gi,inside:{punctuation:/[;:]/g}},url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/g,property:/(\b|\B)[\w-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});; -Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; diff --git a/web/skins/classic/js/chosen/docsupport/prism.js.FIXME b/web/skins/classic/js/chosen/docsupport/prism.js.FIXME deleted file mode 100644 index e6283136c..000000000 --- a/web/skins/classic/js/chosen/docsupport/prism.js.FIXME +++ /dev/null @@ -1,3 +0,0 @@ -Outdated, source-less minified file. Original may have been lost. - -https://github.com/harvesthq/chosen/issues/3005 diff --git a/web/skins/classic/js/chosen/docsupport/style.css b/web/skins/classic/js/chosen/docsupport/style.css deleted file mode 100644 index b1d72684d..000000000 --- a/web/skins/classic/js/chosen/docsupport/style.css +++ /dev/null @@ -1,219 +0,0 @@ -/* Reset */ -html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } - -article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } - -blockquote, q { quotes: none; } -blockquote:before, blockquote:after, q:before, q:after { content: ""; content: none; } -ins { background-color: #ff9; color: #000; text-decoration: none; } -mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } -del { text-decoration: line-through; } -abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } -table { border-collapse: collapse; border-spacing: 0; } -hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } -input, select { vertical-align: middle; } - -body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */ -select, input, textarea, button { font:99% sans-serif; } -pre, code, kbd, samp { font-family: monospace, sans-serif; } - - -body { background: #EEE; color: #444; line-height: 1.4em; } - -header h1 { color: black; font-size: 2em; line-height: 1.1em; display: inline-block; height: 27px; margin: 20px 0 25px; } -header h1 small { font-size: 0.6em; } - -div#content { background: white; border: 1px solid #ccc; border-width: 0 1px 1px; margin: 0 auto; padding: 40px 50px 40px; width: 738px; } - -footer { color: #999; padding-top: 40px; font-size: 0.8em; text-align: center; } - -body { font-family: sans-serif; font-size: 1em; } - -p { margin: 0 0 .7em; max-width: 700px; } -table+p { margin-top: 1em; } - -h2 { border-bottom: 1px solid #ccc; font-size: 1.2em; margin: 3em 0 1em 0; font-weight: bold;} -h3 { font-weight: bold; } - -h2.intro { border-bottom: none; font-size: 1em; font-weight: normal; margin-top:0; } - -ul li { list-style: disc; margin-left: 1em; margin-bottom: 1.25em; } -ol li { margin-left: 1.25em; } -ol ul, ul ul { margin: .25em 0 0; } -ol ul li, ul ul li { list-style-type: circle; margin: 0 0 .25em 1em; } - -li > p { margin-top: .25em; } - -div.side-by-side { width: 100%; margin-bottom: 1em; } -div.side-by-side > div { float: left; width: 49%; } -div.side-by-side > div > em { margin-bottom: 10px; display: block; } - -.faqs em { display: block; } - -.clearfix:after { - content: "\0020"; - display: block; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} - -a { color: #F36C00; outline: none; text-decoration: none; } -a:hover { text-decoration: underline; } - -ul.credits li { margin-bottom: .25em; } - -strong { font-weight: bold; } -i { font-style: italic; } - -.button { - background: #fafafa; - background: -webkit-linear-gradient(top, #ffffff, #eeeeee); - background: -moz-linear-gradient(top, #ffffff, #eeeeee); - background: -o-linear-gradient(top, #ffffff, #eeeeee); - background: linear-gradient(to bottom, #ffffff, #eeeeee); - border: 1px solid #bbbbbb; - border-radius: 4px; - box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.2); - color: #555555; - cursor: pointer; - display: inline-block; - font-family: "Helvetica Neue", Arial, Verdana, "Nimbus Sans L", sans-serif; - font-size: 13px; - font-weight: 500; - height: 31px; - line-height: 28px; - outline: none; - padding: 0 13px; - text-shadow: 0 1px 0 white; - text-decoration: none; - vertical-align: middle; - white-space: nowrap; - -webkit-font-smoothing: antialiased; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.button-blue { - background: #1385e5; - background: -webkit-linear-gradient(top, #53b2fc, #1385e5); - background: -moz-linear-gradient(top, #53b2fc, #1385e5); - background: -o-linear-gradient(top, #53b2fc, #1385e5); - background: linear-gradient(to bottom, #53b2fc, #1385e5); - border-color: #075fa9; - color: white; - font-weight: bold; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4); -} - - -/* Tweak navbar brand link to be super sleek --------------------------------------------------- */ -.oss-bar { - top: 0; - right: 20px; - position: fixed; - z-index: 1030; -} -.oss-bar ul { - float: right; - margin: 0; - list-style: none; -} -.oss-bar ul li { - list-style: none; - float: left; - line-height: 0; - margin: 0; -} -.oss-bar ul li a { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - border: 0; - margin-top: -10px; - display: block; - height: 58px; - background: #F36C00 url(oss-credit.png) no-repeat 20px 22px; - padding: 22px 20px 12px 20px; - text-indent: 120%; /* stupid padding */ - white-space: nowrap; - overflow: hidden; - -webkit-transition: all 0.10s ease-in-out; - -moz-transition: all 0.10s ease-in-out; - transition: all 0.15s ease-in-out; -} -.oss-bar ul li a:hover { - margin-top: 0px; -} -.oss-bar a.harvest { - width: 196px; - background-color: #F36C00; - background-position: -142px 22px; - padding-right: 22px; /* optical illusion */ -} -.oss-bar a.fork { - width: 162px; - background-color: #333333; -} - -.docs-table th, .docs-table td { - border: 1px solid #000; - padding: 4px 6px; - white-space: nowrap; -} - -.docs-table td:last-child { - white-space: normal; -} - -.docs-table th { - font-weight: bold; - text-align: left; -} - -#content pre[class*=language-] { - font-size: 14px; - margin-bottom: 20px; -} - -#content pre[class*=language-] code { - font-size: 14px; - padding: 0; -} - -#content code[class*=language-] { - font-size: 12px; - padding: 2px 4px; -} - -.anchor { - color: inherit; - position: relative; -} - -.anchor:hover { - background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSI3Ij48ZyBmaWxsPSIjNDE0MDQyIj48cGF0aCBkPSJNOS44IDdoLS45bC0uOS0uMWMtLjctLjMtMS40LS43LTEuOC0xLjMtLjItLjEtLjMtLjMtLjMtLjVsLS4zLS40Yy0uMS0uNC0uMi0uOC0uMi0xLjIgMC0uNC4xLS44LjItMS4yaDEuN2MtLjMuNC0uNC44LS40IDEuMiAwIC40LjEuOC4zIDEuMS4xLjIuMi4zLjQuNC4xLjEuMi4yLjQuMy4zLjIuNy4zIDEgLjNoMy40YzEuMiAwIDIuMi0uOSAyLjItMi4xcy0xLTIuMS0yLjItMi4xaC0xLjRjLS4zLS42LS43LTEtMS4yLTEuNGgyLjZjMiAwIDMuNiAxLjYgMy42IDMuNXMtMS42IDMuNS0zLjYgMy41aC0yLjZ6TTguNCAyYy0uMS0uMS0uMi0uMy0uNC0uMy0uMy0uMi0uNy0uMy0xLS4zaC0zLjRjLTEuMiAwLTIuMi45LTIuMiAyLjEgMCAxLjIgMSAyLjEgMi4yIDIuMWgxLjRjLjMuNS43IDEgMS4yIDEuNGgtMi42Yy0yIDAtMy42LTEuNi0zLjYtMy41czEuNi0zLjUgMy42LTMuNWgzLjUwMDAwMDAwMDAwMDAwMDRsLjkuMWMuNy4yIDEuNC43IDEuOCAxLjMuMS4xLjIuMy4zLjUuMS4xLjIuMy4yLjUuMS40LjIuOC4yIDEuMiAwIC40LS4xLjgtLjIgMS4yaC0xLjZjLjMtLjUuNC0uOS40LTEuM3MtLjEtLjgtLjMtMS4xYy0uMS0uMi0uMi0uMy0uNC0uNHoiLz48L2c+PC9zdmc+) 0 50% no-repeat; - background-size: 21px 9px; - margin-left: -27px; - padding-left: 27px; - text-decoration: none; -} - -.select, -.chosen-select, -.chosen-select-no-single, -.chosen-select-no-results, -.chosen-select-deselect, -.chosen-select-rtl, -.chosen-select-width { - width: 350px; -} - -.jquery-version-refer { - margin-top: 40px; - font-style: italic; -} diff --git a/web/skins/classic/js/chosen/index.html b/web/skins/classic/js/chosen/index.html deleted file mode 100644 index 913bfd2a3..000000000 --- a/web/skins/classic/js/chosen/index.html +++ /dev/null @@ -1,1473 +0,0 @@ - - - - - Chosen: A jQuery Plugin by Harvest to Tame Unwieldy Select Boxes - - - - - - - - -
          -
          -
          -
          -

          Chosen (v1.8.2)

          -
          -

          Chosen is a jQuery plugin that makes long, unwieldy select boxes much more user-friendly.

          - -

          - Downloads - Project Source - Contribute -

          - -

          Standard Select

          -
          -
          - Turns This - -
          -
          - Into This - -
          -
          - -

          Multiple Select

          -
          -
          - Turns This - -
          -
          - Into This - -
          -
          - -

          <optgroup> Support

          -
          -
          - Single Select with Groups - -
          -
          - Multiple Select with Groups - -
          -
          - -

          Selected and Disabled Support

          -
          -

          Chosen automatically highlights selected options and removes disabled options.

          -
          - Single Select - -
          -
          - Multiple Select - -
          -
          - -

          Hide Search on Single Select

          -
          -

          The disable_search_threshold option can be specified to hide the search input on single selects if there are n or fewer options.

          -
          $(".chosen-select").chosen({disable_search_threshold: 10});
          -

          -
          - -
          -
          - -

          Default Text Support

          -
          -

          Chosen automatically sets the default field text ("Choose a country...") by reading the select element's data-placeholder value. If no data-placeholder value is present, it will default to "Select an Option" or "Select Some Options" depending on whether the select is single or multiple. You can change these elements in the plugin js file as you see fit.

          -
          <select data-placeholder="Choose a country..." multiple class="chosen-select">
          -

          Note: on single selects, the first element is assumed to be selected by the browser. To take advantage of the default text support, you will need to include a blank option as the first element of your select list.

          -
          - -

          No Results Text Support

          -
          -

          Setting the "No results" search text is as easy as passing an option when you create Chosen:

          -
           $(".chosen-select").chosen({no_results_text: "Oops, nothing found!"}); 
          -

          -
          - Single Select - -
          -
          - Multiple Select - -
          -
          - -

          Limit Selected Options in Multiselect

          -
          -

          You can easily limit how many options the user can select:

          -
          $(".chosen-select").chosen({max_selected_options: 5});
          -

          If you try to select another option with limit reached chosen:maxselected event is triggered:

          -
           $(".chosen-select").bind("chosen:maxselected", function () { ... }); 
          -
          - -

          Allow Deselect on Single Selects

          -
          -

          When a single select box isn't a required field, you can set allow_single_deselect: true and Chosen will add a UI element for option deselection. This will only work if the first option has blank text.

          -
          - -
          -
          - -

          Right-to-Left Support

          -
          -

          You can set right-to-left text by setting rtl: true

          -
           $(".chosen-select").chosen({rtl: true}); 
          - -
          - Single Right-to-Left Select - -
          -
          - Multiple Right-to-Left Select - -
          -
          - -

          Observing, Updating, and Destroying Chosen

          -
          -
            -
          • -

            Observing Form Field Changes

            -

            When working with form fields, you often want to perform some behavior after a value has been selected or deselected. Whenever a user selects a field in Chosen, it triggers a "change" event on the original form field. That lets you do something like this:

            -
            $("#form_field").chosen().change( … );
            -
          • -
          • -

            Updating Chosen Dynamically

            -

            If you need to update the options in your select field and want Chosen to pick up the changes, you'll need to trigger the "chosen:updated" event on the field. Chosen will re-build itself based on the updated content.

            -
            $("#form_field").trigger("chosen:updated");
            -
          • -
          • -

            Destroying Chosen

            -

            To destroy Chosen and revert back to the native select:

            -
            $("#form_field").chosen("destroy");
            -
          • -
          -
          - -

          Custom Width Support

          -
          -

          Using a custom width with Chosen is as easy as passing an option when you create Chosen:

          -
           $(".chosen-select").chosen({width: "95%"}); 
          -
          - Single Select - -
          -
          - Multiple Select - -
          -
          - -

          Labels work, too

          -
          -

          Use labels just like you would a standard select

          -

          -
          - - -
          -
          - - -
          -
          - -

          Setup

          -

          Using Chosen is easy as can be.

          -
            -
          1. Download the plugin and copy the chosen files to your app.
          2. -
          3. Activate the plugin on the select boxes of your choice: $(".chosen-select").chosen()
          4. -
          5. Disco.
          6. -
          - -

          FAQs

          -
            -
          • -

            Do you have all the available options documented somewhere?

            -

            Yes! You can find them on the options page.

            -
          • -
          • -

            Something doesn't work. Can you fix it?

            -

            Yes! Please report all issues using the GitHub issue tracking tool. Please include the plugin version (jQuery or Prototype), browser and OS. The more information provided, the easier it is to fix a problem.

            -
          • -
          • -

            What browsers are supported?

            -

            All modern desktop browsers are supported (Firefox, Chrome, Safari and IE9). Legacy support for IE8 is also enabled. Chosen is disabled on iPhone, iPod Touch, and Android mobile devices (more information).

            -
          • -
          • -

            Didn't there used to be a Prototype version of Chosen?

            -

            There still is!

            -
          • -
          - -

          Credits

          - - - - - -
          -
          - - - - -
          - - - diff --git a/web/skins/classic/js/chosen/index.proto.html b/web/skins/classic/js/chosen/index.proto.html deleted file mode 100644 index 11c9346d6..000000000 --- a/web/skins/classic/js/chosen/index.proto.html +++ /dev/null @@ -1,1472 +0,0 @@ - - - - - Chosen: A Prototype Plugin by Harvest to Tame Unwieldy Select Boxes - - - - - - - - -
          -
          -
          -

          Chosen - Prototype Version (v1.8.2)

          -
          -

          Chosen is a Prototype plugin that makes long, unwieldy select boxes much more user-friendly.

          - -

          - Downloads - Project Source - Contribute -

          - -

          Looking for the jQuery version?

          - -

          Standard Select

          -
          -
          - Turns This - -
          -
          - Into This - -
          -
          - -

          Multiple Select

          -
          -
          - Turns This - -
          -
          - Into This - -
          -
          - -

          <optgroup> Support

          -
          -
          - Single Select with Groups - -
          -
          - Multiple Select with Groups - -
          -
          - -

          Selected and Disabled Support

          -
          -

          Chosen automatically highlights selected options and removes disabled options.

          -
          - Single Select - -
          -
          - Multiple Select - -
          -
          - -

          Hide Search on Single Select

          -
          -

          The disable_search_threshold option can be specified to hide the search input on single selects if there are n or fewer options.

          -
           new Chosen($("chosen_select_field"),{disable_search_threshold: 10}); 
          -

          -
          - -
          -
          - -

          Default Text Support

          -
          -

          Chosen automatically sets the default field text ("Choose a country...") by reading the select element's data-placeholder value. If no data-placeholder value is present, it will default to "Select an Option" or "Select Some Options" depending on whether the select is single or multiple. You can change these elements in the plugin js file as you see fit.

          -
          <select data-placeholder="Choose a country..." multiple class="chosen-select">
          -

          Note: on single selects, the first element is assumed to be selected by the browser. To take advantage of the default text support, you will need to include a blank option as the first element of your select list.

          -
          - -

          No Results Text Support

          -
          -

          Setting the "No results" search text is as easy as passing an option when you create Chosen:

          -
          new Chosen($("chosen_select_field"),{no_results_text: "Oops, nothing found!"}); 
          - -
          - Single Select - -
          -
          - Multiple Select - -
          -
          - -

          Limit Selected Options in Multiselect

          -
          -

          You can easily limit how many options the user can select:

          -
          new Chosen($("chosen_select_field"),{max_selected_options: 5}); 
          -

          If you try to select another option with limit reached chosen:maxselected event is triggered:

          -
          $("chosen_select_field").observe("chosen:maxselected", function(evt) { ... }); 
          -
          - -

          Allow Deselect on Single Selects

          -
          -

          When a single select box isn't a required field, you can set allow_single_deselect: true and Chosen will add a UI element for option deselection. This will only work if the first option has blank text.

          -
          - -
          -
          - -

          Right-to-Left Support

          -
          -

          You can set right-to-left text by setting rtl: true

          -
           $(".chosen-select").chosen({rtl: true}); 
          -
          - Single Right-to-Left Select - -
          -
          - Multiple Right-to-Left Select - -
          -
          - -

          Observing, Updating, and Destroying Chosen

          -
          -
            -
          • -

            Observing Form Field Changes

            -

            When working with form fields, you often want to perform some behavior after a value has been selected or deselected. Whenever a user selects a field in Chosen, it triggers a "change" event on the original form field. That lets you do something like this:

            -
            $("#form_field").chosen().change( … );
            -

            Note: Prototype doesn't offer support for triggering standard browser events. Event.simulate is required to trigger the change event when using the Prototype version.

            -
          • -
          • -

            Updating Chosen Dynamically

            -

            If you need to update the options in your select field and want Chosen to pick up the changes, you'll need to trigger the "chosen:updated" event on the field. Chosen will re-build itself based on the updated content.

            -
            Event.fire($("form_field"), "chosen:updated");
            -
          • -
          • -

            Destroying Chosen

            -

            To destroy Chosen and revert back to the native select, call destroy on the Chosen instance:

            -
            chosen = new Chosen($("form_field"));
            -
            -// ...later
            -chosen.destroy();
            -
          • -
          -
          - -

          Custom Width Support

          -
          -

          Using a custom width with Chosen is as easy as passing an option when you create Chosen:

          -
          new Chosen($("chosen_select_field"),{width: "95%"}); 
          -
          - Single Select - -
          -
          - Multiple Select - -
          -
          - -

          Labels work, too

          -
          -

          Use labels just like you would a standard select

          -

          -
          - - -
          -
          - - -
          -
          - -

          Setup

          -

          Using Chosen is easy as can be.

          -
            -
          1. Download the plugin and copy the chosen files to your app.
          2. -
          3. Activate the plugin by creating a new instance of Chosen: New Chosen(some_form_field,some_options);
          4. -
          5. Disco.
          6. -
          - -

          FAQs

          -
            -
          • -

            Do you have all the available options documented somewhere?

            -

            Yes! You can find them on the options page.

            -
          • -
          • -

            Something doesn't work. Can you fix it?

            -

            Yes! Please report all issues using the GitHub issue tracking tool. Please include the plugin version (jQuery or Prototype), browser and OS. The more information provided, the easier it is to fix a problem.

            -
          • -
          • -

            What browsers are supported?

            -

            All modern desktop browsers are supported (Firefox, Chrome, Safari and IE9). Legacy support for IE8 is also enabled. Chosen is disabled on iPhone, iPod Touch, and Android mobile devices (more information).

            -
          • -
          - -

          Credits

          - - - - - -
          -
          - - - - - - - diff --git a/web/skins/classic/js/chosen/options.html b/web/skins/classic/js/chosen/options.html deleted file mode 100644 index 3e2a6a87e..000000000 --- a/web/skins/classic/js/chosen/options.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - Chosen: A jQuery Plugin by Harvest to Tame Unwieldy Select Boxes - - - - - - -
          -
          -
          -

          Chosen (v1.8.2)

          -
          -

          Chosen has a number of options and attributes that allow you to have full control of your select boxes.

          - -

          Options

          -

          The following options are available to pass into Chosen on instantiation.

          - -

          Example:

          -
          -  $(".my_select_box").chosen({
          -    disable_search_threshold: 10,
          -    no_results_text: "Oops, nothing found!",
          -    width: "95%"
          -  });
          -
          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          OptionDefaultDescription
          allow_single_deselectfalseWhen set to true on a single select, Chosen adds a UI element which selects the first element (if it is blank).
          disable_searchfalseWhen set to true, Chosen will not display the search field (single selects only).
          disable_search_threshold0Hide the search input on single selects if there are n or fewer options.
          enable_split_word_searchtrueBy default, searching will match on any word within an option tag. Set this option to false if you want to only match on the entire text of an option tag.
          inherit_select_classesfalseWhen set to true, Chosen will grab any classes on the original select field and add them to Chosen’s container div.
          max_selected_optionsInfinityLimits how many options the user can select. When the limit is reached, the chosen:maxselected event is triggered.
          no_results_text"No results match"The text to be displayed when no matching results are found. The current search is shown at the end of the text (e.g., - No results match "Bad Search").
          placeholder_text_multiple"Select Some Options"The text to be displayed as a placeholder when no options are selected for a multiple select.
          placeholder_text_single"Select an Option"The text to be displayed as a placeholder when no options are selected for a single select.
          search_containsfalseBy default, Chosen’s search matches starting at the beginning of a word. Setting this option to true allows matches starting from anywhere within a word. This is especially useful for options that include a lot of special characters or phrases in ()s and []s.
          single_backstroke_deletetrueBy default, pressing delete/backspace on multiple selects will remove a selected choice. When false, pressing delete/backspace will highlight the last choice, and a second press deselects it.
          widthOriginal select width.The width of the Chosen select box. By default, Chosen attempts to match the width of the select box you are replacing. If your select is hidden when Chosen is instantiated, you must specify a width or the select will show up with a width of 0.
          display_disabled_optionstrueBy default, Chosen includes disabled options in search results with a special styling. Setting this option to false will hide disabled results and exclude them from searches.
          display_selected_optionstrue -

          By default, Chosen includes selected options in search results with a special styling. Setting this option to false will hide selected results and exclude them from searches.

          -

          Note: this is for multiple selects only. In single selects, the selected result will always be displayed.

          -
          include_group_label_in_selectedfalse -

          By default, Chosen only shows the text of a selected option. Setting this option to true will show the text and group (if any) of the selected option.

          -
          max_shown_resultsInfinity -

          Only show the first (n) matching options in the results. This can be used to increase performance for selects with very many options.

          -
          case_sensitive_searchfalse -

          By default Chosen's search is case-insensitive. Setting this option to true makes the search case-sensitive.

          -
          hide_results_on_selecttrue -

          By default Chosen's results are hidden after a option is selected. Setting this option to false will keep the results open after selection. This only applies to multiple selects.

          -
          rtlfalse -

          Chosen supports right-to-left text in select boxes. Set this option to true to support right-to-left text options.

          -

          Note: the chosen-rtl class on the select has precedence over this option. However, the classname approach is deprecated and will be removed in future versions of Chosen.

          -
          - -

          Attributes

          -

          Certain attributes placed on the select tag or its options can be used to configure Chosen.

          - -

          Example:

          - -
          -  <select class="my_select_box" data-placeholder="Select Your Options">
          -    <option value="1">Option 1</option>
          -    <option value="2" selected>Option 2</option>
          -    <option value="3" disabled>Option 3</option>
          -  </select>
          -
          - - - - - - - - - - - - - - - - - -
          AttributeDescription
          data-placeholder -

          The text to be displayed as a placeholder when no options are selected for a select. Defaults to "Select an Option" for single selects or "Select Some Options" for multiple selects.

          -

          Note:This attribute overrides anything set in the placeholder_text_multiple or placeholder_text_single options.

          -
          multipleThe attribute multiple on your select box dictates whether Chosen will render a multiple or single select.
          selected, disabledChosen automatically highlights selected options and disables disabled options.
          - -

          Classes

          -

          Classes placed on the select tag can be used to configure Chosen.

          - -

          Example:

          - -
          -  <select class="my_select_box chosen-rtl">
          -    <option value="1">Option 1</option>
          -    <option value="2">Option 2</option>
          -    <option value="3">Option 3</option>
          -  </select>
          -
          - - - - - - - - - - -
          ClassnameDescription
          chosen-rtl -

          Chosen supports right-to-left text in select boxes. Add the class chosen-rtl to your select tag to support right-to-left text options.

          -

          Note: The chosen-rtl class will pass through to the Chosen select even when the inherit_select_classes option is set to false.

          -

          Note: This is deprecated in favor of using the rtl: true option (see the Options section).

          -
          - -

          Triggered Events

          -

          Chosen triggers a number of standard and custom events on the original select field.

          - -

          Example:

          - -
          -  $('.my_select_box').on('change', function(evt, params) {
          -    do_something(evt, params);
          -  });
          -
          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          EventDescription
          change -

          Chosen triggers the standard DOM event whenever a selection is made (it also sends a selected or deselected parameter that tells you which option was changed).

          -

          Note: The selected and deselected parameters are not available for Prototype.

          -
          chosen:readyTriggered after Chosen has been fully instantiated.
          chosen:maxselectedTriggered if max_selected_options is set and that total is broken.
          chosen:showing_dropdownTriggered when Chosen’s dropdown is opened.
          chosen:hiding_dropdownTriggered when Chosen’s dropdown is closed.
          chosen:no_resultsTriggered when a search returns no matching results.
          - -

          - Note: all custom Chosen events (those that begin with chosen:) also include the chosen object as a parameter. -

          - -

          Triggerable Events

          -

          You can trigger several events on the original select field to invoke a behavior in Chosen.

          - -

          Example:

          - -
          -  // tell Chosen that a select has changed
          -  $('.my_select_box').trigger('chosen:updated');
          -
          - - - - - - - - - - - - - - - - - - - - - -
          EventDescription
          chosen:updatedThis event should be triggered whenever Chosen’s underlying select element changes (such as a change in selected options).
          chosen:activateThis is the equivalant of focusing a standard HTML select field. When activated, Chosen will capure keypress events as if you had clicked the field directly.
          chosen:openThis event activates Chosen and also displays the search results.
          chosen:closeThis event deactivates Chosen and hides the search results.
          - - - -
          -
          - - - - diff --git a/web/skins/classic/js/chosen/package.json b/web/skins/classic/js/chosen/package.json deleted file mode 100644 index 20c4480a3..000000000 --- a/web/skins/classic/js/chosen/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "chosen-js", - "version": "1.8.1", - "description": "Chosen is a JavaScript plugin that makes select boxes user-friendly. It is currently available in both jQuery and Prototype flavors.", - "keywords": [ - "select", - "multiselect", - "dropdown", - "form", - "input", - "ui" - ], - "homepage": "https://harvesthq.github.io/chosen/", - "bugs": "https://github.com/harvesthq/chosen/issues", - "license": "MIT", - "contributors": [ - { - "name": "Patrick Filler", - "url": "https://github.com/pfiller" - }, - { - "name": "Christophe Coevoet", - "url": "https://github.com/stof" - }, - { - "name": "Ken Earley", - "url": "https://github.com/kenearley" - }, - { - "name": "Koen Punt", - "url": "https://github.com/koenpunt" - } - ], - "dependencies": {}, - "files": [ - "chosen.jquery.js", - "chosen.jquery.min.js", - "chosen.proto.js", - "chosen.proto.min.js", - "chosen.css", - "chosen.min.css", - "chosen-sprite@2x.png", - "chosen-sprite.png" - ], - "main": "chosen.jquery.js", - "repository": { - "type": "git", - "url": "https://github.com/harvesthq/chosen.git" - } -} diff --git a/web/skins/classic/js/vjs.eot b/web/skins/classic/js/font/vjs.eot similarity index 100% rename from web/skins/classic/js/vjs.eot rename to web/skins/classic/js/font/vjs.eot diff --git a/web/skins/classic/js/vjs.ttf b/web/skins/classic/js/font/vjs.ttf similarity index 100% rename from web/skins/classic/js/vjs.ttf rename to web/skins/classic/js/font/vjs.ttf diff --git a/web/skins/classic/js/vjs.woff b/web/skins/classic/js/font/vjs.woff similarity index 100% rename from web/skins/classic/js/vjs.woff rename to web/skins/classic/js/font/vjs.woff diff --git a/web/skins/classic/js/jquery-ui-1.12.1/external/jquery/jquery.js b/web/skins/classic/js/jquery-ui-1.12.1/external/jquery/jquery.js deleted file mode 100644 index 7fc60fca7..000000000 --- a/web/skins/classic/js/jquery-ui-1.12.1/external/jquery/jquery.js +++ /dev/null @@ -1,11008 +0,0 @@ -/*! - * jQuery JavaScript Library v1.12.4 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-05-20T17:17Z - */ - -(function( global, factory ) { - - if ( typeof module === "object" && typeof module.exports === "object" ) { - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Support: Firefox 18+ -// Can't be in strict mode, several libs including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -//"use strict"; -var deletedIds = []; - -var document = window.document; - -var slice = deletedIds.slice; - -var concat = deletedIds.concat; - -var push = deletedIds.push; - -var indexOf = deletedIds.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var support = {}; - - - -var - version = "1.12.4", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android<4.1, IE<9 - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: deletedIds.sort, - splice: deletedIds.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var src, copyIsArray, copy, name, options, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type( obj ) === "array"; - }, - - isWindow: function( obj ) { - /* jshint eqeqeq: false */ - return obj != null && obj == obj.window; - }, - - isNumeric: function( obj ) { - - // parseFloat NaNs numeric-cast false positives (null|true|false|"") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - // adding 1 corrects loss of precision from parseFloat (#15100) - var realStringObj = obj && obj.toString(); - return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0; - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - isPlainObject: function( obj ) { - var key; - - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - try { - - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call( obj, "constructor" ) && - !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { - return false; - } - } catch ( e ) { - - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Support: IE<9 - // Handle iteration over inherited properties before own properties. - if ( !support.ownFirst ) { - for ( key in obj ) { - return hasOwn.call( obj, key ); - } - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - for ( key in obj ) {} - - return key === undefined || hasOwn.call( obj, key ); - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function( data ) { - if ( data && jQuery.trim( data ) ) { - - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - ( window.execScript || function( data ) { - window[ "eval" ].call( window, data ); // jscs:ignore requireDotNotation - } )( data ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android<4.1, IE<9 - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - var len; - - if ( arr ) { - if ( indexOf ) { - return indexOf.call( arr, elem, i ); - } - - len = arr.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - - for ( ; i < len; i++ ) { - - // Skip accessing in sparse arrays - if ( i in arr && arr[ i ] === elem ) { - return i; - } - } - } - - return -1; - }, - - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - while ( j < len ) { - first[ i++ ] = second[ j++ ]; - } - - // Support: IE<9 - // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) - if ( len !== len ) { - while ( second[ j ] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var args, proxy, tmp; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: function() { - return +( new Date() ); - }, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -// JSHint would error on this code due to the Symbol not being defined in ES5. -// Defining this global in .jshintrc would create a danger of using the global -// unguarded in another place, it seems safer to just disable JSHint for these -// three lines. -/* jshint ignore: start */ -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = deletedIds[ Symbol.iterator ]; -} -/* jshint ignore: end */ - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: iOS 8.2 (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.2.1 - * http://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2015-10-17 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // General-purpose constants - MAX_NEGATIVE = 1 << 31, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // http://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }; - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, nidselect, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']"; - while ( i-- ) { - groups[i] = nidselect + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return !!fn( div ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( div.parentNode ) { - div.parentNode.removeChild( div ); - } - // release memory in IE - div = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, parent, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( (parent = document.defaultView) && parent.top !== parent ) { - // Support: IE 11 - if ( parent.addEventListener ) { - parent.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( parent.attachEvent ) { - parent.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( div ) { - div.className = "i"; - return !div.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( document.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( div ) { - docElem.appendChild( div ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID find and filter - if ( support.getById ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var m = context.getElementById( id ); - return m ? [ m ] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - docElem.appendChild( div ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibing-combinator selector` fails - if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( div ) { - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( div.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( (oldCache = uniqueCache[ dir ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ dir ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = ""; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ ); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - } ); - - } - - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - - } - - if ( typeof qualifier === "string" ) { - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); - } - - return jQuery.grep( elements, function( elem ) { - return ( jQuery.inArray( elem, qualifier ) > -1 ) !== not; - } ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, - ret = [], - self = this, - len = self.length; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // init accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector.charAt( 0 ) === "<" && - selector.charAt( selector.length - 1 ) === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[ 2 ] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[ 0 ] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return typeof root.ready !== "undefined" ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var i, - targets = jQuery( target, this ), - len = targets.length; - - return this.filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( pos ? - pos.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[ 0 ], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem, this ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - do { - cur = cur[ dir ]; - } while ( cur && cur.nodeType !== 1 ); - - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - ret = jQuery.uniqueSort( ret ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - } - - return this.pushStack( ret ); - }; -} ); -var rnotwhite = ( /\S+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = true; - if ( !memory ) { - self.disable(); - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ], - [ "notify", "progress", jQuery.Callbacks( "memory" ) ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this === promise ? newDefer.promise() : this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( function() { - - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || - ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. - // If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .progress( updateFunc( i, progressContexts, progressValues ) ) - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -} ); - - -// The deferred used on DOM ready -var readyList; - -jQuery.fn.ready = function( fn ) { - - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - jQuery( document ).off( "ready" ); - } - } -} ); - -/** - * Clean-up method for dom ready events - */ -function detach() { - if ( document.addEventListener ) { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - - } else { - document.detachEvent( "onreadystatechange", completed ); - window.detachEvent( "onload", completed ); - } -} - -/** - * The ready event handler and self cleanup method - */ -function completed() { - - // readyState === "complete" is good enough for us to call the dom ready in oldIE - if ( document.addEventListener || - window.event.type === "load" || - document.readyState === "complete" ) { - - detach(); - jQuery.ready(); - } -} - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called - // after the browser event has already occurred. - // Support: IE6-10 - // Older IE sometimes signals "interactive" too soon - if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - - // Standards-based browsers support DOMContentLoaded - } else if ( document.addEventListener ) { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); - - // If IE event model is used - } else { - - // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", completed ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", completed ); - - // If IE and not a frame - // continually check to see if the document is ready - var top = false; - - try { - top = window.frameElement == null && document.documentElement; - } catch ( e ) {} - - if ( top && top.doScroll ) { - ( function doScrollCheck() { - if ( !jQuery.isReady ) { - - try { - - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll( "left" ); - } catch ( e ) { - return window.setTimeout( doScrollCheck, 50 ); - } - - // detach all dom ready events - detach(); - - // and execute any waiting functions - jQuery.ready(); - } - } )(); - } - } - } - return readyList.promise( obj ); -}; - -// Kick off the DOM ready check even if the user does not -jQuery.ready.promise(); - - - - -// Support: IE<9 -// Iteration over object's inherited properties before its own -var i; -for ( i in jQuery( support ) ) { - break; -} -support.ownFirst = i === "0"; - -// Note: most support tests are defined in their respective modules. -// false until the test is run -support.inlineBlockNeedsLayout = false; - -// Execute ASAP in case we need to set body.style.zoom -jQuery( function() { - - // Minified: var a,b,c,d - var val, div, body, container; - - body = document.getElementsByTagName( "body" )[ 0 ]; - if ( !body || !body.style ) { - - // Return for frameset docs that don't have a body - return; - } - - // Setup - div = document.createElement( "div" ); - container = document.createElement( "div" ); - container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; - body.appendChild( container ).appendChild( div ); - - if ( typeof div.style.zoom !== "undefined" ) { - - // Support: IE<8 - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; - - support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; - if ( val ) { - - // Prevent IE 6 from affecting layout for positioned elements #11048 - // Prevent IE from shrinking the body in IE 7 mode #12869 - // Support: IE<8 - body.style.zoom = 1; - } - } - - body.removeChild( container ); -} ); - - -( function() { - var div = document.createElement( "div" ); - - // Support: IE<9 - support.deleteExpando = true; - try { - delete div.test; - } catch ( e ) { - support.deleteExpando = false; - } - - // Null elements to avoid leaks in IE. - div = null; -} )(); -var acceptData = function( elem ) { - var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ], - nodeType = +elem.nodeType || 1; - - // Do not set data on non-element DOM nodes because it will not be cleared (#8335). - return nodeType !== 1 && nodeType !== 9 ? - false : - - // Nodes accept data unless otherwise specified; rejection can be conditional - !noData || noData !== true && elem.getAttribute( "classid" ) === noData; -}; - - - - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /([A-Z])/g; - -function dataAttr( elem, key, data ) { - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - -function internalData( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !acceptData( elem ) ) { - return; - } - - var ret, thisCache, - internalKey = jQuery.expando, - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) && - data === undefined && typeof name === "string" ) { - return; - } - - if ( !id ) { - - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - - // Avoid exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( typeof name === "string" ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; -} - -function internalRemoveData( elem, name, pvt ) { - if ( !acceptData( elem ) ) { - return; - } - - var thisCache, i, - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split( " " ); - } - } - } else { - - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - i = name.length; - while ( i-- ) { - delete thisCache[ name[ i ] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - /* jshint eqeqeq: false */ - } else if ( support.deleteExpando || cache != cache.window ) { - /* jshint eqeqeq: true */ - delete cache[ id ]; - - // When all else fails, undefined - } else { - cache[ id ] = undefined; - } -} - -jQuery.extend( { - cache: {}, - - // The following elements (space-suffixed to avoid Object.prototype collisions) - // throw uncatchable exceptions if you attempt to set expando properties - noData: { - "applet ": true, - "embed ": true, - - // ...but Flash objects (which have this classid) *can* handle expandos - "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data ) { - return internalData( elem, name, data ); - }, - - removeData: function( elem, name ) { - return internalRemoveData( elem, name ); - }, - - // For internal use only. - _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); - }, - - _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Special expections of .data basically thwart jQuery.access, - // so implement the relevant behavior ourselves - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = jQuery.data( elem ); - - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE11+ - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - jQuery._data( elem, "parsedAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - jQuery.data( this, key ); - } ); - } - - return arguments.length > 1 ? - - // Sets one value - this.each( function() { - jQuery.data( this, key, value ); - } ) : - - // Gets one value - // Try to fetch any internally stored data first - elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; - }, - - removeData: function( key ) { - return this.each( function() { - jQuery.removeData( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray( data ) ) { - queue = jQuery._data( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // not intended for public consumption - generates a queueHooks object, - // or returns the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return jQuery._data( elem, key ) || jQuery._data( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - jQuery._removeData( elem, type + "queue" ); - jQuery._removeData( elem, key ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = jQuery._data( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); - - -( function() { - var shrinkWrapBlocksVal; - - support.shrinkWrapBlocks = function() { - if ( shrinkWrapBlocksVal != null ) { - return shrinkWrapBlocksVal; - } - - // Will be changed later if needed. - shrinkWrapBlocksVal = false; - - // Minified: var b,c,d - var div, body, container; - - body = document.getElementsByTagName( "body" )[ 0 ]; - if ( !body || !body.style ) { - - // Test fired too early or in an unsupported environment, exit. - return; - } - - // Setup - div = document.createElement( "div" ); - container = document.createElement( "div" ); - container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; - body.appendChild( container ).appendChild( div ); - - // Support: IE6 - // Check if elements with layout shrink-wrap their children - if ( typeof div.style.zoom !== "undefined" ) { - - // Reset CSS: box-sizing; display; margin; border - div.style.cssText = - - // Support: Firefox<29, Android 2.3 - // Vendor-prefix box-sizing - "-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" + - "box-sizing:content-box;display:block;margin:0;border:0;" + - "padding:1px;width:1px;zoom:1"; - div.appendChild( document.createElement( "div" ) ).style.width = "5px"; - shrinkWrapBlocksVal = div.offsetWidth !== 3; - } - - body.removeChild( container ); - - return shrinkWrapBlocksVal; - }; - -} )(); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHidden = function( elem, el ) { - - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || - !jQuery.contains( elem.ownerDocument, elem ); - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, - maxIterations = 20, - currentValue = tween ? - function() { return tween.cur(); } : - function() { return jQuery.css( elem, prop, "" ); }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; - - // Adjust and apply - initialInUnit = initialInUnit / scale; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - length = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < length; i++ ) { - fn( - elems[ i ], - key, - raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[ 0 ], key ) : emptyGet; -}; -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([\w:-]+)/ ); - -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); - -var rleadingWhitespace = ( /^\s+/ ); - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|" + - "details|dialog|figcaption|figure|footer|header|hgroup|main|" + - "mark|meter|nav|output|picture|progress|section|summary|template|time|video"; - - - -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - - -( function() { - var div = document.createElement( "div" ), - fragment = document.createDocumentFragment(), - input = document.createElement( "input" ); - - // Setup - div.innerHTML = "
          a"; - - // IE strips leading whitespace when .innerHTML is used - support.leadingWhitespace = div.firstChild.nodeType === 3; - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - support.tbody = !div.getElementsByTagName( "tbody" ).length; - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - support.html5Clone = - document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - input.type = "checkbox"; - input.checked = true; - fragment.appendChild( input ); - support.appendChecked = input.checked; - - // Make sure textarea (and checkbox) defaultValue is properly cloned - // Support: IE6-IE11+ - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // #11217 - WebKit loses check when the name is after the checked attribute - fragment.appendChild( div ); - - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input = document.createElement( "input" ); - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 - // old WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<9 - // Cloned elements keep attachEvent handlers, we use addEventListener on IE9+ - support.noCloneEvent = !!div.addEventListener; - - // Support: IE<9 - // Since attributes and properties are the same in IE, - // cleanData must set properties to undefined rather than use removeAttribute - div[ jQuery.expando ] = 1; - support.attributes = !div.getAttribute( jQuery.expando ); -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - option: [ 1, "" ], - legend: [ 1, "
          ", "
          " ], - area: [ 1, "", "" ], - - // Support: IE8 - param: [ 1, "", "" ], - thead: [ 1, "", "
          " ], - tr: [ 2, "", "
          " ], - col: [ 2, "", "
          " ], - td: [ 3, "", "
          " ], - - // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, - // unless wrapped in a div with non-breaking characters in front of it. - _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
          ", "
          " ] -}; - -// Support: IE8-IE9 -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - var elems, elem, - i = 0, - found = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== "undefined" ? - context.querySelectorAll( tag || "*" ) : - undefined; - - if ( !found ) { - for ( found = [], elems = context.childNodes || context; - ( elem = elems[ i ] ) != null; - i++ - ) { - if ( !tag || jQuery.nodeName( elem, tag ) ) { - found.push( elem ); - } else { - jQuery.merge( found, getAll( elem, tag ) ); - } - } - } - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], found ) : - found; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var elem, - i = 0; - for ( ; ( elem = elems[ i ] ) != null; i++ ) { - jQuery._data( - elem, - "globalEval", - !refElements || jQuery._data( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/, - rtbody = / from table fragments - if ( !support.tbody ) { - - // String was a , *may* have spurious - elem = tag === "table" && !rtbody.test( elem ) ? - tmp.firstChild : - - // String was a bare or - wrap[ 1 ] === "
          " && !rtbody.test( elem ) ? - tmp : - 0; - - j = elem && elem.childNodes.length; - while ( j-- ) { - if ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), "tbody" ) && - !tbody.childNodes.length ) { - - elem.removeChild( tbody ); - } - } - } - - jQuery.merge( nodes, tmp.childNodes ); - - // Fix #12392 for WebKit and IE > 9 - tmp.textContent = ""; - - // Fix #12392 for oldIE - while ( tmp.firstChild ) { - tmp.removeChild( tmp.firstChild ); - } - - // Remember the top-level container for proper cleanup - tmp = safe.lastChild; - } - } - } - - // Fix #11356: Clear elements from fragment - if ( tmp ) { - safe.removeChild( tmp ); - } - - // Reset defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - if ( !support.appendChecked ) { - jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); - } - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( safe.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - tmp = null; - - return safe; -} - - -( function() { - var i, eventName, - div = document.createElement( "div" ); - - // Support: IE<9 (lack submit/change bubble), Firefox (lack focus(in | out) events) - for ( i in { submit: true, change: true, focusin: true } ) { - eventName = "on" + i; - - if ( !( support[ i ] = eventName in window ) ) { - - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) - div.setAttribute( eventName, "t" ); - support[ i ] = div.attributes[ eventName ].expando === false; - } - } - - // Null elements to avoid leaks in IE. - div = null; -} )(); - - -var rformElems = /^(?:input|select|textarea)$/i, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE9 -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - var tmp, events, t, handleObjIn, - special, eventHandle, handleObj, - handlers, type, namespaces, origType, - elemData = jQuery._data( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && - ( !e || jQuery.event.triggered !== e.type ) ? - jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - - // Add elem as a property of the handle fn to prevent a memory leak - // with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - var j, handleObj, tmp, - origCount, t, events, - special, handlers, type, - namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery._removeData( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - var handle, ontype, cur, - bubbleType, special, tmp, i, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && - jQuery._data( cur, "handle" ); - - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( - ( !special._default || - special._default.apply( eventPath.pop(), data ) === false - ) && acceptData( elem ) - ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - try { - elem[ type ](); - } catch ( e ) { - - // IE<9 dies on focus/blur to hidden element (#1486,#12518) - // only reproducible on winXP IE8 native, not IE9 in IE8 mode - } - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, j, ret, matched, handleObj, - handlerQueue = [], - args = slice.call( arguments ), - handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, matches, sel, handleObj, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Support (at least): Chrome, IE9 - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // - // Support: Firefox<=42+ - // Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343) - if ( delegateCount && cur.nodeType && - ( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) { - - /* jshint eqeqeq: false */ - for ( ; cur != this; cur = cur.parentNode || this ) { - /* jshint eqeqeq: true */ - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push( { elem: cur, handlers: matches } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: IE<9 - // Fix target property (#1925) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Support: Safari 6-8+ - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Support: IE<9 - // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) - event.metaKey = !!event.metaKey; - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " + - "metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split( " " ), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: ( "button buttons clientX clientY fromElement offsetX offsetY " + - "pageX pageY screenX screenY toElement" ).split( " " ), - filter: function( event, original ) { - var body, eventDoc, doc, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + - ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + - ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? - original.toElement : - fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - try { - this.focus(); - return false; - } catch ( e ) { - - // Support: IE<9 - // If we error on focus to hidden element (#1486, #12518), - // let .trigger() run the handlers - } - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - // Piggyback on a donor event to simulate a different one - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - - // Previously, `originalEvent: {}` was set here, so stopPropagation call - // would not be triggered on donor event, since in our own - // jQuery.event.stopPropagation function we had a check for existence of - // originalEvent.stopPropagation method, so, consequently it would be a noop. - // - // Guard for simulated events was moved to jQuery.event.stopPropagation function - // since `originalEvent` should point to the original event for the - // constancy with other events and for more focused logic - } - ); - - jQuery.event.trigger( e, null, elem ); - - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } - } : - function( elem, type, handle ) { - var name = "on" + type; - - if ( elem.detachEvent ) { - - // #8545, #7054, preventing memory leaks for custom events in IE6-8 - // detachEvent needed property on element, by name of that event, - // to properly expose it to GC - if ( typeof elem[ name ] === "undefined" ) { - elem[ name ] = null; - } - - elem.detachEvent( name, handle ); - } - }; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: IE < 9, Android < 4.0 - src.returnValue === false ? - returnTrue : - returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - if ( !e ) { - return; - } - - // If preventDefault exists, run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // Support: IE - // Otherwise set the returnValue property of the original event to false - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( !e || this.isSimulated ) { - return; - } - - // If stopPropagation exists, run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - - // Support: IE - // Set the cancelBubble property of the original event to true - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && e.stopImmediatePropagation ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://code.google.com/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -// IE submit delegation -if ( !support.submit ) { - - jQuery.event.special.submit = { - setup: function() { - - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? - - // Support: IE <=8 - // We use jQuery.prop instead of elem.form - // to allow fixing the IE8 delegated submit issue (gh-2332) - // by 3rd party polyfills/workarounds. - jQuery.prop( elem, "form" ) : - undefined; - - if ( form && !jQuery._data( form, "submit" ) ) { - jQuery.event.add( form, "submit._submit", function( event ) { - event._submitBubble = true; - } ); - jQuery._data( form, "submit", true ); - } - } ); - - // return undefined since we don't need an event listener - }, - - postDispatch: function( event ) { - - // If form was submitted by the user, bubble the event up the tree - if ( event._submitBubble ) { - delete event._submitBubble; - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event ); - } - } - }, - - teardown: function() { - - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !support.change ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._justChanged = true; - } - } ); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._justChanged && !event.isTrigger ) { - this._justChanged = false; - } - - // Allow triggered, simulated change events (#11500) - jQuery.event.simulate( "change", this, event ); - } ); - } - return false; - } - - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "change" ) ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event ); - } - } ); - jQuery._data( elem, "change", true ); - } - } ); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || - ( elem.type !== "radio" && elem.type !== "checkbox" ) ) { - - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return !rformElems.test( this.nodeName ); - } - }; -} - -// Support: Firefox -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome, Safari -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = jQuery._data( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = jQuery._data( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - jQuery._removeData( doc, fix ); - } else { - jQuery._data( doc, fix, attaches ); - } - } - }; - } ); -} - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - }, - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -var rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, - rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ), - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, - - // Support: IE 10-11, Edge 10240+ - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g, - safeFragment = createSafeFragment( document ), - fragmentDiv = safeFragment.appendChild( document.createElement( "div" ) ); - -// Support: IE<8 -// Manipulating tables requires a tbody -function manipulationTarget( elem, content ) { - return jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? - - elem.getElementsByTagName( "tbody" )[ 0 ] || - elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) : - elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( jQuery.find.attr( elem, "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - return elem; -} - -function cloneCopyEvent( src, dest ) { - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } -} - -function fixCloneNodeIssues( src, dest ) { - var nodeName, e, data; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - nodeName = dest.nodeName.toLowerCase(); - - // IE6-8 copies events bound via attachEvent when using cloneNode. - if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { - data = jQuery._data( dest ); - - for ( e in data.events ) { - jQuery.removeEvent( dest, e, data.handle ); - } - - // Event data gets referenced instead of copied if the expando gets copied too - dest.removeAttribute( jQuery.expando ); - } - - // IE blanks contents when cloning scripts, and tries to evaluate newly-set text - if ( nodeName === "script" && dest.text !== src.text ) { - disableScript( dest ).text = src.text; - restoreScript( dest ); - - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. - } else if ( nodeName === "object" ) { - if ( dest.parentNode ) { - dest.outerHTML = src.outerHTML; - } - - // This path appears unavoidable for IE9. When cloning an object - // element in IE9, the outerHTML strategy above is not sufficient. - // If the src has innerHTML and the destination does not, - // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( support.html5Clone && ( src.innerHTML && !jQuery.trim( dest.innerHTML ) ) ) { - dest.innerHTML = src.innerHTML; - } - - } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - - dest.defaultChecked = dest.checked = src.checked; - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.defaultSelected = dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var first, node, hasScripts, - scripts, doc, fragment, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android<4.1, PhantomJS<2 - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - jQuery.globalEval( - ( node.text || node.textContent || node.innerHTML || "" ) - .replace( rcleanScript, "" ) - ); - } - } - } - } - - // Fix #11809: Avoid leaking memory - fragment = first = null; - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - elems = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = elems[ i ] ) != null; i++ ) { - - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var destElements, node, clone, i, srcElements, - inPage = jQuery.contains( elem.ownerDocument, elem ); - - if ( support.html5Clone || jQuery.isXMLDoc( elem ) || - !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { - - clone = elem.cloneNode( true ); - - // IE<=8 does not properly clone detached, unknown element nodes - } else { - fragmentDiv.innerHTML = elem.outerHTML; - fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); - } - - if ( ( !support.noCloneEvent || !support.noCloneChecked ) && - ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - // Fix all IE cloning issues - for ( i = 0; ( node = srcElements[ i ] ) != null; ++i ) { - - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[ i ] ) { - fixCloneNodeIssues( node, destElements[ i ] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0; ( node = srcElements[ i ] ) != null; i++ ) { - cloneCopyEvent( node, destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - destElements = srcElements = node = null; - - // Return the cloned set - return clone; - }, - - cleanData: function( elems, /* internal */ forceAcceptData ) { - var elem, type, id, data, - i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, - attributes = support.attributes, - special = jQuery.event.special; - - for ( ; ( elem = elems[ i ] ) != null; i++ ) { - if ( forceAcceptData || acceptData( elem ) ) { - - id = elem[ internalKey ]; - data = id && cache[ id ]; - - if ( data ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - - delete cache[ id ]; - - // Support: IE<9 - // IE does not allow us to delete expando properties from nodes - // IE creates expando attributes along with the property - // IE does not have a removeAttribute function on Document nodes - if ( !attributes && typeof elem.removeAttribute !== "undefined" ) { - elem.removeAttribute( internalKey ); - - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://code.google.com/p/chromium/issues/detail?id=378607 - } else { - elem[ internalKey ] = undefined; - } - - deletedIds.push( id ); - } - } - } - } - } -} ); - -jQuery.fn.extend( { - - // Keep domManip exposed until 3.0 (gh-2225) - domManip: domManip, - - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().append( - ( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value ) - ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - - // If this is a select, ensure that it displays empty (#12336) - // Support: IE<9 - if ( elem.options && jQuery.nodeName( elem, "select" ) ) { - elem.options.length = 0; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined ) { - return elem.nodeType === 1 ? - elem.innerHTML.replace( rinlinejQuery, "" ) : - undefined; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - ( support.htmlSerialize || !rnoshimcache.test( value ) ) && - ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - - // Remove element nodes and prevent memory leaks - elem = this[ i ] || {}; - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - i = 0, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); - - -var iframe, - elemdisplay = { - - // Support: Firefox - // We have to pre-define these values for FF (#10227) - HTML: "block", - BODY: "block" - }; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ - -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - display = jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = ( iframe || jQuery( "