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/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..971d85ea5 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,26 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 + +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 + +# Issues with these labels will never be considered stale +exemptLabels: + - "Under Review" + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: true + +# Label to use when marking an issue as stale +staleLabel: stale + +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 854f14376..5529f8daf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -804,6 +804,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/db/zm_create.sql.in b/db/zm_create.sql.in index f67fc0116..dc2de15ee 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -518,6 +518,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@; @@ -560,7 +561,7 @@ CREATE TABLE `Servers` ( `Name` varchar(64) NOT NULL default '', `State_Id` int(10) unsigned, `Status` enum('Unknown','NotRunning','Running') NOT NULL default 'Unknown', - `Load` DECIMAL(5,1), + `CpuLoad` DECIMAL(5,1) default NULL, `TotalMem` bigint unsigned default null, `FreeMem` bigint unsigned default null, `TotalSwap` bigint unsigned default null, @@ -778,7 +779,8 @@ 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); -- -- Add some monitor preset values -- diff --git a/db/zm_update-1.31.30.sql b/db/zm_update-1.31.30.sql deleted file mode 100644 index c87b4409a..000000000 --- a/db/zm_update-1.31.30.sql +++ /dev/null @@ -1,20 +0,0 @@ -DROP TABLE IF EXISTS `Monitor_Status`; -CREATE TABLE `Monitor_Status` ( - `MonitorId` int(10) unsigned NOT NULL, - `Status` enum('Unknown','NotRunning','Running','Connected','Signal') NOT NULL default 'Unknown', - `CaptureFPS` DECIMAL(10,2) NOT NULL default 0, - `AnalysisFPS` DECIMAL(5,2) NOT NULL default 0, - PRIMARY KEY (`MonitorId`) -) ENGINE=MEMORY; - -SET SESSION sql_mode='NO_AUTO_VALUE_ON_ZERO'; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM Storage WHERE Name = 'Default' AND Id=0 AND Path='/var/cache/zoneminder/events' - ) > 0, - "SELECT 'Default Storage Area already exists.'", - "INSERT INTO Storage (Id,Name,Path,Scheme,ServerId) VALUES (0,'Default','/var/cache/zoneminder/events','Medium',NULL)" - )); - -PREPARE stmt FROM @s; -EXECUTE stmt; diff --git a/db/zm_update-1.31.40.sql b/db/zm_update-1.31.40.sql index 37a6e260b..50ffed736 100644 --- a/db/zm_update-1.31.40.sql +++ b/db/zm_update-1.31.40.sql @@ -12,7 +12,7 @@ PREPARE stmt FROM @s; EXECUTE stmt; -ALTER TABLE `Monitors` MODIFY `OutputCodec` int(10) UNSIGNED NOT NULL default 0; +ALTER TABLE `Monitors` MODIFY `OutputCodec` INT UNSIGNED default 0; SET @s = (SELECT IF( (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() 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/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/readme/README.Fedora b/distros/redhat/readme/README.Fedora index d0ccf8c4e..3366c31a6 100644 --- a/distros/redhat/readme/README.Fedora +++ b/distros/redhat/readme/README.Fedora @@ -3,6 +3,11 @@ What's New 1. See the ZoneMinder release notes for a list of new features: https://github.com/ZoneMinder/zoneminder/releases + +2. The contents of the ZoneMinder Apache config file have changed. In + addition, this ZoneMinder package now requires you to manually symlink the + ZoneMinder Apache config file. See new install step 6 and upgrade step 3 + below for details. New installs ============ diff --git a/distros/redhat/readme/README.Redhat7 b/distros/redhat/readme/README.Redhat7 index b70e7768d..bb2dcdaa0 100644 --- a/distros/redhat/readme/README.Redhat7 +++ b/distros/redhat/readme/README.Redhat7 @@ -4,6 +4,11 @@ What's New 1. See the ZoneMinder release notes for a list of new features: https://github.com/ZoneMinder/zoneminder/releases +2. The contents of the ZoneMinder Apache config file have changed. In + addition, this ZoneMinder package now requires you to manually symlink the + ZoneMinder Apache config file. See new install step 6 and upgrade step 3 + below for details. + New installs ============ @@ -80,7 +85,7 @@ New installs When in doubt, proceed with the default: sudo ln -s /etc/zm/www/zoneminder.conf /etc/httpd/conf.d/ - sudo dnf install mod_ssl + sudo yum install mod_ssl 7. Now start the web server: diff --git a/distros/redhat/systemd/zoneminder.tmpfiles.in b/distros/redhat/systemd/zoneminder.tmpfiles.in index de155b4cc..21e6119fe 100644 --- a/distros/redhat/systemd/zoneminder.tmpfiles.in +++ b/distros/redhat/systemd/zoneminder.tmpfiles.in @@ -1,5 +1,5 @@ D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_CACHEDIR@ 0755 @WEB_USER@ @WEB_GROUP@ -D @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@ +d @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_DIR_IMAGES@ 0755 @WEB_USER@ @WEB_GROUP@ diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index c52ccbad9..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.43 +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) @@ -74,8 +74,11 @@ BuildRequires: gcc-c++ BuildRequires: vlc-devel BuildRequires: libcurl-devel BuildRequires: libv4l-devel +BuildRequires: desktop-file-utils + +# ZoneMinder looks for and records the location of the ffmpeg binary during build +BuildRequires: ffmpeg BuildRequires: ffmpeg-devel -BuildRequires: desktop-file-utils # Required for mp4 container support BuildRequires: libmp4v2-devel @@ -89,6 +92,7 @@ BuildRequires: x264-devel Requires: php-mysqli Requires: php-common Requires: php-gd +%{?fedora:Requires: php-json} Requires: php-pecl-apcu %{?with_apcu_bc:Requires: php-pecl-apcu-bc} Requires: cambozola @@ -248,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/ubuntu1204/conf/apache2/zoneminder.conf b/distros/ubuntu1204/conf/apache2/zoneminder.conf index 81e9713db..8e2957cbf 100644 --- a/distros/ubuntu1204/conf/apache2/zoneminder.conf +++ b/distros/ubuntu1204/conf/apache2/zoneminder.conf @@ -9,7 +9,17 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" # Order matters. This Alias must come first Alias /zm/cache /var/cache/zoneminder/cache - Options -Indexes +FollowSymLinks + Options -Indexes +FollowSymLinks + AllowOverride None + + # Apache 2.4 + Require all granted + + + # Apache 2.2 + Order deny,allow + Allow from all + Alias /zm /usr/share/zoneminder/www @@ -45,4 +55,3 @@ Alias /zm /usr/share/zoneminder/www RewriteRule ^ index.php [L] RewriteBase /zm/api - diff --git a/distros/ubuntu1604/conf/apache2/zoneminder.conf b/distros/ubuntu1604/conf/apache2/zoneminder.conf index a51b153a9..598996bc0 100644 --- a/distros/ubuntu1604/conf/apache2/zoneminder.conf +++ b/distros/ubuntu1604/conf/apache2/zoneminder.conf @@ -9,7 +9,17 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" # Order matters. This alias must come first. Alias /zm/cache /var/cache/zoneminder/cache - Options -Indexes +FollowSymLinks + Options -Indexes +FollowSymLinks + AllowOverride None + + # Apache 2.4 + Require all granted + + + # Apache 2.2 + Order deny,allow + Allow from all + Alias /zm /usr/share/zoneminder/www 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/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 6eb41e57e..c6136717a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -2711,7 +2711,7 @@ our @options = ( }, { name => 'ZM_TELEMETRY_DATA', - default => 'yes', + default => 'no', description => 'Send usage information to ZoneMinder', help => q` Enable collection of usage information of the local system and send @@ -2954,7 +2954,7 @@ our @options = ( When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern - browsers. When this condiction has occured, ZoneMinder will write a + browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extenstion has ben installed, @@ -2963,6 +2963,37 @@ our @options = ( type => $types{boolean}, category => 'web', }, + { + name => 'ZM_WEB_FILTER_SOURCE', + default => 'Hostname', + description => 'How to filter information in the source column.', + help => q` + This option only affects monitors with a source type of Ffmpeg, + Libvlc, or WebSite. This setting controls what information is + displayed in the Source column on the console. Selecting 'None' + will not filter anything. The entire source string will be + displayed, which may contain sensitive information. Selecting + 'NoCredentials' will strip out usernames and passwords from the + string. If there are any port numbers in the string and they are + common (80, 554, etc) then those will be removed as well. + Selecting 'Hostname' will filter out all information except for + the hostname or ip address. When in doubt, stay with the default + 'Hostname'. This feature uses the php function 'url_parts' to + identify the various pieces of the url. If the url in question + is unusual or not standard in some way, then filtering may not + produce the desired results. + `, + type => { + db_type =>'string', + hint =>'None|Hostname|NoCredentials', + pattern =>qr|^([NH])|i, + format =>q( ($1 =~ /^Non/) + ? 'None' + : ($1 =~ /^H/ ? 'Hostname' : 'NoCredentials' ) + ) + }, + category => 'web', + }, { name => 'ZM_WEB_H_REFRESH_MAIN', default => '60', 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/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index c39a6a73f..fcc1392a2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -108,7 +108,7 @@ sub zmDbConnect { , $Config{ZM_DB_PASS} ); }; - if ( !$dbh or $@ ) { + if ( !$dbh or $@ ) { Error("Error reconnecting to db: errstr:$DBI::errstr error val:$@"); } else { $dbh->{AutoCommit} = 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 8a9c78ada..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(); @@ -602,7 +602,7 @@ Debug("Files to move @files"); die "Unable to add key for $filename"; } my $duration = time - $starttime; - Debug("PUT to S3 " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($size/$duration) . "/sec"); + Debug("PUT to S3 " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec'); } # end foreach file. $moved = 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 1b158b2ac..f1a72c8cf 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -307,7 +307,7 @@ sub Sql { if ( $self->{AutoMessage} ) { # Include all events, including events that are still ongoing # and have no EndTime yet - $sql .= ' AND ( '.$self->{Sql}.' )'; + $sql .= ' WHERE ( '.$self->{Sql}.' )'; } else { # Only include closed events (events with valid EndTime) $sql .= ' WHERE (E.EndTime IS NOT NULL) AND ( '.$self->{Sql}.' )'; 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 8dbcf8efa..3f84052e5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -198,6 +198,7 @@ sub initialise( @ ) { my $this = shift; my %options = @_; + $this->{hasTerm} = -t STDERR; $this->{id} = $options{id} if defined($options{id}); $this->{logPath} = $options{logPath} if defined($options{logPath}); @@ -245,7 +246,8 @@ sub initialise( @ ) { $tempSyslogLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')); if ( $Config{ZM_LOG_DEBUG} ) { - foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) { + # Splitting on an empty string doesn't return an empty string, it returns an empty array + foreach my $target ( $Config{ZM_LOG_DEBUG_TARGET} ? split(/\|/, $Config{ZM_LOG_DEBUG_TARGET}) : '' ) { if ( $target eq $this->{id} || $target eq '_'.$this->{id} || $target eq $this->{idRoot} @@ -278,6 +280,9 @@ sub initialise( @ ) { $this->{initialised} = !undef; + # this function can get called on a previously initialized log Object, so clean any sth's + $this->{sth} = undef; + Debug( 'LogOpts: level='.$codes{$this->{level}} .'/'.$codes{$this->{effectiveLevel}} .', screen='.$codes{$this->{termLevel}} @@ -319,6 +324,8 @@ sub reinitialise { my $screenLevel = $this->termLevel(); $this->termLevel(NOLOG); $this->termLevel($screenLevel) if $screenLevel > NOLOG; + + $this->{sth} = undef; } # Prevents undefined logging levels @@ -392,6 +399,12 @@ sub level { # ICON: I am remarking this out because I don't see the point of having an effective level, if we are just going to set it to level. #$this->{effectiveLevel} = $this->{level} if ( $this->{level} > $this->{effectiveLevel} ); + # ICON: The point is that LOG_DEBUG can be set either in db or in env var and will get passed in here. + # So this will turn on debug, even if not output has Debug level turned on. I think it should be the other way around + + # ICON: Let's try this line instead. effectiveLevel is 1 DEBUG from above, but LOG_DEBUG is off, then $this->level will be 0, and + # so effectiveLevel will become 0 + $this->{effectiveLevel} = $this->{level} if ( $this->{level} < $this->{effectiveLevel} ); } return $this->{level}; } @@ -474,7 +487,7 @@ sub openSyslog { sub closeSyslog { my $this = shift; -#closelog(); + closelog(); } sub logFile { @@ -517,54 +530,59 @@ sub logPrint { 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]' + , strftime('%x %H:%M:%S', localtime($seconds)) + , $microseconds + , $this->{id} + , $$ + , $codes{$level} + , $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() ) or ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) { - - my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )'; - $this->{sth} = $this->{dbh}->prepare_cached($sql); - if ( !$this->{sth} ) { + if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) { + $this->{sth} = undef; + if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) { + #print(STDERR "Can't log to database: "); $this->{databaseLevel} = NOLOG; - Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); - } else { - my $res = $this->{sth}->execute($seconds+($microseconds/1000000.0) - , $this->{id} - , $$ - , $level - , $code - , $string - , $this->{fileName} - ); - if ( !$res ) { - $this->{databaseLevel} = NOLOG; - Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr()); - } + return; } - } else { - print(STDERR "Can't log to database: "); + } + + my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )'; + $this->{sth} = $this->{dbh}->prepare_cached($sql) if ! $this->{sth}; + if ( !$this->{sth} ) { + $this->{databaseLevel} = NOLOG; + Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); + return; + } + + my $res = $this->{sth}->execute($seconds+($microseconds/1000000.0) + , $this->{id} + , $$ + , $level + , $codes{$level} + , $string + , $this->{fileName} + ); + if ( !$res ) { + $this->{databaseLevel} = NOLOG; + Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr()); } } # end if doing db logging } # end if level < effectivelevel 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/zmdc.pl.in b/scripts/zmdc.pl.in index 7f179db71..715c972f2 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -119,13 +119,13 @@ if ( $command eq 'version' ) { } my $needs_daemon = $command !~ /(?:startup|shutdown|status|check|logrot|version)/; my $daemon = shift @ARGV; -if ( $needs_daemon && ! $daemon ) { +if ( $needs_daemon && !$daemon ) { print(STDERR "No daemon given\n"); pod2usage(-exitstatus => -1); } my @args; -my $daemon_patt = '('.join( '|', @daemons ).')'; +my $daemon_patt = '('.join('|', @daemons).')'; if ( $needs_daemon ) { if ( $daemon =~ /^${daemon_patt}$/ ) { $daemon = $1; @@ -139,7 +139,7 @@ foreach my $arg ( @ARGV ) { # Detaint arguments, if they look ok #if ( $arg =~ /^(-{0,2}[\w]+)/ ) if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) { - push( @args, $1 ); + push @args, $1; } else { print(STDERR "Bogus argument '$arg' found"); exit(-1); @@ -172,7 +172,7 @@ if ( !$server_up ) { exit(); } elsif ( $command ne 'startup' ) { print("Unable to connect to server using socket at " . SOCK_FILE . "\n"); - exit( -1 ); + exit(-1); } # The server isn't there @@ -210,7 +210,7 @@ if ( ($command eq 'check') && !$daemon ) { # The server is there, connect to it CLIENT->autoflush(); -my $message = join(';', $command, ( $daemon ? $daemon : () ), @args ); +my $message = join(';', $command, ( $daemon ? $daemon : () ), @args); print(CLIENT $message); shutdown(CLIENT, 1); while( my $line = ) { @@ -242,16 +242,31 @@ use constant KILL_DELAY => 60; # seconds to wait between sending TERM and sendin our %cmd_hash; our %pid_hash; our %terminating_processes; +our %pids_to_reap; our $zm_terminate = 0; sub run { + + # Call this first otherwise stdout/stderror redirects to the pidfile = bad + if ( open(my $PID, '>', ZM_PID) ) { + print($PID $$); + close($PID); + } else { + # Log not initialized at this point so use die instead + die "Can't open pid file at ".ZM_PID."\n"; + } + my $fd = 0; + + # THis also closes dbh and CLIENT and SERVER while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } setpgrp(); + # dbh got closed with the rest of the fd's above, so need to reconnect. + my $dbh = zmDbConnect(1); logInit(); dPrint(ZoneMinder::Logger::INFO, 'Server starting at ' @@ -259,24 +274,16 @@ sub run { ."\n" ); - if ( open(my $PID, '>', ZM_PID) ) { - print($PID $$); - close($PID); - } else { - Error("Can't open pid file at " . ZM_PID); - } - # Tell any existing processes to die, wait 1 second between TERM and KILL killAll(1); dPrint(ZoneMinder::Logger::INFO, 'Socket should be open at ' .main::SOCK_FILE); - my $dbh = zmDbConnect(1); socket(SERVER, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); unlink(main::SOCK_FILE) or Error('Unable to unlink ' . main::SOCK_FILE .". Error message was: $!") if -e main::SOCK_FILE; bind(SERVER, $saddr) or Fatal("Can't bind to " . main::SOCK_FILE . ": $!"); listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); - $SIG{CHLD} = \&reaper; + $SIG{CHLD} = \&chld_sig_handler; $SIG{INT} = \&shutdown_sig_handler; $SIG{TERM} = \&shutdown_sig_handler; $SIG{ABRT} = \&shutdown_sig_handler; @@ -287,35 +294,28 @@ sub run { my $win = $rin; my $ein = $win; my $timeout = 1; - my $Server = undef; my $secs_count = 0; - if ( $Config{ZM_SERVER_ID} ) { - require ZoneMinder::Server; - $Server = new ZoneMinder::Server($Config{ZM_SERVER_ID}); - dPrint(ZoneMinder::Logger::INFO, 'Loading Server record have ' . $$Server{Name}); - } - while( !$zm_terminate ) { if ( $Config{ZM_SERVER_ID} ) { if ( ! ( $secs_count % 60 ) ) { - Debug("Connecting"); while ( (!$zm_terminate) and !($dbh and $dbh->ping()) ) { - Warning("Not connected to db ($dbh)".($dbh?" ping(".$dbh->ping().")":''). ($DBI::errstr?" errstr($DBI::errstr)":'').' Reconnecting'); + Warning("Not connected to db ($dbh)".($dbh?' ping('.$dbh->ping().')':''). ($DBI::errstr?" errstr($DBI::errstr)":'').' Reconnecting'); $dbh = zmDbConnect(); } + last if $zm_terminate; + my @cpuload = CpuLoad(); - Debug("UPdating Server record @cpuload"); + Debug("Updating Server record @cpuload"); if ( ! defined $dbh->do(q{UPDATE Servers SET Status=?,CpuLoad=?,TotalMem=?,FreeMem=?,TotalSwap=?,FreeSwap=? WHERE Id=?}, undef, 'Running', $cpuload[0], &totalmem, &freemem, &totalswap, &freeswap, $Config{ZM_SERVER_ID} ) ) { - Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); + Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID} :".$dbh->errstr()); } } $secs_count += 1; } my $nfound = select(my $rout = $rin, undef, undef, $timeout); -Debug("Aftere select $nfound"); if ( $nfound > 0 ) { if ( vec($rout, fileno(SERVER), 1) ) { my $paddr = accept(CLIENT, SERVER); @@ -337,7 +337,7 @@ Debug("Aftere select $nfound"); # Do nothing, this is all we're here for dPrint(ZoneMinder::Logger::WARNING, "Already running, ignoring command '$command'\n"); } elsif ( $command eq 'shutdown' ) { - # Breka out of while loop + # Break out of while loop last; } elsif ( $command eq 'check' ) { check($daemon, @args); @@ -370,19 +370,17 @@ Debug("Aftere select $nfound"); #print( "Select timed out\n" ); } -Debug("restartPending"); restartPending(); -Debug("check_for_processes_to_kill"); - check_for_processes_to_kill(); - + check_for_processes_to_kill() if %terminating_processes; + reaper() if %pids_to_reap; } # end while dPrint(ZoneMinder::Logger::INFO, 'Server exiting at ' - .strftime( '%y/%m/%d %H:%M:%S', localtime() ) + .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); if ( $Config{ZM_SERVER_ID} ) { - $dbh = zmDbConnect() if ! $dbh->ping(); + $dbh = zmDbConnect() if ! ($dbh and $dbh->ping()); if ( ! defined $dbh->do(q{UPDATE Servers SET Status='NotRunning' WHERE Id=?}, undef, $Config{ZM_SERVER_ID}) ) { Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); } @@ -391,6 +389,7 @@ Debug("check_for_processes_to_kill"); } sub cPrint { + # One thought here, if no client exists to read these... does it block? if ( fileno(CLIENT) ) { print CLIENT @_ } @@ -399,9 +398,7 @@ sub cPrint { # I think the purpose of this is to echo the logs to the client process so it can then display them. sub dPrint { my $logLevel = shift; - if ( fileno(CLIENT) ) { - print CLIENT @_ - } + cPrint(@_); if ( $logLevel == ZoneMinder::Logger::DEBUG ) { Debug(@_); } elsif ( $logLevel == ZoneMinder::Logger::INFO ) { @@ -435,12 +432,14 @@ sub start { my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); - Debug("Blocking SIGCHLD"); sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!"); - Debug("forking"); + # Apparently the child closing the db connection can affect the parent. + zmDbDisconnect(); if ( my $cpid = fork() ) { + + $dbh = zmDbConnect(1); # This logReinit is required. Not sure why. - logReinit(); + #logReinit(); $process->{pid} = $cpid; $process->{started} = time(); @@ -453,9 +452,10 @@ sub start { $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; sigprocmask(SIG_SETMASK, $sigset) or Fatal("Can't restore SIGCHLD: $!"); - Debug("unblocking child"); } elsif ( defined($cpid) ) { -# Force reconnection to the db. + # Child process + + # Force reconnection to the db. $dbh got copied, but isn't really valid anymore. $dbh = zmDbConnect(1); logReinit(); @@ -472,7 +472,7 @@ sub start { my @good_args; foreach my $arg ( @args ) { -# Detaint arguments, if they look ok + # Detaint arguments, if they look ok if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) { push @good_args, $1; } else { @@ -488,8 +488,8 @@ sub start { POSIX::close($fd++); } -# Child process $SIG{CHLD} = 'DEFAULT'; + $SIG{HUP} = 'DEFAULT'; $SIG{INT} = 'DEFAULT'; $SIG{TERM} = 'DEFAULT'; $SIG{ABRT} = 'DEFAULT'; @@ -516,7 +516,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(); } @@ -635,15 +635,24 @@ sub shutdown_sig_handler { $zm_terminate = 1; } -sub reaper { +sub chld_sig_handler { my $saved_status = $!; # Wait for a child to terminate while ( (my $cpid = waitpid(-1, WNOHANG)) > 0 ) { - my $status = $?; + $pids_to_reap{$cpid} = { status=>$?, stopped=>time() }; + } # end while waitpid + $SIG{CHLD} = \&chld_sig_handler; + $! = $saved_status; +} +sub reaper { + foreach my $cpid ( keys %pids_to_reap ) { my $process = $pid_hash{$cpid}; delete $pid_hash{$cpid}; + my $reap_info = $pids_to_reap{$cpid}; + my ( $status, $stopped ) = @$reap_info{'status','stopped'}; + delete $pids_to_reap{$cpid}; if ( !$process ) { dPrint(ZoneMinder::Logger::INFO, "Can't find child with pid of '$cpid'\n"); @@ -652,7 +661,7 @@ sub reaper { delete $terminating_processes{$$process{command}}; delete $$process{term_sent_at}; - $process->{stopped} = time(); + $process->{stopped} = $stopped; $process->{runtime} = ($process->{stopped}-$process->{started}); delete $process->{pid}; @@ -701,15 +710,12 @@ sub reaper { $process->{delay} = $Config{ZM_MAX_RESTART_DELAY}; } } - Debug("Delay for $$process{command} is now $$process{delay}"); + #Debug("Delay for $$process{command} is now $$process{delay}"); } else { delete $cmd_hash{$$process{command}}; } - } # end while waitpid - $SIG{CHLD} = \&reaper; - $! = $saved_status; - Debug("Leaving reaper"); -} + } # end foreach pid_to_reap +} # end sub reaper sub restartPending { # Restart any pending processes, we list them first because cmd_hash may change in foreach @@ -720,7 +726,6 @@ sub restartPending { start($process->{daemon}, @{$process->{args}}); } } - Debug("done restartPending"); } sub shutdownAll { @@ -730,6 +735,8 @@ sub shutdownAll { send_stop(1, $pid_hash{$pid}); } while ( keys %terminating_processes ) { + + reaper() if %pids_to_reap; check_for_processes_to_kill(); if ( %terminating_processes ) { Debug("Still " . %terminating_processes . ' to die. sleeping'); @@ -808,7 +815,7 @@ sub status { foreach my $process ( values %cmd_hash ) { if ( $process->{pending} ) { dPrint(ZoneMinder::Logger::DEBUG, "'$process->{command}' pending at " - .strftime( '%y/%m/%d %H:%M:%S', localtime($process->{pending})) + .strftime('%y/%m/%d %H:%M:%S', localtime($process->{pending})) ."\n" ); } diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index fa59d91e0..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':()), @@ -309,7 +316,7 @@ sub checkFilter { } if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { if ( !$event->{Messaged} ) { - $delete_ok = undef if !sendMessage($filter, $event); + $delete_ok = undef if !sendMessage($filter, $Event); } } if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { @@ -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"); @@ -833,7 +840,7 @@ sub sendEmail { sub sendMessage { my $filter = shift; - my $event = shift; + my $Event = shift; if ( ! $Config{ZM_FROM_EMAIL} ) { Error('No from email address defined, not sending message'); @@ -846,10 +853,10 @@ sub sendMessage { Info('Creating notification message'); - my $subject = substituteTags($Config{ZM_MESSAGE_SUBJECT}, $filter, $event); + my $subject = substituteTags($Config{ZM_MESSAGE_SUBJECT}, $filter, $Event); return 0 if !$subject; my @attachments; - my $body = substituteTags($Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments); + my $body = substituteTags($Config{ZM_MESSAGE_BODY}, $filter, $Event, \@attachments); return 0 if !$body; Info("Sending notification message '$subject'"); @@ -930,11 +937,11 @@ sub sendMessage { my $sql = 'UPDATE Events SET Messaged = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute($event->{Id}) + my $res = $sth->execute($Event->{Id}) or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); return 1; -} +} # end sub sendMessage sub executeCommand { my $filter = shift; diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 70934a8ef..15512ff57 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,64 @@ 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 isActiveSanityCheck(); # 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 +154,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 +164,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 +196,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,55 +206,58 @@ 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 ( $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 ( $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} ) { - if ( $Server and exists $$Server{'zmaudit'} and ! $$Server{'zmaudit'} ) { - Debug("Not running zmaudit.pl because it is turned off for this server."); + if ( $Server and exists $$Server{zmaudit} and ! $$Server{zmaudit} ) { + Debug('Not running zmaudit.pl because it is turned off for this server.'); } else { runCommand('zmdc.pl start zmaudit.pl -c'); } } if ( $Config{ZM_OPT_TRIGGERS} ) { - if ( $Server and exists $$Server{'zmtrigger'} and ! $$Server{'zmtrigger'} ) { - Debug("Not running zmtrigger.pl because it is turned off for this server."); + if ( $Server and exists $$Server{zmtrigger} and ! $$Server{zmtrigger} ) { + Debug('Not running zmtrigger.pl because it is turned off for this server.'); } else { runCommand('zmdc.pl start zmtrigger.pl'); } @@ -272,25 +275,25 @@ if ( $command =~ /^(?:start|restart)$/ ) { if ($Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { runCommand('zmdc.pl start zmeventnotification.pl'); } - if ( $Server and exists $$Server{'zmstats'} and ! $$Server{'zmstats'} ) { - Debug("Not running zmstats.pl because it is turned off for this server."); + if ( $Server and exists $$Server{zmstats} and ! $$Server{zmstats} ) { + Debug('Not running zmstats.pl because it is turned off for this server.'); } else { runCommand('zmdc.pl start zmstats.pl'); } } 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 { @@ -299,11 +302,11 @@ sub isActiveSanityCheck { $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 ) { # PP - no row, or too many rows. Either case is an error @@ -313,68 +316,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 { @@ -382,24 +370,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/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 10fd55e11..b17750397 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -69,41 +69,44 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); -Info( "Watchdog starting, pausing for ".START_DELAY." seconds\n" ); -sleep( START_DELAY ); +Info('Watchdog starting, pausing for '.START_DELAY.' seconds'); +sleep(START_DELAY); my $dbh = zmDbConnect(); my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors'; -my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); +my $sth = $dbh->prepare_cached($sql) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); while( 1 ) { my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) - or Fatal( "Can't execute: ".$sth->errstr() ); + or Fatal('Can\'t execute: '.$sth->errstr()); while( my $monitor = $sth->fetchrow_hashref() ) { - my $now = time(); next if $monitor->{Function} eq 'None'; next if $monitor->{Type} eq 'WebSite'; + my $now = time(); my $restart = 0; - if ( zmMemVerify( $monitor ) ) { + if ( zmMemVerify($monitor) ) { # Check we have got an image recently - my $capture_time = zmGetLastWriteTime( $monitor ); + my $capture_time = zmGetLastWriteTime($monitor); if ( !defined($capture_time) ) { # Can't read from shared data - Debug( "LastWriteTime is not defined." ); - zmMemInvalidate( $monitor ); + Debug('LastWriteTime is not defined.'); + zmMemInvalidate($monitor); next; } - Debug( "LastWriteTime is = $capture_time." ); + Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); if ( !$capture_time ) { - my $startup_time = zmGetStartupTime( $monitor ); + my $startup_time = zmGetStartupTime($monitor); if ( $now - $startup_time > $Config{ZM_WATCH_MAX_DELAY} ) { - Info( "Restarting capture daemon for ".$monitor->{Name}.", no image since startup. Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}\n" ); + Info( + "Restarting capture daemon for $$monitor{Name}, no image since startup. ". + "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" + ); $restart = 1; } else { # We can't get the last capture time so can't be sure it's died, it might just be starting up. - zmMemInvalidate( $monitor ); + zmMemInvalidate($monitor); next; } } @@ -115,44 +118,44 @@ while( 1 ) { : $Config{ZM_WATCH_MAX_DELAY} ; my $image_delay = $now-$capture_time; - Debug( "Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay\n" ); + Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay"); if ( $image_delay > $max_image_delay ) { - Info( "Restarting capture daemon for " - .$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)\n" + Info("Restarting capture daemon for " + .$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)" ); $restart = 1; } } # end if ! restart } else { - Info( "Restarting capture daemon for ".$monitor->{Name}.", shared data not valid\n" ); + Info("Restarting capture daemon for $monitor->{Name}, shared data not valid"); $restart = 1; } if ( $restart ) { # Because zma depends on zmc, and zma can hold the shm in place, preventing zmc from using the space in /dev/shm, # we need to stop zma before restarting zmc. - runCommand( "zmdc.pl stop zma -m $$monitor{Id}" ) if $monitor->{Function} ne 'Monitor'; + runCommand("zmdc.pl stop zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; my $command; if ( $monitor->{Type} eq 'Local' ) { $command = "zmdc.pl restart zmc -d $monitor->{Device}"; } else { $command = "zmdc.pl restart zmc -m $monitor->{Id}"; } - runCommand( $command ); - runCommand( "zmdc.pl start zma -m $$monitor{Id}" ) if $monitor->{Function} ne 'Monitor'; + runCommand($command); + runCommand("zmdc.pl start zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; } elsif ( $monitor->{Function} ne 'Monitor' ) { # Now check analysis daemon $restart = 0; # Check we have got an image recently - my $image_time = zmGetLastReadTime( $monitor ); + my $image_time = zmGetLastReadTime($monitor); if ( !defined($image_time) ) { # Can't read from shared data $restart = 1; - Error("Error reading shared data for $$monitor{Id} $$monitor{Name}\n"); + Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); } elsif ( !$image_time ) { # We can't get the last capture time so can't be sure it's died. $restart = 1; - Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero.\n"); + Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); } else { my $max_image_delay = ( $monitor->{MaxFPS} @@ -162,34 +165,34 @@ while( 1 ) { : $Config{ZM_WATCH_MAX_DELAY} ; my $image_delay = $now-$image_time; - Debug( "Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay\n" ); + Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay"); if ( $image_delay > $max_image_delay ) { - Info( "Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," - ." time since last analysis $image_delay seconds ($now-$image_time)\n" + Info("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," + ." time since last analysis $image_delay seconds ($now-$image_time)" ); $restart = 1; } } if ( $restart ) { - Info( "Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); + Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); my $command; if ( $monitor->{Type} eq 'Local' ) { $command = "zmdc.pl restart zmc -d $monitor->{Device}"; } else { $command = "zmdc.pl restart zmc -m $monitor->{Id}"; } - runCommand( $command ); + runCommand($command); } # end if restart } # end if check analysis daemon # Prevent open handles building up if we have connect to shared memory - zmMemInvalidate( $monitor ); # Close our file handle to the zmc process we are about to end + zmMemInvalidate($monitor); # Close our file handle to the zmc process we are about to end } # end foreach monitor - sleep( $Config{ZM_WATCH_CHECK_INTERVAL} ); + sleep($Config{ZM_WATCH_CHECK_INTERVAL}); } # end while (1) -Info( "Watchdog exiting\n" ); +Info("Watchdog exiting"); exit(); 1; diff --git a/src/zm_camera.h b/src/zm_camera.h index 292ece560..c69321cea 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -104,7 +104,10 @@ public: bool CanCapture() const { return( capture ); } - bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); } + bool SupportsNativeVideo() const { + return (type == FFMPEG_SRC); + //return (type == FFMPEG_SRC )||(type == REMOTE_SRC); + } virtual int PrimeCapture() { return( 0 ); } virtual int PreCapture()=0; diff --git a/src/zm_db.cpp b/src/zm_db.cpp index d4a927f3b..806ef8e5c 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -24,7 +24,7 @@ #include "zm_db.h" MYSQL dbconn; -Mutex db_mutex; +RecursiveMutex db_mutex; bool zmDbConnected = false; @@ -91,15 +91,15 @@ void zmDbClose() { } MYSQL_RES * zmDbFetch(const char * query) { - if ( ! zmDbConnected ) { + if ( !zmDbConnected ) { Error("Not connected."); return NULL; } db_mutex.lock(); if ( mysql_query(&dbconn, query) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); db_mutex.unlock(); + Error("Can't run query: %s", mysql_error(&dbconn)); return NULL; } Debug(4, "Success running query: %s", query); diff --git a/src/zm_db.h b/src/zm_db.h index de47a6108..c707ad2b7 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -41,7 +41,7 @@ class zmDbRow { }; extern MYSQL dbconn; -extern Mutex db_mutex; +extern RecursiveMutex db_mutex; bool zmDbConnect(); void zmDbClose(); diff --git a/src/zm_event.cpp b/src/zm_event.cpp index ee533937f..868fd8971 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -67,7 +67,8 @@ Event::Event( untimedEvent = true; start_time = now; } else if ( start_time.tv_sec > now.tv_sec ) { - Error("StartTime in the future %u.%u > %u.%u", + Error( + "StartTime in the future %u.%u > %u.%u", start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec ); start_time = now; @@ -89,8 +90,8 @@ Event::Event( snprintf(sql, sizeof(sql), "INSERT INTO Events " "( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme )" - " VALUES " - "( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )", + " VALUES " + "( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )", monitor->Id(), storage->Id(), start_time.tv_sec, @@ -183,12 +184,13 @@ Event::Event( } // Create empty id tag file - std::string id_file = stringtf("%s/.%" PRIu64, path, id); + std::string id_file = stringtf("%s/.%" PRIu64, path.c_str(), id); if ( FILE *id_fp = fopen(id_file.c_str(), "w") ) fclose(id_fp); else Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno)); } // deep storage or not + Debug(2,"Created event %d at %s", id, path.c_str()); last_db_frame = 0; @@ -288,7 +290,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. @@ -436,7 +438,7 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str frames++; static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); + snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path.c_str(), frames); if ( save_jpegs & 1 ) { Debug(1, "Writing pre-capture frame %d", frames); WriteFrameImage(images[i], *(timestamps[i]), event_file); @@ -446,7 +448,7 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str // neccessarily be of the motion. But some events are less than 10 frames, // so I am changing this to 1, but we should overwrite it later with a better snapshot. if ( frames == 1 ) { - std::string snapshot_file = std::string(path) + "/snapshot.jpg"; + std::string snapshot_file = path + "/snapshot.jpg"; WriteFrameImage(images[i], *(timestamps[i]), snapshot_file.c_str()); } } @@ -508,7 +510,7 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a frames++; static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); + snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path.c_str(), frames); if ( save_jpegs & 1 ) { Debug(1, "Writing capture frame %d to %s", frames, event_file); @@ -519,9 +521,9 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a //If this is the first frame, we should add a thumbnail to the event directory // On the first frame, max_score will be zero, this effectively makes us write out a thumbnail // for the first frame as well. - if ( score > max_score ) { - std::string snapshot_file = std::string(path) + "/snapshot.jpg"; - WriteFrameImage( image, timestamp, snapshot_file.c_str() ); + if ( frames == 1 || score > (int)max_score ) { + std::string snapshot_file = path + "/snapshot.jpg"; + WriteFrameImage(image, timestamp, snapshot_file.c_str()); } } @@ -587,11 +589,12 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a 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 ( save_jpegs & 2 ) { - WriteFrameImage(alarm_image, timestamp, event_file, true); + snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path.c_str(), 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_local_camera.cpp b/src/zm_local_camera.cpp index 4e1349562..a7b0ac517 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -41,16 +41,16 @@ static unsigned int BigEndian; -static int vidioctl( int fd, int request, void *arg ) { +static int vidioctl(int fd, int request, void *arg) { int result = -1; do { - result = ioctl( fd, request, arg ); - } while ( result == -1 && errno == EINTR ); - return( result ); + result = ioctl(fd, request, arg); + } while( result == -1 && errno == EINTR ); + return result; } #if HAVE_LIBSWSCALE -static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette ) { +static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; #if ZM_HAS_V4L2 @@ -146,7 +146,7 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette //case V4L2_PIX_FMT_YVYU : default : { - Fatal( "Can't find swscale format for palette %d", palette ); + Fatal("Can't find swscale format for palette %d", palette); break; // These are all spare and may match some of the above pixFormat = AV_PIX_FMT_YUVJ420P; @@ -174,20 +174,20 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; } - } - } + } // end switch palette + } // end if v4l2 #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { switch( palette ) { case VIDEO_PALETTE_RGB32 : - if(BigEndian) + if ( BigEndian ) pixFormat = AV_PIX_FMT_ARGB; else pixFormat = AV_PIX_FMT_BGRA; break; case VIDEO_PALETTE_RGB24 : - if(BigEndian) + if ( BigEndian ) pixFormat = AV_PIX_FMT_RGB24; else pixFormat = AV_PIX_FMT_BGR24; @@ -213,7 +213,7 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette break; default : { - Fatal( "Can't find swscale format for palette %d", palette ); + Fatal("Can't find swscale format for palette %d", palette); break; // These are all spare and may match some of the above pixFormat = AV_PIX_FMT_YUVJ420P; @@ -242,19 +242,47 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; } - } - } + } // end switch palette + } // end if v4l1 #endif // ZM_HAS_V4L1 - return( pixFormat ); -} + return pixFormat; +} // end getFfPixFormatFromV4lPalette #endif // HAVE_LIBSWSCALE #if ZM_HAS_V4L2 static char palette_desc[32]; /* Automatic format selection preferred formats */ -static const uint32_t prefered_rgb32_formats[] = {V4L2_PIX_FMT_BGR32, V4L2_PIX_FMT_RGB32, V4L2_PIX_FMT_BGR24, V4L2_PIX_FMT_RGB24, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_YUV422P, V4L2_PIX_FMT_YUV420}; -static const uint32_t prefered_rgb24_formats[] = {V4L2_PIX_FMT_BGR24, V4L2_PIX_FMT_RGB24, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_YUV422P, V4L2_PIX_FMT_YUV420}; -static const uint32_t prefered_gray8_formats[] = {V4L2_PIX_FMT_GREY, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_YUV422P, V4L2_PIX_FMT_YUV420}; +static const uint32_t prefered_rgb32_formats[] = { + V4L2_PIX_FMT_BGR32, + V4L2_PIX_FMT_RGB32, + V4L2_PIX_FMT_BGR24, + V4L2_PIX_FMT_RGB24, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_UYVY, + V4L2_PIX_FMT_JPEG, + V4L2_PIX_FMT_MJPEG, + V4L2_PIX_FMT_YUV422P, + V4L2_PIX_FMT_YUV420 +}; +static const uint32_t prefered_rgb24_formats[] = { + V4L2_PIX_FMT_BGR24, + V4L2_PIX_FMT_RGB24, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_UYVY, + V4L2_PIX_FMT_JPEG, + V4L2_PIX_FMT_MJPEG, + V4L2_PIX_FMT_YUV422P, + V4L2_PIX_FMT_YUV420 +}; +static const uint32_t prefered_gray8_formats[] = { + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_UYVY, + V4L2_PIX_FMT_JPEG, + V4L2_PIX_FMT_MJPEG, + V4L2_PIX_FMT_YUV422P, + V4L2_PIX_FMT_YUV420 +}; #endif int LocalCamera::camera_count = 0; @@ -334,13 +362,13 @@ LocalCamera::LocalCamera( if ( *(unsigned char*)&checkval == 0xDD ) { BigEndian = 0; Debug(2,"little-endian processor detected"); - } else if( *(unsigned char*)&checkval == 0xAA ) { + } else if ( *(unsigned char*)&checkval == 0xAA ) { BigEndian = 1; Debug(2,"Big-endian processor detected"); } else { Error("Unable to detect the processor's endianness. Assuming little-endian."); BigEndian = 0; - } + } #if ZM_HAS_V4L2 if ( v4l_version == 2 && palette == 0 ) { @@ -352,7 +380,8 @@ LocalCamera::LocalCamera( palette = V4L2_PIX_FMT_YUYV; } else { if ( capture ) { - Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); + Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", + palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); } } } @@ -398,10 +427,6 @@ LocalCamera::LocalCamera( /* RGB24 palette and 24bit target colourspace */ } else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) { conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - - /* BGR24 palette and 24bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24 ) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; @@ -411,13 +436,14 @@ LocalCamera::LocalCamera( subpixelorder = ZM_SUBPIX_ORDER_NONE; /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { - if ( capture ) + if ( capture ) { #if HAVE_LIBSWSCALE Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected", (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours); #else - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); + Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); #endif + } #if HAVE_LIBSWSCALE /* Try using swscale for the conversion */ conversion_type = 1; @@ -502,14 +528,14 @@ LocalCamera::LocalCamera( } else { Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); } - } - } - } + } // end if conversion_type == 2 + } // end if needs conversion + } // end if v4l2 #endif // ZM_HAS_V4L2 /* V4L1 format matching */ #if ZM_HAS_V4L1 - if ( v4l_version == 1) { + if ( v4l_version == 1 ) { /* Try to find a match for the selected palette and target colourspace */ /* RGB32 palette and 32bit target colourspace */ @@ -552,7 +578,7 @@ LocalCamera::LocalCamera( subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %u",colours); + Panic("Unexpected colours: %u", colours); } if ( capture ) { if ( !sws_isSupportedInput(capturePixFormat) ) { @@ -573,7 +599,6 @@ LocalCamera::LocalCamera( /* Don't have swscale, see what we can do */ conversion_type = 2; #endif - if ( conversion_type == 2 ) { Debug(2,"Using ZM for image conversion"); if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8 ) { @@ -600,16 +625,16 @@ LocalCamera::LocalCamera( } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_RGB32 ) { conversion_fptr = &zm_convert_yuyv_rgba; subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB24) { + } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB24 ) { conversion_fptr = &zm_convert_rgb555_rgb; subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB32) { + } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB32 ) { conversion_fptr = &zm_convert_rgb555_rgba; subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB24) { + } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB24 ) { conversion_fptr = &zm_convert_rgb565_rgb; subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB32) { + } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB32 ) { conversion_fptr = &zm_convert_rgb565_rgba; subpixelorder = ZM_SUBPIX_ORDER_RGBA; } else { @@ -632,21 +657,21 @@ LocalCamera::LocalCamera( tmpPicture = avcodec_alloc_frame(); #endif if ( !tmpPicture ) - Fatal( "Could not allocate temporary picture" ); + Fatal("Could not allocate temporary picture"); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int pSize = av_image_get_buffer_size( imagePixFormat, width, height,1 ); + unsigned int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); #else - int pSize = avpicture_get_size( imagePixFormat, width, height ); + unsigned int pSize = avpicture_get_size(imagePixFormat, width, height); #endif - if ( (unsigned int)pSize != imagesize ) { + if ( pSize != imagesize ) { Fatal("Image size mismatch. Required: %d Available: %u", pSize, imagesize); } - imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL); if ( !imgConversionContext ) { - Fatal( "Unable to initialise image scaling context" ); + Fatal("Unable to initialise image scaling context"); } } else { tmpPicture = NULL; @@ -676,56 +701,66 @@ LocalCamera::~LocalCamera() { void LocalCamera::Initialise() { #if HAVE_LIBSWSCALE if ( logDebugging() ) - av_log_set_level( AV_LOG_DEBUG ); + av_log_set_level(AV_LOG_DEBUG); else - av_log_set_level( AV_LOG_QUIET ); + av_log_set_level(AV_LOG_QUIET); #endif // HAVE_LIBSWSCALE - - Debug( 3, "Opening video device %s", device.c_str() ); + Debug(3, "Opening video device %s", device.c_str()); //if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 ) - if ( (vid_fd = open( device.c_str(), O_RDWR, 0 )) < 0 ) - Fatal( "Failed to open video device %s: %s", device.c_str(), strerror(errno) ); + if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 ) + Fatal("Failed to open video device %s: %s", device.c_str(), strerror(errno)); struct stat st; - if ( stat( device.c_str(), &st ) < 0 ) - Fatal( "Failed to stat video device %s: %s", device.c_str(), strerror(errno) ); + if ( stat(device.c_str(), &st) < 0 ) + Fatal("Failed to stat video device %s: %s", device.c_str(), strerror(errno)); if ( !S_ISCHR(st.st_mode) ) - Fatal( "File %s is not device file: %s", device.c_str(), strerror(errno) ); + Fatal("File %s is not device file: %s", device.c_str(), strerror(errno)); #if ZM_HAS_V4L2 - Debug( 2, "V4L2 support enabled, using V4L%d api", v4l_version ); + Debug(2, "V4L2 support enabled, using V4L%d api", v4l_version); if ( v4l_version == 2 ) { struct v4l2_capability vid_cap; - Debug( 3, "Checking video device capabilities" ); - if ( vidioctl( vid_fd, VIDIOC_QUERYCAP, &vid_cap ) < 0 ) - Fatal( "Failed to query video device: %s", strerror(errno) ); + Debug(3, "Checking video device capabilities"); + if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) + Fatal("Failed to query video device: %s", strerror(errno)); if ( !(vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) - Fatal( "Video device is not video capture device" ); + Fatal("Video device is not video capture device"); if ( !(vid_cap.capabilities & V4L2_CAP_STREAMING) ) - Fatal( "Video device does not support streaming i/o" ); + Fatal("Video device does not support streaming i/o"); - Debug( 3, "Setting up video format" ); + Debug(3, "Setting up video format"); - memset( &v4l2_data.fmt, 0, sizeof(v4l2_data.fmt) ); + memset(&v4l2_data.fmt, 0, sizeof(v4l2_data.fmt)); v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if ( vidioctl( vid_fd, VIDIOC_G_FMT, &v4l2_data.fmt ) < 0 ) - Fatal( "Failed to get video format: %s", strerror(errno) ); + Fatal("Failed to get video format: %s", strerror(errno)); - Debug( 4, " v4l2_data.fmt.type = %08x", v4l2_data.fmt.type ); - Debug( 4, " v4l2_data.fmt.fmt.pix.width = %d", v4l2_data.fmt.fmt.pix.width ); - Debug( 4, " v4l2_data.fmt.fmt.pix.height = %d", v4l2_data.fmt.fmt.pix.height ); - Debug( 4, " v4l2_data.fmt.fmt.pix.pixelformat = %08x", v4l2_data.fmt.fmt.pix.pixelformat ); - Debug( 4, " v4l2_data.fmt.fmt.pix.field = %08x", v4l2_data.fmt.fmt.pix.field ); - Debug( 4, " v4l2_data.fmt.fmt.pix.bytesperline = %08x", v4l2_data.fmt.fmt.pix.bytesperline ); - Debug( 4, " v4l2_data.fmt.fmt.pix.sizeimage = %08x", v4l2_data.fmt.fmt.pix.sizeimage ); - Debug( 4, " v4l2_data.fmt.fmt.pix.colorspace = %08x", v4l2_data.fmt.fmt.pix.colorspace ); - Debug( 4, " v4l2_data.fmt.fmt.pix.priv = %08x", v4l2_data.fmt.fmt.pix.priv ); + Debug(4, + " v4l2_data.fmt.type = %08x\n" + " v4l2_data.fmt.fmt.pix.width = %08x\n" + " v4l2_data.fmt.fmt.pix.height = %08x\n" + " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" + " v4l2_data.fmt.fmt.pix.field = %08x\n" + " v4l2_data.fmt.fmt.pix.bytesperline = %08x\n" + " v4l2_data.fmt.fmt.pix.sizeimage = %08x\n" + " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" + " v4l2_data.fmt.fmt.pix.priv = %08x\n" + , v4l2_data.fmt.type + , v4l2_data.fmt.fmt.pix.width + , v4l2_data.fmt.fmt.pix.height + , v4l2_data.fmt.fmt.pix.pixelformat + , v4l2_data.fmt.fmt.pix.field + , v4l2_data.fmt.fmt.pix.bytesperline + , v4l2_data.fmt.fmt.pix.sizeimage + , v4l2_data.fmt.fmt.pix.colorspace + , v4l2_data.fmt.fmt.pix.priv + ); v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; v4l2_data.fmt.fmt.pix.width = width; @@ -735,29 +770,40 @@ void LocalCamera::Initialise() { if ( (extras & 0xff) != 0 ) { v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { - Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); + if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { + Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff)); v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { - Fatal( "Failed to set video format: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { + Fatal("Failed to set video format: %s", strerror(errno)); } } } else { - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { - Fatal( "Failed to set video format: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { + Fatal("Failed to set video format: %s", strerror(errno)); } } /* Note VIDIOC_S_FMT may change width and height. */ - Debug( 4, " v4l2_data.fmt.type = %08x", v4l2_data.fmt.type ); - Debug( 4, " v4l2_data.fmt.fmt.pix.width = %08x", v4l2_data.fmt.fmt.pix.width ); - Debug( 4, " v4l2_data.fmt.fmt.pix.height = %08x", v4l2_data.fmt.fmt.pix.height ); - Debug( 4, " v4l2_data.fmt.fmt.pix.pixelformat = %08x", v4l2_data.fmt.fmt.pix.pixelformat ); - Debug( 4, " v4l2_data.fmt.fmt.pix.field = %08x", v4l2_data.fmt.fmt.pix.field ); - Debug( 4, " v4l2_data.fmt.fmt.pix.bytesperline = %08x", v4l2_data.fmt.fmt.pix.bytesperline ); - Debug( 4, " v4l2_data.fmt.fmt.pix.sizeimage = %08x", v4l2_data.fmt.fmt.pix.sizeimage ); - Debug( 4, " v4l2_data.fmt.fmt.pix.colorspace = %08x", v4l2_data.fmt.fmt.pix.colorspace ); - Debug( 4, " v4l2_data.fmt.fmt.pix.priv = %08x", v4l2_data.fmt.fmt.pix.priv ); + Debug(4, + " v4l2_data.fmt.type = %08x\n" + " v4l2_data.fmt.fmt.pix.width = %08x\n" + " v4l2_data.fmt.fmt.pix.height = %08x\n" + " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" + " v4l2_data.fmt.fmt.pix.field = %08x\n" + " v4l2_data.fmt.fmt.pix.bytesperline = %08x\n" + " v4l2_data.fmt.fmt.pix.sizeimage = %08x\n" + " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" + " v4l2_data.fmt.fmt.pix.priv = %08x\n" + , v4l2_data.fmt.type + , v4l2_data.fmt.fmt.pix.width + , v4l2_data.fmt.fmt.pix.height + , v4l2_data.fmt.fmt.pix.pixelformat + , v4l2_data.fmt.fmt.pix.field + , v4l2_data.fmt.fmt.pix.bytesperline + , v4l2_data.fmt.fmt.pix.sizeimage + , v4l2_data.fmt.fmt.pix.colorspace + , v4l2_data.fmt.fmt.pix.priv + ); if ( v4l2_data.fmt.fmt.pix.width != width ) { Warning("Failed to set requested width"); @@ -769,19 +815,19 @@ void LocalCamera::Initialise() { /* Buggy driver paranoia. */ unsigned int min; min = v4l2_data.fmt.fmt.pix.width * 2; - if (v4l2_data.fmt.fmt.pix.bytesperline < min) + if ( v4l2_data.fmt.fmt.pix.bytesperline < min ) v4l2_data.fmt.fmt.pix.bytesperline = min; min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height; - if (v4l2_data.fmt.fmt.pix.sizeimage < min) + if ( v4l2_data.fmt.fmt.pix.sizeimage < min ) v4l2_data.fmt.fmt.pix.sizeimage = min; - v4l2_jpegcompression jpeg_comp; if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { - if ( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { + v4l2_jpegcompression jpeg_comp; + if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { if ( errno == EINVAL ) { Debug(2, "JPEG compression options are not available"); } else { - Warning("Failed to get JPEG compression options: %s", strerror(errno) ); + Warning("Failed to get JPEG compression options: %s", strerror(errno)); } } else { /* Set flags and quality. MJPEG should not have the huffman tables defined */ @@ -793,24 +839,24 @@ void LocalCamera::Initialise() { jpeg_comp.quality = 85; /* Update the JPEG options */ - if ( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { - Warning("Failed to set JPEG compression options: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0 ) { + Warning("Failed to set JPEG compression options: %s", strerror(errno)); } else { - if ( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { - Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { + Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno)); } else { Debug(4, "JPEG quality: %d, markers: %#x", jpeg_comp.quality, jpeg_comp.jpeg_markers); } } } - } + } // end if JPEG/MJPEG - Debug( 3, "Setting up request buffers" ); + Debug(3, "Setting up request buffers"); - memset( &v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs) ); + memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs)); if ( channel_count > 1 ) { - Debug( 3, "Channel count is %d", channel_count ); + Debug(3, "Channel count is %d", channel_count); if ( v4l_multi_buffer ){ v4l2_data.reqbufs.count = 2*channel_count; } else { @@ -819,23 +865,24 @@ void LocalCamera::Initialise() { } else { v4l2_data.reqbufs.count = 8; } - Debug( 3, "Request buffers count is %d", v4l2_data.reqbufs.count ); + Debug(3, "Request buffers count is %d", v4l2_data.reqbufs.count); v4l2_data.reqbufs.type = v4l2_data.fmt.type; v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP; - if ( vidioctl( vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0 ) { if ( errno == EINVAL ) { - Fatal( "Unable to initialise memory mapping, unsupported in device" ); + Fatal("Unable to initialise memory mapping, unsupported in device"); } else { - Fatal( "Unable to initialise memory mapping: %s", strerror(errno) ); + Fatal("Unable to initialise memory mapping: %s", strerror(errno)); } } if ( v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1) ) - Fatal( "Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count ); + Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count); - Debug( 3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count ); + Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", + channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count); v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; #if HAVE_LIBSWSCALE @@ -844,7 +891,7 @@ void LocalCamera::Initialise() { for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { struct v4l2_buffer vid_buf; - memset( &vid_buf, 0, sizeof(vid_buf) ); + memset(&vid_buf, 0, sizeof(vid_buf)); //vid_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vid_buf.type = v4l2_data.fmt.type; @@ -852,14 +899,15 @@ void LocalCamera::Initialise() { vid_buf.memory = v4l2_data.reqbufs.memory; vid_buf.index = i; - if ( vidioctl( vid_fd, VIDIOC_QUERYBUF, &vid_buf ) < 0 ) - Fatal( "Unable to query video buffer: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0 ) + Fatal("Unable to query video buffer: %s", strerror(errno)); v4l2_data.buffers[i].length = vid_buf.length; - v4l2_data.buffers[i].start = mmap( NULL, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset ); + v4l2_data.buffers[i].start = mmap(NULL, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset); if ( v4l2_data.buffers[i].start == MAP_FAILED ) - Fatal( "Can't map video buffer %u (%u bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); + Fatal("Can't map video buffer %u (%u bytes) to memory: %s(%d)", + i, vid_buf.length, strerror(errno), errno); #if HAVE_LIBSWSCALE #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) @@ -868,44 +916,49 @@ void LocalCamera::Initialise() { capturePictures[i] = avcodec_alloc_frame(); #endif if ( !capturePictures[i] ) - Fatal( "Could not allocate picture" ); + Fatal("Could not allocate picture"); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(capturePictures[i]->data, + av_image_fill_arrays( + capturePictures[i]->data, capturePictures[i]->linesize, - (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, + (uint8_t*)v4l2_data.buffers[i].start, + capturePixFormat, v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height, 1); + v4l2_data.fmt.fmt.pix.height, + 1); #else - avpicture_fill( (AVPicture *)capturePictures[i], + avpicture_fill( + (AVPicture *)capturePictures[i], (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height ); + v4l2_data.fmt.fmt.pix.height + ); #endif #endif // HAVE_LIBSWSCALE - } + } // end foreach request buf - Debug( 3, "Configuring video source" ); + Debug(3, "Configuring video source"); - if ( vidioctl( vid_fd, VIDIOC_S_INPUT, &channel ) < 0 ) { - Fatal( "Failed to set camera source %d: %s", channel, strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0 ) { + Fatal("Failed to set camera source %d: %s", channel, strerror(errno)); } struct v4l2_input input; v4l2_std_id stdId; - memset( &input, 0, sizeof(input) ); + memset(&input, 0, sizeof(input)); - if ( vidioctl( vid_fd, VIDIOC_ENUMINPUT, &input ) < 0 ) { - Fatal( "Failed to enumerate input %d: %s", channel, strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0 ) { + Fatal("Failed to enumerate input %d: %s", channel, strerror(errno)); } if ( (input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN) ) { - Fatal( "Device does not support video standard %d", standard ); + Fatal("Device does not support video standard %d", standard); } stdId = standard; if ( (input.std != V4L2_STD_UNKNOWN) && vidioctl( vid_fd, VIDIOC_S_STD, &stdId ) < 0 ) { - Fatal( "Failed to set video standard %d: %s", standard, strerror(errno) ); + Fatal("Failed to set video standard %d: %s", standard, strerror(errno)); } Contrast(contrast); @@ -916,46 +969,41 @@ void LocalCamera::Initialise() { #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { - Debug( 3, "Configuring picture attributes" ); + Debug(3, "Configuring picture attributes"); struct video_picture vid_pic; - memset( &vid_pic, 0, sizeof(vid_pic) ); - if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) - Fatal( "Failed to get picture attributes: %s", strerror(errno) ); + memset(&vid_pic, 0, sizeof(vid_pic)); + if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) + Fatal("Failed to get picture attributes: %s", strerror(errno)); - Debug( 4, "Old P:%d", vid_pic.palette ); - Debug( 4, "Old D:%d", vid_pic.depth ); - Debug( 4, "Old B:%d", vid_pic.brightness ); - Debug( 4, "Old h:%d", vid_pic.hue ); - Debug( 4, "Old Cl:%d", vid_pic.colour ); - Debug( 4, "Old Cn:%d", vid_pic.contrast ); + Debug(4, + "Old Palette:%d, depth:%d, brightness:%d, hue:%d, colour:%d, contrast:%d", + vid_pic.palette, + vid_pic.depth, + vid_pic.brightness, + vid_pic.hue, + vid_pic.colour, + vid_pic.contrast + ); switch (vid_pic.palette = palette) { case VIDEO_PALETTE_RGB32 : - { vid_pic.depth = 32; break; - } case VIDEO_PALETTE_RGB24 : - { vid_pic.depth = 24; break; - } case VIDEO_PALETTE_GREY : - { vid_pic.depth = 8; break; - } case VIDEO_PALETTE_RGB565 : case VIDEO_PALETTE_YUYV : case VIDEO_PALETTE_YUV422 : case VIDEO_PALETTE_YUV420P : case VIDEO_PALETTE_YUV422P : default: - { vid_pic.depth = 16; break; - } } if ( brightness >= 0 ) vid_pic.brightness = brightness; @@ -963,24 +1011,21 @@ void LocalCamera::Initialise() { if ( colour >= 0 ) vid_pic.colour = colour; if ( contrast >= 0 ) vid_pic.contrast = contrast; - if ( ioctl( vid_fd, VIDIOCSPICT, &vid_pic ) < 0 ) { - Error( "Failed to set picture attributes: %s", strerror(errno) ); + if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { + Error("Failed to set picture attributes: %s", strerror(errno)); if ( config.strict_video_config ) exit(-1); } - Debug( 3, "Configuring window attributes" ); + Debug(3, "Configuring window attributes"); struct video_window vid_win; - memset( &vid_win, 0, sizeof(vid_win) ); - if ( ioctl( vid_fd, VIDIOCGWIN, &vid_win) < 0 ) { - Error( "Failed to get window attributes: %s", strerror(errno) ); - exit(-1); + memset(&vid_win, 0, sizeof(vid_win)); + if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) { + Fatal("Failed to get window attributes: %s", strerror(errno)); } - Debug( 4, "Old X:%d", vid_win.x ); - Debug( 4, "Old Y:%d", vid_win.y ); - Debug( 4, "Old W:%d", vid_win.width ); - Debug( 4, "Old H:%d", vid_win.height ); + Debug(4, "Old X:%d Y:%d W:%d H:%d", + vid_win.x, vid_win.y, vid_win.width, vid_win.height); vid_win.x = 0; vid_win.y = 0; @@ -988,30 +1033,29 @@ void LocalCamera::Initialise() { vid_win.height = height; vid_win.flags &= ~VIDEO_WINDOW_INTERLACE; - if ( ioctl( vid_fd, VIDIOCSWIN, &vid_win ) < 0 ) { - Error( "Failed to set window attributes: %s", strerror(errno) ); + if ( ioctl(vid_fd, VIDIOCSWIN, &vid_win) < 0 ) { + Error("Failed to set window attributes: %s", strerror(errno)); if ( config.strict_video_config ) exit(-1); } - Info( "vid_win.width = %08x", vid_win.width ); - Info( "vid_win.height = %08x", vid_win.height ); - Info( "vid_win.flags = %08x", vid_win.flags ); + Info("vid_win.width = %08x, vid_win.height = %08x, vid_win.flags = %08x", + vid_win.width, vid_win.height, vid_win.flags); - Debug( 3, "Setting up request buffers" ); - if ( ioctl( vid_fd, VIDIOCGMBUF, &v4l1_data.frames ) < 0 ) - Fatal( "Failed to setup memory: %s", strerror(errno) ); + Debug(3, "Setting up request buffers"); + if ( ioctl(vid_fd, VIDIOCGMBUF, &v4l1_data.frames) < 0 ) + Fatal("Failed to setup memory: %s", strerror(errno)); if ( channel_count > 1 && !v4l_multi_buffer ) v4l1_data.frames.frames = 1; v4l1_data.buffers = new video_mmap[v4l1_data.frames.frames]; - Debug( 4, "vmb.frames = %d", v4l1_data.frames.frames ); - Debug( 4, "vmb.size = %d", v4l1_data.frames.size ); + Debug(4, "vmb.frames = %d, vmb.size = %d", + v4l1_data.frames.frames, v4l1_data.frames.size); - Debug( 3, "Setting up %d frame buffers", v4l1_data.frames.frames ); + Debug(3, "Setting up %d frame buffers", v4l1_data.frames.frames); - v4l1_data.bufptr = (unsigned char *)mmap( 0, v4l1_data.frames.size, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, 0 ); + v4l1_data.bufptr = (unsigned char *)mmap(0, v4l1_data.frames.size, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, 0); if ( v4l1_data.bufptr == MAP_FAILED ) - Fatal( "Could not mmap video: %s", strerror(errno) ); + Fatal("Could not mmap video: %s", strerror(errno)); #if HAVE_LIBSWSCALE capturePictures = new AVFrame *[v4l1_data.frames.frames]; @@ -1027,66 +1071,66 @@ void LocalCamera::Initialise() { capturePictures[i] = avcodec_alloc_frame(); #endif if ( !capturePictures[i] ) - Fatal( "Could not allocate picture" ); + Fatal("Could not allocate picture"); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(capturePictures[i]->data, + av_image_fill_arrays( + capturePictures[i]->data, capturePictures[i]->linesize, (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], capturePixFormat, width, height, 1); #else - avpicture_fill( (AVPicture *)capturePictures[i], + avpicture_fill( + (AVPicture *)capturePictures[i], (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], capturePixFormat, width, height ); #endif } #endif // HAVE_LIBSWSCALE - Debug( 3, "Configuring video source" ); + Debug(3, "Configuring video source"); struct video_channel vid_src; - memset( &vid_src, 0, sizeof(vid_src) ); + memset(&vid_src, 0, sizeof(vid_src)); vid_src.channel = channel; - if ( ioctl( vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) - Fatal( "Failed to get camera source: %s", strerror(errno) ); + if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) + Fatal("Failed to get camera source: %s", strerror(errno)); - Debug( 4, "Old C:%d", vid_src.channel ); - Debug( 4, "Old F:%d", vid_src.norm ); - Debug( 4, "Old Fl:%x", vid_src.flags ); - Debug( 4, "Old T:%d", vid_src.type ); + Debug(4, "Old C:%d, F:%d, Fl:%x, T:%d", + vid_src.channel, vid_src.norm, vid_src.flags, vid_src.type); vid_src.norm = standard; vid_src.flags = 0; vid_src.type = VIDEO_TYPE_CAMERA; - if ( ioctl( vid_fd, VIDIOCSCHAN, &vid_src ) < 0 ) { - Error( "Failed to set camera source %d: %s", channel, strerror(errno) ); + if ( ioctl(vid_fd, VIDIOCSCHAN, &vid_src) < 0 ) { + Error("Failed to set camera source %d: %s", channel, strerror(errno)); if ( config.strict_video_config ) exit(-1); } - if ( ioctl( vid_fd, VIDIOCGWIN, &vid_win) < 0 ) - Fatal( "Failed to get window data: %s", strerror(errno) ); + if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) + Fatal("Failed to get window data: %s", strerror(errno)); - Info( "vid_win.width = %08x", vid_win.width ); - Info( "vid_win.height = %08x", vid_win.height ); - Info( "vid_win.flags = %08x", vid_win.flags ); + Info("vid_win.width = %08x, vid_win.height = %08x, vid_win.flags = %08x", + vid_win.width, vid_win.height, vid_win.flags); - Debug( 4, "New X:%d", vid_win.x ); - Debug( 4, "New Y:%d", vid_win.y ); - Debug( 4, "New W:%d", vid_win.width ); - Debug( 4, "New H:%d", vid_win.height ); + Debug(4, "New X:%d Y:%d W:%d H:%d", + vid_win.x, vid_win.y, vid_win.width, vid_win.height); - if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) - Fatal( "Failed to get window data: %s", strerror(errno) ); + if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) + Fatal("Failed to get window data: %s", strerror(errno)); - Debug( 4, "New P:%d", vid_pic.palette ); - Debug( 4, "New D:%d", vid_pic.depth ); - Debug( 4, "New B:%d", vid_pic.brightness ); - Debug( 4, "New h:%d", vid_pic.hue ); - Debug( 4, "New Cl:%d", vid_pic.colour ); - Debug( 4, "New Cn:%d", vid_pic.contrast ); - } + Debug(4, + "New Palette:%d, depth:%d, brightness:%d, hue:%d, colour:%d, contrast:%d", + vid_pic.palette, + vid_pic.depth, + vid_pic.brightness, + vid_pic.hue, + vid_pic.colour, + vid_pic.contrast + ); + } // end if v4l #endif // ZM_HAS_V4L1 -} +} // end LocalCamera::Initialize void LocalCamera::Terminate() { #if ZM_HAS_V4L2 @@ -1111,28 +1155,28 @@ void LocalCamera::Terminate() { if ( munmap(v4l2_data.buffers[i].start, v4l2_data.buffers[i].length) < 0 ) Error("Failed to munmap buffer %d: %s", i, strerror(errno)); } - } else + } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { + if ( v4l_version == 1 ) { #if HAVE_LIBSWSCALE - for( int i=0; i < v4l1_data.frames.frames; i++ ) { - /* Free capture pictures */ + for ( int i=0; i < v4l1_data.frames.frames; i++ ) { + /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free(&capturePictures[i]); + av_frame_free(&capturePictures[i]); #else - av_freep(&capturePictures[i]); + av_freep(&capturePictures[i]); #endif - } + } #endif - Debug(3, "Unmapping video buffers"); - if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) - Error("Failed to munmap buffers: %s", strerror(errno)); + Debug(3, "Unmapping video buffers"); + if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) + Error("Failed to munmap buffers: %s", strerror(errno)); - delete[] v4l1_data.buffers; - } // end if using v4l1 + delete[] v4l1_data.buffers; + } // end if using v4l1 #endif // ZM_HAS_V4L1 close(vid_fd); @@ -1150,8 +1194,9 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { int enum_fd; /* Open the device */ - if ( (enum_fd = open( device.c_str(), O_RDWR, 0 )) < 0 ) { - Error( "Automatic format selection failed to open video device %s: %s", device.c_str(), strerror(errno) ); + if ( (enum_fd = open(device.c_str(), O_RDWR, 0)) < 0 ) { + Error("Automatic format selection failed to open video device %s: %s", + device.c_str(), strerror(errno)); return selected_palette; } @@ -1160,8 +1205,8 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { fmtinfo.index = nIndex; fmtinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // FIXME This will crash if there are more than 64 formats. - while(vidioctl( enum_fd, VIDIOC_ENUM_FMT, &fmtinfo ) >= 0) { - if (nIndex >= 64 ) { + while ( vidioctl(enum_fd, VIDIOC_ENUM_FMT, &fmtinfo) >= 0 ) { + if ( nIndex >= 64 ) { Error("More than 64 formats detected, can't handle that."); break; } @@ -1170,7 +1215,12 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { fmt_fcc[nIndex] = fmtinfo.pixelformat; Debug(3, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d", - fmt_desc[nIndex], (fmt_fcc[nIndex]>>24)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex])&0xff ,nIndex); + fmt_desc[nIndex], + (fmt_fcc[nIndex]>>24)&0xff, + (fmt_fcc[nIndex]>>16)&0xff, + (fmt_fcc[nIndex]>>8)&0xff, + (fmt_fcc[nIndex])&0xff, + nIndex); /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); @@ -1234,182 +1284,198 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers int devIndex = 0; do { if ( device ) - strncpy( queryDevice, device, sizeof(queryDevice)-1 ); + strncpy(queryDevice, device, sizeof(queryDevice)-1); else - sprintf( queryDevice, "/dev/video%d", devIndex ); + sprintf(queryDevice, "/dev/video%d", devIndex); + if ( (vid_fd = open(queryDevice, O_RDWR)) <= 0 ) { if ( device ) { - Error( "Failed to open video device %s: %s", queryDevice, strerror(errno) ); + Error("Failed to open video device %s: %s", queryDevice, strerror(errno)); if ( verbose ) - sprintf( output+strlen(output), "Error, failed to open video device %s: %s\n", queryDevice, strerror(errno) ); + sprintf(output+strlen(output), "Error, failed to open video device %s: %s\n", + queryDevice, strerror(errno)); else - sprintf( output+strlen(output), "error%d\n", errno ); - return( false ); + sprintf(output+strlen(output), "error%d\n", errno); + return false; } else { - return( true ); + return true; } } if ( verbose ) - sprintf( output+strlen(output), "Video Device: %s\n", queryDevice ); + sprintf(output+strlen(output), "Video Device: %s\n", queryDevice); else - sprintf( output+strlen(output), "d:%s|", queryDevice ); + sprintf(output+strlen(output), "d:%s|", queryDevice); #if ZM_HAS_V4L2 if ( version == 2 ) { struct v4l2_capability vid_cap; - if ( vidioctl( vid_fd, VIDIOC_QUERYCAP, &vid_cap ) < 0 ) { - Error( "Failed to query video device: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) { + Error("Failed to query video device: %s", strerror(errno)); if ( verbose ) - sprintf( output, "Error, failed to query video capabilities %s: %s\n", queryDevice, strerror(errno) ); + sprintf(output, "Error, failed to query video capabilities %s: %s\n", + queryDevice, strerror(errno)); else - sprintf( output, "error%d\n", errno ); - return( false ); + sprintf(output, "error%d\n", errno); + return false; } if ( verbose ) { - sprintf( output+strlen(output), "General Capabilities\n" ); - sprintf( output+strlen(output), " Driver: %s\n", vid_cap.driver ); - sprintf( output+strlen(output), " Card: %s\n", vid_cap.card ); - sprintf( output+strlen(output), " Bus: %s\n", vid_cap.bus_info ); - sprintf( output+strlen(output), " Version: %u.%u.%u\n", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff ); - sprintf( output+strlen(output), " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.capabilities, - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), + sprintf(output+strlen(output), "General Capabilities\n"); + sprintf(output+strlen(output), " Driver: %s\n", vid_cap.driver); + sprintf(output+strlen(output), " Card: %s\n", vid_cap.card); + sprintf(output+strlen(output), " Bus: %s\n", vid_cap.bus_info); + sprintf(output+strlen(output), " Version: %u.%u.%u\n", + (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff); + sprintf(output+strlen(output), " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + vid_cap.capabilities, + capString(vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)"), + capString(vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output"), + capString(vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay"), + capString(vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture"), + capString(vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output"), + capString(vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture"), + capString(vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output"), #ifdef V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), + capString(vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay"), #else // V4L2_CAP_VIDEO_OUTPUT_OVERLAY "", #endif // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), - capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), - capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), - capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), - capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) + capString(vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner"), + capString(vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out"), + capString(vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio"), + capString(vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)"), + capString(vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o"), + capString(vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)") ); } else { - sprintf( output+strlen(output), "D:%s|", vid_cap.driver ); - sprintf( output+strlen(output), "C:%s|", vid_cap.card ); - sprintf( output+strlen(output), "B:%s|", vid_cap.bus_info ); - sprintf( output+strlen(output), "V:%u.%u.%u|", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff ); - sprintf( output+strlen(output), "T:0x%x|", vid_cap.capabilities ); + sprintf(output+strlen(output), "D:%s|", vid_cap.driver); + sprintf(output+strlen(output), "C:%s|", vid_cap.card); + sprintf(output+strlen(output), "B:%s|", vid_cap.bus_info); + sprintf(output+strlen(output), "V:%u.%u.%u|", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff); + sprintf(output+strlen(output), "T:0x%x|", vid_cap.capabilities); } if ( verbose ) - sprintf( output+strlen(output), " Standards:\n" ); + sprintf(output+strlen(output), " Standards:\n"); else - sprintf( output+strlen(output), "S:" ); + sprintf(output+strlen(output), "S:"); struct v4l2_standard standard; int standardIndex = 0; do { - memset( &standard, 0, sizeof(standard) ); + memset(&standard, 0, sizeof(standard)); standard.index = standardIndex; - if ( vidioctl( vid_fd, VIDIOC_ENUMSTD, &standard ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_ENUMSTD, &standard) < 0 ) { if ( errno == EINVAL || errno == ENODATA || errno == ENOTTY ) { - Debug( 6, "Done enumerating standard %d: %d %s", standard.index, errno, strerror(errno) ); + Debug(6, "Done enumerating standard %d: %d %s", standard.index, errno, strerror(errno)); standardIndex = -1; break; } else { - Error( "Failed to enumerate standard %d: %d %s", standard.index, errno, strerror(errno) ); + Error("Failed to enumerate standard %d: %d %s", standard.index, errno, strerror(errno)); if ( verbose ) - sprintf( output, "Error, failed to enumerate standard %d: %d %s\n", standard.index, errno, strerror(errno) ); + sprintf(output, "Error, failed to enumerate standard %d: %d %s\n", standard.index, errno, strerror(errno)); else - sprintf( output, "error%d\n", errno ); - return( false ); + sprintf(output, "error%d\n", errno); + return false; } } if ( verbose ) - sprintf( output+strlen(output), " %s\n", standard.name ); + sprintf(output+strlen(output), " %s\n", standard.name); else - sprintf( output+strlen(output), "%s/", standard.name ); + sprintf(output+strlen(output), "%s/", standard.name); } while ( standardIndex++ >= 0 ); if ( !verbose && output[strlen(output)-1] == '/') output[strlen(output)-1] = '|'; if ( verbose ) - sprintf( output+strlen(output), " Formats:\n" ); + sprintf(output+strlen(output), " Formats:\n"); else - sprintf( output+strlen(output), "F:" ); + sprintf(output+strlen(output), "F:"); struct v4l2_fmtdesc format; int formatIndex = 0; do { - memset( &format, 0, sizeof(format) ); + memset(&format, 0, sizeof(format)); format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.index = formatIndex; - if ( vidioctl( vid_fd, VIDIOC_ENUM_FMT, &format ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_ENUM_FMT, &format) < 0 ) { if ( errno == EINVAL ) { formatIndex = -1; break; } else { - Error( "Failed to enumerate format %d: %s", format.index, strerror(errno) ); + Error("Failed to enumerate format %d: %s", format.index, strerror(errno)); if ( verbose ) - sprintf( output, "Error, failed to enumerate format %d: %s\n", format.index, strerror(errno) ); + sprintf(output, "Error, failed to enumerate format %d: %s\n", format.index, strerror(errno)); else - sprintf( output, "error%d\n", errno ); - return( false ); + sprintf(output, "error%d\n", errno); + return false; } } if ( verbose ) - sprintf( output+strlen(output), " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", - format.description, (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, format.pixelformat&0xff); + sprintf( + output+strlen(output), + " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", + format.description, + (format.pixelformat>>24)&0xff, + (format.pixelformat>>16)&0xff, + (format.pixelformat>>8)&0xff, + format.pixelformat&0xff); else - sprintf( output+strlen(output), "0x%02hhx%02hhx%02hhx%02hhx/", - (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat)&0xff); + sprintf( + output+strlen(output), + "0x%02hhx%02hhx%02hhx%02hhx/", + (format.pixelformat>>24)&0xff, + (format.pixelformat>>16)&0xff, + (format.pixelformat>>8)&0xff, + (format.pixelformat)&0xff); } while ( formatIndex++ >= 0 ); - if ( verbose ) - sprintf( output+strlen(output), "Crop Capabilities\n" ); - else + if ( !verbose ) output[strlen(output)-1] = '|'; + else + sprintf(output+strlen(output), "Crop Capabilities\n"); struct v4l2_cropcap cropcap; - memset( &cropcap, 0, sizeof(cropcap) ); + memset(&cropcap, 0, sizeof(cropcap)); cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) { if ( errno != EINVAL ) { /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop capabilities: %s", strerror(errno) ); + Error("Failed to query crop capabilities: %s", strerror(errno)); } if ( verbose ) { - sprintf( output+strlen(output), " Cropping is not supported\n"); + sprintf(output+strlen(output), " Cropping is not supported\n"); } else { /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|", 0, 0 ); + sprintf(output+strlen(output), "B:%dx%d|",0,0); } } else { struct v4l2_crop crop; - memset( &crop, 0, sizeof(crop) ); + memset(&crop, 0, sizeof(crop)); crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_G_CROP, &crop) < 0 ) { if ( errno != EINVAL ) { /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop: %s", strerror(errno) ); + Error("Failed to query crop: %s", strerror(errno)); } if ( verbose ) { - sprintf( output+strlen(output), " Cropping is not supported\n"); + sprintf(output+strlen(output), " Cropping is not supported\n"); } else { /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); + sprintf(output+strlen(output), "B:%dx%d|",0,0); } } else { /* Cropping supported */ if ( verbose ) { - sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); - sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); - sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); + sprintf(output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height); + sprintf(output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height); + sprintf(output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height); } else { - sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); + sprintf(output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height); } } } /* Crop code */ @@ -1417,54 +1483,52 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers struct v4l2_input input; int inputIndex = 0; do { - memset( &input, 0, sizeof(input) ); + memset(&input, 0, sizeof(input)); input.index = inputIndex; - if ( vidioctl( vid_fd, VIDIOC_ENUMINPUT, &input ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0 ) { if ( errno == EINVAL ) { break; - } else { - Error( "Failed to enumerate input %d: %s", input.index, strerror(errno) ); - if ( verbose ) - sprintf( output, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno) ); - else - sprintf( output, "error%d\n", errno ); - return( false ); - } + } + Error("Failed to enumerate input %d: %s", input.index, strerror(errno)); + if ( verbose ) + sprintf(output, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno)); + else + sprintf(output, "error%d\n", errno); + return false; } } while ( inputIndex++ >= 0 ); if ( verbose ) - sprintf( output+strlen(output), "Inputs: %d\n", inputIndex ); + sprintf(output+strlen(output), "Inputs: %d\n", inputIndex); else - sprintf( output+strlen(output), "I:%d|", inputIndex ); + sprintf(output+strlen(output), "I:%d|", inputIndex); inputIndex = 0; do { - memset( &input, 0, sizeof(input) ); + memset(&input, 0, sizeof(input)); input.index = inputIndex; - if ( vidioctl( vid_fd, VIDIOC_ENUMINPUT, &input ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0 ) { if ( errno == EINVAL ) { inputIndex = -1; break; - } else { - Error( "Failed to enumerate input %d: %s", input.index, strerror(errno) ); - if ( verbose ) - sprintf( output, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno) ); - else - sprintf( output, "error%d\n", errno ); - return( false ); } + Error("Failed to enumerate input %d: %s", input.index, strerror(errno)); + if ( verbose ) + sprintf(output, "Error, failed to enumerate input %d: %s\n", input.index, strerror(errno)); + else + sprintf(output, "error%d\n", errno); + return false; } - if ( vidioctl( vid_fd, VIDIOC_S_INPUT, &input.index ) < 0 ) { - Error( "Failed to set video input %d: %s", input.index, strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &input.index) < 0 ) { + Error("Failed to set video input %d: %s", input.index, strerror(errno)); if ( verbose ) - sprintf( output, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno) ); + sprintf(output, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno)); else - sprintf( output, "error%d\n", errno ); - return( false ); + sprintf(output, "error%d\n", errno); + return false; } if ( verbose ) { @@ -1551,7 +1615,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output, "Error, failed to get window attributes: %s\n", strerror(errno) ); else sprintf( output, "error%d\n", errno ); - return( false ); + return false; } if ( verbose ) { sprintf( output+strlen(output), "Window Attributes\n" ); @@ -1574,7 +1638,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output, "Error, failed to get picture attributes: %s\n", strerror(errno) ); else sprintf( output, "error%d\n", errno ); - return( false ); + return false; } if ( verbose ) { sprintf( output+strlen(output), "Picture Attributes\n" ); @@ -1624,7 +1688,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output, "Error, failed to get channel %d attributes: %s\n", chan, strerror(errno) ); else sprintf( output, "error%d\n", errno ); - return( false ); + return false; } if ( verbose ) { sprintf( output+strlen(output), "Channel %d Attributes\n", chan ); @@ -1659,9 +1723,8 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers close( vid_fd ); if ( device ) break; - } - while ( ++devIndex < 32 ); - return( true ); + } while ( ++devIndex < 32 ); + return true; } int LocalCamera::Brightness( int p_brightness ) { @@ -1669,51 +1732,53 @@ int LocalCamera::Brightness( int p_brightness ) { if ( v4l_version == 2 ) { struct v4l2_control vid_control; - memset( &vid_control, 0, sizeof(vid_control) ); + memset(&vid_control, 0, sizeof(vid_control)); vid_control.id = V4L2_CID_BRIGHTNESS; - if ( vidioctl( vid_fd, VIDIOC_G_CTRL, &vid_control ) < 0 ) { - if ( errno != EINVAL ) - Error( "Unable to query brightness: %s", strerror(errno) ) - else - Warning( "Brightness control is not supported" ) + if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { + if ( errno != EINVAL ) { + Error("Unable to query brightness: %s", strerror(errno)); + } else { + Warning("Brightness control is not supported"); + } //Info( "Brightness 1 %d", vid_control.value ); } else if ( p_brightness >= 0 ) { vid_control.value = p_brightness; //Info( "Brightness 2 %d", vid_control.value ); /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl ( vid_fd, VIDIOC_S_CTRL, &vid_control ) ) { - if ( errno != ERANGE ) - Error( "Unable to set brightness: %s", strerror(errno) ) - else - Warning( "Given brightness value (%d) may be out-of-range", p_brightness ) + if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { + if ( errno != ERANGE ) { + Error("Unable to set brightness: %s", strerror(errno)); + } else { + Warning("Given brightness value (%d) may be out-of-range", p_brightness); + } } //Info( "Brightness 3 %d", vid_control.value ); } - return( vid_control.value ); + return vid_control.value; } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { struct video_picture vid_pic; - memset( &vid_pic, 0, sizeof(video_picture) ); - if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error( "Failed to get picture attributes: %s", strerror(errno) ); - return( -1 ); + memset(&vid_pic, 0, sizeof(video_picture)); + if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { + Error("Failed to get picture attributes: %s", strerror(errno)); + return -1; } if ( p_brightness >= 0 ) { vid_pic.brightness = p_brightness; - if ( ioctl( vid_fd, VIDIOCSPICT, &vid_pic ) < 0 ) { - Error( "Failed to set picture attributes: %s", strerror(errno) ); - return( -1 ); + if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { + Error("Failed to set picture attributes: %s", strerror(errno)); + return -1; } } - return( vid_pic.brightness ); + return vid_pic.brightness; } #endif // ZM_HAS_V4L1 - return( -1 ); + return -1; } int LocalCamera::Hue( int p_hue ) { @@ -1724,45 +1789,46 @@ int LocalCamera::Hue( int p_hue ) { memset( &vid_control, 0, sizeof(vid_control) ); vid_control.id = V4L2_CID_HUE; - if ( vidioctl( vid_fd, VIDIOC_G_CTRL, &vid_control ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { if ( errno != EINVAL ) - Error( "Unable to query hue: %s", strerror(errno) ) + Error("Unable to query hue: %s", strerror(errno)) else - Warning( "Hue control is not supported" ) + Warning("Hue control is not supported") } else if ( p_hue >= 0 ) { vid_control.value = p_hue; /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl ( vid_fd, VIDIOC_S_CTRL, &vid_control ) < 0 ) { - if ( errno != ERANGE ) - Error( "Unable to set hue: %s", strerror(errno) ) - else - Warning( "Given hue value (%d) may be out-of-range", p_hue ) + if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) { + if ( errno != ERANGE ) { + Error("Unable to set hue: %s", strerror(errno)); + } else { + Warning("Given hue value (%d) may be out-of-range", p_hue); + } } } - return( vid_control.value ); + return vid_control.value; } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { struct video_picture vid_pic; - memset( &vid_pic, 0, sizeof(video_picture) ); - if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error( "Failed to get picture attributes: %s", strerror(errno) ); - return( -1 ); + memset(&vid_pic, 0, sizeof(video_picture)); + if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { + Error("Failed to get picture attributes: %s", strerror(errno)); + return -1; } if ( p_hue >= 0 ) { vid_pic.hue = p_hue; - if ( ioctl( vid_fd, VIDIOCSPICT, &vid_pic ) < 0 ) { - Error( "Failed to set picture attributes: %s", strerror(errno) ); - return( -1 ); + if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { + Error("Failed to set picture attributes: %s", strerror(errno)); + return -1; } } - return( vid_pic.hue ); + return vid_pic.hue; } #endif // ZM_HAS_V4L1 - return( -1 ); + return -1; } int LocalCamera::Colour( int p_colour ) { @@ -1770,48 +1836,50 @@ int LocalCamera::Colour( int p_colour ) { if ( v4l_version == 2 ) { struct v4l2_control vid_control; - memset( &vid_control, 0, sizeof(vid_control) ); + memset(&vid_control, 0, sizeof(vid_control)); vid_control.id = V4L2_CID_SATURATION; - if ( vidioctl( vid_fd, VIDIOC_G_CTRL, &vid_control ) < 0 ) { - if ( errno != EINVAL ) - Error( "Unable to query saturation: %s", strerror(errno) ) - else - Warning( "Saturation control is not supported" ) + if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { + if ( errno != EINVAL ) { + Error("Unable to query saturation: %s", strerror(errno)); + } else { + Warning("Saturation control is not supported"); + } } else if ( p_colour >= 0 ) { vid_control.value = p_colour; /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl ( vid_fd, VIDIOC_S_CTRL, &vid_control ) < 0 ) { - if ( errno != ERANGE ) - Error( "Unable to set saturation: %s", strerror(errno) ) - else - Warning( "Given saturation value (%d) may be out-of-range", p_colour ) + if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) { + if ( errno != ERANGE ) { + Error("Unable to set saturation: %s", strerror(errno)); + } else { + Warning("Given saturation value (%d) may be out-of-range", p_colour); + } } } - return( vid_control.value ); + return vid_control.value; } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { struct video_picture vid_pic; - memset( &vid_pic, 0, sizeof(video_picture) ); - if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error( "Failed to get picture attributes: %s", strerror(errno) ); - return( -1 ); + memset(&vid_pic, 0, sizeof(video_picture)); + if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0) { + Error("Failed to get picture attributes: %s", strerror(errno)); + return -1; } if ( p_colour >= 0 ) { vid_pic.colour = p_colour; - if ( ioctl( vid_fd, VIDIOCSPICT, &vid_pic ) < 0 ) { - Error( "Failed to set picture attributes: %s", strerror(errno) ); - return( -1 ); + if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { + Error("Failed to set picture attributes: %s", strerror(errno)); + return -1; } } - return( vid_pic.colour ); + return vid_pic.colour; } #endif // ZM_HAS_V4L1 - return( -1 ); + return -1; } int LocalCamera::Contrast( int p_contrast ) { @@ -1819,99 +1887,102 @@ int LocalCamera::Contrast( int p_contrast ) { if ( v4l_version == 2 ) { struct v4l2_control vid_control; - memset( &vid_control, 0, sizeof(vid_control) ); + memset(&vid_control, 0, sizeof(vid_control)); vid_control.id = V4L2_CID_CONTRAST; - if ( vidioctl( vid_fd, VIDIOC_G_CTRL, &vid_control ) < 0 ) { - if ( errno != EINVAL ) - Error( "Unable to query contrast: %s", strerror(errno) ) - else - Warning( "Contrast control is not supported" ) + if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { + if ( errno != EINVAL ) { + Error("Unable to query contrast: %s", strerror(errno)); + } else { + Warning("Contrast control is not supported"); + } } else if ( p_contrast >= 0 ) { vid_control.value = p_contrast; /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl ( vid_fd, VIDIOC_S_CTRL, &vid_control ) ) { - if ( errno != ERANGE ) - Error( "Unable to set contrast: %s", strerror(errno) ) - else - Warning( "Given contrast value (%d) may be out-of-range", p_contrast ) + if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { + if ( errno != ERANGE ) { + Error("Unable to set contrast: %s", strerror(errno)); + } else { + Warning("Given contrast value (%d) may be out-of-range", p_contrast); + } } } - return( vid_control.value ); + return vid_control.value; } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { struct video_picture vid_pic; - memset( &vid_pic, 0, sizeof(video_picture) ); - if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error( "Failed to get picture attributes: %s", strerror(errno) ); - return( -1 ); + memset(&vid_pic, 0, sizeof(video_picture)); + if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { + Error("Failed to get picture attributes: %s", strerror(errno)); + return -1; } if ( p_contrast >= 0 ) { vid_pic.contrast = p_contrast; - if ( ioctl( vid_fd, VIDIOCSPICT, &vid_pic ) < 0 ) { - Error( "Failed to set picture attributes: %s", strerror(errno) ); - return( -1 ); + if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { + Error("Failed to set picture attributes: %s", strerror(errno)); + return -1; } } - return( vid_pic.contrast ); + return vid_pic.contrast; } #endif // ZM_HAS_V4L1 - return( -1 ); + return -1; } int LocalCamera::PrimeCapture() { Initialise(); - Debug( 2, "Priming capture" ); + Debug(2, "Priming capture"); #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { - Debug( 3, "Queueing (%d) buffers", v4l2_data.reqbufs.count ); + Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count); for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) { struct v4l2_buffer vid_buf; memset( &vid_buf, 0, sizeof(vid_buf) ); if ( v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) { - Warning("Unknown type: (%d)", v4l2_data.fmt.type ); + Warning("Unknown type: (%d)", v4l2_data.fmt.type); } vid_buf.type = v4l2_data.fmt.type; vid_buf.memory = v4l2_data.reqbufs.memory; vid_buf.index = frame; - if ( vidioctl( vid_fd, VIDIOC_QBUF, &vid_buf ) < 0 ) - Fatal( "Failed to queue buffer %d: %s", frame, strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) + Fatal("Failed to queue buffer %d: %s", frame, strerror(errno)); } v4l2_data.bufptr = NULL; - Debug( 3, "Starting video stream" ); + Debug(3, "Starting video stream"); //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //enum v4l2_buf_type type = v4l2_data.fmt.type; enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; - if ( vidioctl( vid_fd, VIDIOC_STREAMON, &type ) < 0 ) - Fatal( "Failed to start capture stream: %s", strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_STREAMON, &type) < 0 ) + Fatal("Failed to start capture stream: %s", strerror(errno)); } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { for ( int frame = 0; frame < v4l1_data.frames.frames; frame++ ) { - Debug( 3, "Queueing frame %d", frame ); - if ( ioctl( vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[frame] ) < 0 ) { - Error( "Capture failure for frame %d: %s", frame, strerror(errno) ); - return( -1 ); + Debug(3, "Queueing frame %d", frame); + if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[frame]) < 0 ) { + Error("Capture failure for frame %d: %s", frame, strerror(errno)); + return -1; } } } #endif // ZM_HAS_V4L1 -mVideoStreamId = 0; + mVideoStreamId = 0; return 0; -} +} // end LocalCamera::PrimeCapture int LocalCamera::PreCapture() { + //Debug(5, "Pre-capturing"); return 0; } @@ -1927,7 +1998,7 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { captures_per_frame = v4l_captures_per_frame; if ( captures_per_frame <= 0 ) { captures_per_frame = 1; - Warning( "Invalid Captures Per Frame setting: %d", captures_per_frame ); + Warning("Invalid Captures Per Frame setting: %d", captures_per_frame); } // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer @@ -1936,19 +2007,19 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { if ( v4l_version == 2 ) { static struct v4l2_buffer vid_buf; - memset( &vid_buf, 0, sizeof(vid_buf) ); + memset(&vid_buf, 0, sizeof(vid_buf)); vid_buf.type = v4l2_data.fmt.type; //vid_buf.memory = V4L2_MEMORY_MMAP; vid_buf.memory = v4l2_data.reqbufs.memory; + Debug(3, "Capturing %d frames", captures_per_frame); while ( captures_per_frame ) { - //Debug( 3, "Capturing %d frames", captures_per_frame ); if ( vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0 ) { if ( errno == EIO ) { - Warning( "Capture failure, possible signal loss?: %s", strerror(errno) ); + Warning("Capture failure, possible signal loss?: %s", strerror(errno)); } else { - Error( "Unable to capture frame %d: %s", vid_buf.index, strerror(errno) ); + Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno)); } return -1; } @@ -1956,21 +2027,25 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { v4l2_data.bufptr = &vid_buf; capture_frame = v4l2_data.bufptr->index; + bytes += vid_buf.bytesused; + if ( --captures_per_frame ) { - if ( vidioctl( vid_fd, VIDIOC_QBUF, &vid_buf ) < 0 ) { - Error( "Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno) ); + if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) { + Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno)); return -1; } } } // while captures_per_frame - Debug( 3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel ); + Debug(3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel); buffer = (unsigned char *)v4l2_data.buffers[v4l2_data.bufptr->index].start; buffer_bytesused = v4l2_data.bufptr->bytesused; + bytes += buffer_bytesused; if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { - Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); + Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", + v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); } } // end if v4l2 #if ZM_HAS_V4L1 @@ -1979,24 +2054,26 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { - Debug( 3, "Capturing %d frames", captures_per_frame ); + Debug(3, "Capturing %d frames", captures_per_frame); while ( captures_per_frame ) { - Debug( 3, "Syncing frame %d", v4l1_data.active_frame ); - if ( ioctl( vid_fd, VIDIOCSYNC, &v4l1_data.active_frame ) < 0 ) { - Error( "Sync failure for frame %d buffer %d: %s", v4l1_data.active_frame, captures_per_frame, strerror(errno) ); + Debug(3, "Syncing frame %d", v4l1_data.active_frame); + if ( ioctl(vid_fd, VIDIOCSYNC, &v4l1_data.active_frame) < 0 ) { + Error("Sync failure for frame %d buffer %d: %s", + v4l1_data.active_frame, captures_per_frame, strerror(errno) ); return -1; } captures_per_frame--; if ( captures_per_frame ) { - Debug( 3, "Capturing frame %d", v4l1_data.active_frame ); - if ( ioctl( vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame] ) < 0 ) { - Error( "Capture failure for buffer %d (%d): %s", v4l1_data.active_frame, captures_per_frame, strerror(errno) ); + Debug(3, "Capturing frame %d", v4l1_data.active_frame); + if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame]) < 0 ) { + Error("Capture failure for buffer %d (%d): %s", + v4l1_data.active_frame, captures_per_frame, strerror(errno)); return -1; } } } capture_frame = v4l1_data.active_frame; - Debug( 3, "Captured %d for channel %d", capture_frame, channel ); + Debug(3, "Captured %d for channel %d", capture_frame, channel); buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; } @@ -2006,7 +2083,7 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { if ( conversion_type != 0 ) { - Debug( 3, "Performing format conversion" ); + Debug(3, "Performing format conversion"); /* Request a writeable buffer of the target image */ uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); @@ -2016,8 +2093,7 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { } #if HAVE_LIBSWSCALE if ( conversion_type == 1 ) { - - Debug( 9, "Setting up a frame" ); + Debug(9, "Calling sws_scale to perform the conversion"); /* Use swscale to convert the image directly into the shared memory */ #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(tmpPicture->data, @@ -2039,18 +2115,18 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { } else #endif if ( conversion_type == 2 ) { - Debug( 9, "Calling the conversion function" ); + Debug(9, "Calling the conversion function"); /* Call the image conversion function and convert directly into the shared memory */ (*conversion_fptr)(buffer, directbuffer, pixels); } else if ( conversion_type == 3 ) { // Need to store the jpeg data too - Debug( 9, "Decoding the JPEG image" ); + Debug(9, "Decoding the JPEG image"); /* JPEG decoding */ zm_packet.image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); } } else { - Debug( 5, "No format conversion performed. Assigning the image" ); + Debug(3, "No format conversion performed. Assigning the image"); /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); @@ -2061,16 +2137,16 @@ int LocalCamera::Capture( ZMPacket &zm_packet ) { } // end int LocalCamera::Capture() int LocalCamera::PostCapture() { - Debug( 4, "Post-capturing" ); + Debug(4, "Post-capturing"); // Requeue the buffer unless we need to switch or are a duplicate camera on a channel if ( channel_count > 1 || channel_prime ) { #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { if ( channel_count > 1 ) { int next_channel = (channel_index+1)%channel_count; - Debug( 3, "Switching video source to %d", channels[next_channel] ); - if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel] ) < 0) { - Error( "Failed to set camera source %d: %s", channels[next_channel], strerror(errno) ); + Debug(3, "Switching video source to %d", channels[next_channel]); + if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0 ) { + Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno)); return -1; } @@ -2097,29 +2173,29 @@ int LocalCamera::PostCapture() { #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { if ( channel_count > 1 ) { - Debug( 3, "Switching video source" ); + Debug(3, "Switching video source"); int next_channel = (channel_index+1)%channel_count; struct video_channel vid_src; - memset( &vid_src, 0, sizeof(vid_src) ); + memset(&vid_src, 0, sizeof(vid_src)); vid_src.channel = channel; - if ( ioctl( vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) { - Error( "Failed to get camera source %d: %s", channel, strerror(errno) ); - return(-1); + if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) { + Error("Failed to get camera source %d: %s", channel, strerror(errno)); + return -1; } vid_src.channel = channels[next_channel]; vid_src.norm = standards[next_channel]; vid_src.flags = 0; vid_src.type = VIDEO_TYPE_CAMERA; - if ( ioctl( vid_fd, VIDIOCSCHAN, &vid_src ) < 0 ) { - Error( "Failed to set camera source %d: %s", channel, strerror(errno) ); - return( -1 ); + if ( ioctl(vid_fd, VIDIOCSCHAN, &vid_src) < 0 ) { + Error("Failed to set camera source %d: %s", channel, strerror(errno)); + return -1; } } - Debug( 3, "Requeueing frame %d", v4l1_data.active_frame ); - if ( ioctl( vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame] ) < 0 ) { - Error( "Capture failure for frame %d: %s", v4l1_data.active_frame, strerror(errno) ); - return( -1 ); + Debug(3, "Requeueing frame %d", v4l1_data.active_frame); + if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame]) < 0 ) { + Error("Capture failure for frame %d: %s", v4l1_data.active_frame, strerror(errno)); + return -1; } v4l1_data.active_frame = (v4l1_data.active_frame+1)%v4l1_data.frames.frames; } @@ -2127,6 +2203,7 @@ int LocalCamera::PostCapture() { } return 0; } + AVStream *LocalCamera::get_VideoStream() { if ( ! video_stream ) { AVFormatContext *oc = avformat_alloc_context(); @@ -2153,6 +2230,4 @@ AVStream *LocalCamera::get_VideoStream() { return video_stream; } - #endif // ZM_HAS_V4L - diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index d99f72b1c..a42f79d19 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -39,7 +39,7 @@ #include bool Logger::smInitialised = false; -Logger *Logger::smInstance = 0; +Logger *Logger::smInstance = NULL; Logger::StringMap Logger::smCodes; Logger::IntMap Logger::smSyslogPriorities; @@ -59,9 +59,9 @@ static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 void Logger::usrHandler( int sig ) { Logger *logger = fetch(); if ( sig == SIGUSR1 ) - logger->level( logger->level()+1 ); + logger->level(logger->level()+1); else if ( sig == SIGUSR2 ) - logger->level( logger->level()-1 ); + logger->level(logger->level()-1); Info("Logger - Level changed to %d", logger->level()); } @@ -80,7 +80,7 @@ Logger::Logger() : mFlush(false) { if ( smInstance ) { - Panic( "Attempt to create second instance of Logger class" ); + Panic("Attempt to create second instance of Logger class"); } if ( !smInitialised ) { @@ -134,11 +134,11 @@ void Logger::initialise(const std::string &id, const Options &options) { std::string tempLogFile; - if ( (envPtr = getTargettedEnv("LOG_FILE")) ) + if ( (envPtr = getTargettedEnv("LOG_FILE")) ) { tempLogFile = envPtr; - else if ( options.mLogFile.size() ) + } else if ( options.mLogFile.size() ) { tempLogFile = options.mLogFile; - else { + } else { if ( options.mLogPath.size() ) { mLogPath = options.mLogPath; } @@ -170,7 +170,7 @@ void Logger::initialise(const std::string &id, const Options &options) { tempSyslogLevel = config.log_level_syslog >= DEBUG1 ? DEBUG9 : config.log_level_syslog; // Legacy - if ( (envPtr = getenv( "LOG_PRINT" )) ) + if ( (envPtr = getenv("LOG_PRINT")) ) tempTerminalLevel = atoi(envPtr) ? DEBUG9 : NOLOG; if ( (envPtr = getTargettedEnv("LOG_LEVEL")) ) @@ -219,7 +219,7 @@ void Logger::initialise(const std::string &id, const Options &options) { mFlush = false; if ( (envPtr = getenv("LOG_FLUSH")) ) { - mFlush = atoi( envPtr ); + mFlush = atoi(envPtr); } else if ( config.log_debug ) { mFlush = true; } @@ -570,12 +570,12 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co } } -void logInit( const char *name, const Logger::Options &options ) { +void logInit(const char *name, const Logger::Options &options) { if ( !Logger::smInstance ) Logger::smInstance = new Logger(); Logger::Options tempOptions = options; tempOptions.mLogPath = staticConfig.PATH_LOGS; - Logger::smInstance->initialise( name, tempOptions ); + Logger::smInstance->initialise(name, tempOptions); } void logTerm() { diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 0224c3b4a..d08c155e9 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -890,6 +890,18 @@ bool Monitor::connect() { exit(-1); } } + if ( purpose == ANALYSIS ) { + if ( analysis_fps ) { + // Size of pre event buffer must be greater than pre_event_count + // if alarm_frame_count > 1, because in this case the buffer contains + // alarmed images that must be discarded when event is created + pre_event_buffer_count = pre_event_count + alarm_frame_count - 1; + } + + timestamps = new struct timeval *[pre_event_count]; + images = new Image *[pre_event_count]; + last_signal = shared_data->signal; + } Debug(3, "Success connecting"); return true; @@ -1523,7 +1535,6 @@ bool Monitor::CheckSignal( const Image *image ) { void Monitor::CheckAction() { struct timeval now; gettimeofday(&now, NULL); - if ( shared_data->action ) { // Can there be more than 1 bit set in the action? Shouldn't these be elseifs? if ( shared_data->action & RELOAD ) { @@ -1595,8 +1606,8 @@ bool Monitor::Analyse() { // last_write_index is the last capture // last_read_index is the last analysis - if ( ! Enabled() ) { - Warning("SHouldn't be doing Analyze when not Enabled"); + if ( !Enabled() ) { + Warning("Shouldn't be doing Analyze when not Enabled"); return false; } @@ -1726,7 +1737,7 @@ bool Monitor::Analyse() { cause += ", "; cause += LINKED_CAUSE; } - noteSet.insert( linked_monitors[i]->Name() ); + noteSet.insert(linked_monitors[i]->Name()); score += 50; } } // end foreach linked_monitor @@ -2326,19 +2337,17 @@ int Monitor::Capture() { //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps ); Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", name, image_count, new_capture_fps, new_capture_bandwidth); - if ( new_capture_fps != capture_fps ) { - capture_fps = new_capture_fps; - last_fps_time = now; - static char sql[ZM_SQL_SML_BUFSIZ]; - db_mutex.lock(); - snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,CaptureFPS,CaptureBandwidth) VALUES (%d, %.2lf,%u) ON DUPLICATE KEY UPDATE CaptureFPS = %.2lf, CaptureBandwidth=%u", - id, capture_fps, new_capture_bandwidth, capture_fps, new_capture_bandwidth); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); - } // end if fps has changed + capture_fps = new_capture_fps; + last_fps_time = now; + static char sql[ZM_SQL_SML_BUFSIZ]; + db_mutex.lock(); + snprintf(sql, sizeof(sql), + "INSERT INTO Monitor_Status (MonitorId,CaptureFPS,CaptureBandwidth) VALUES (%d, %.2lf,%u) ON DUPLICATE KEY UPDATE CaptureFPS = %.2lf, CaptureBandwidth=%u", + id, capture_fps, new_capture_bandwidth, capture_fps, new_capture_bandwidth); + if ( mysql_query(&dbconn, sql) ) { + Error("Can't run query: %s", mysql_error(&dbconn)); + } + db_mutex.unlock(); } } // end if report fps } else { // result == 0 diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 978dda890..1e57bb495 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -691,7 +691,7 @@ Debug(2, "Have checking command Queue for connkey: %d", connkey ); } // end if buffered playback frame_count++; } else { - Debug(5,"Waiting for capture"); + Debug(4,"Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))); 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_remote_camera_http.h b/src/zm_remote_camera_http.h index cbaa6e9f8..b4f9ebe3a 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -73,7 +73,7 @@ public: int Capture( ZMPacket &p ); int PostCapture(); AVStream* get_VideoStream(); - int Close() { return 0; }; + int Close() { Disconnect(); return 0; }; }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 2ca51596d..2d7d8887d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -367,6 +367,6 @@ int RemoteCameraRtsp::Capture( ZMPacket &zm_packet ) { } // end int RemoteCameraRtsp::Capture(ZMPacket &packet) int RemoteCameraRtsp::PostCapture() { - return( 0 ); + return 0; } #endif // HAVE_LIBAVFORMAT 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_thread.cpp b/src/zm_thread.cpp index e9f6f7621..64d07f104 100644 --- a/src/zm_thread.cpp +++ b/src/zm_thread.cpp @@ -97,6 +97,15 @@ bool Mutex::locked() { return( state == EBUSY ); } +RecursiveMutex::RecursiveMutex() { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + if ( pthread_mutex_init(&mMutex, &attr) < 0 ) + Error("Unable to create pthread mutex: %s", strerror(errno)); +} + Condition::Condition( Mutex &mutex ) : mMutex( mutex ) { if ( pthread_cond_init( &mCondition, NULL ) < 0 ) Fatal( "Unable to create pthread condition: %s", strerror(errno) ); diff --git a/src/zm_thread.h b/src/zm_thread.h index e1facc6a5..0c41a93a5 100644 --- a/src/zm_thread.h +++ b/src/zm_thread.h @@ -59,27 +59,34 @@ public: }; class Mutex { -friend class Condition; + friend class Condition; -private: - pthread_mutex_t mMutex; + private: + pthread_mutex_t mMutex; -public: - Mutex(); - ~Mutex(); + public: + Mutex(); + ~Mutex(); -private: - pthread_mutex_t *getMutex() { - return( &mMutex ); - } + private: + pthread_mutex_t *getMutex() { + return &mMutex; + } -public: - int trylock(); - void lock(); - void lock( int secs ); - void lock( double secs ); - void unlock(); - bool locked(); + public: + int trylock(); + void lock(); + void lock( int secs ); + void lock( double secs ); + void unlock(); + bool locked(); +}; + +class RecursiveMutex : public Mutex { + private: + pthread_mutex_t mMutex; + public: + RecursiveMutex(); }; class ScopedMutex { diff --git a/src/zm_video.cpp b/src/zm_video.cpp index 22379f2b3..07bedc683 100644 --- a/src/zm_video.cpp +++ b/src/zm_video.cpp @@ -376,7 +376,10 @@ int X264MP4Writer::x264config() { x264params.b_annexb = 0; /* TODO: Setup error handler */ - // x264params.i_log_level = X264_LOG_DEBUG; + if ( logDebugging() ) + x264params.i_log_level = X264_LOG_DEBUG; + else + x264params.i_log_level = X264_LOG_NONE; /* Process user parameters (excluding preset, tune and profile) */ for ( unsigned int i = 0; i < user_params.size(); i++ ) { diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 1615427ac..b0303faa1 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -689,6 +689,10 @@ bool VideoStore::setup_resampler() { audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; audio_out_ctx->channels = audio_in_ctx->channels; audio_out_ctx->channel_layout = audio_in_ctx->channel_layout; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) +#else + audio_out_ctx->refcounted_frames = 1; +#endif if ( audio_out_codec->supported_samplerates ) { int found = 0; diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index b75d3678f..7bdf5bf23 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -17,12 +17,15 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // +#define __STDC_FORMAT_MACROS 1 +#include #include "zm.h" #include "zm_db.h" #include "zm_zone.h" #include "zm_image.h" #include "zm_monitor.h" + void Zone::Setup( Monitor *p_monitor, int p_id, diff --git a/src/zma.cpp b/src/zma.cpp index 83035f011..33dedbe54 100644 --- a/src/zma.cpp +++ b/src/zma.cpp @@ -83,7 +83,7 @@ int main( int argc, char *argv[] ) { while (1) { int option_index = 0; - int c = getopt_long (argc, argv, "m:h:v", long_options, &option_index); + int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index); if ( c == -1 ) { break; } @@ -144,7 +144,7 @@ int main( int argc, char *argv[] ) { unsigned int analysis_update_delay = monitor->GetAnalysisUpdateDelay(); time_t last_analysis_update_time, cur_time; monitor->UpdateAdaptiveSkip(); - last_analysis_update_time = time( 0 ); + last_analysis_update_time = time(0); while( (!zm_terminate) && monitor->ShmValid() ) { // Process the next image @@ -181,5 +181,5 @@ int main( int argc, char *argv[] ) { Image::Deinitialise(); logTerm(); zmDbClose(); - return( 0 ); + return 0; } diff --git a/src/zmc.cpp b/src/zmc.cpp index 543a67135..dbe317731 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -386,5 +386,5 @@ int main(int argc, char *argv[]) { logTerm(); zmDbClose(); - return result; + return zm_terminate ? 0 : result; } diff --git a/src/zms.cpp b/src/zms.cpp index e8d20f72f..74dab2ed9 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -46,7 +46,7 @@ bool ValidateAccess( User *user, int mon_id ) { user->Id(), user->getUsername(), mon_id ); exit( -1 ); } - return( allowed ); + return allowed; } int main( int argc, const char *argv[] ) { @@ -74,45 +74,44 @@ int main( int argc, const char *argv[] ) { unsigned int playback_buffer = 0; bool nph = false; - const char *basename = strrchr( argv[0], '/' ); - if (basename) //if we found a / lets skip past it + const char *basename = strrchr(argv[0], '/'); + if ( basename ) //if we found a / lets skip past it basename++; else //argv[0] will not always contain the full path, but rather just the script name basename = argv[0]; const char *nph_prefix = "nph-"; - if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) ) { + if ( basename && !strncmp(basename, nph_prefix, strlen(nph_prefix)) ) { nph = true; } zmLoadConfig(); - - const char *query = getenv( "QUERY_STRING" ); + const char *query = getenv("QUERY_STRING"); if ( query ) { - Debug( 1, "Query: %s", query ); + Debug(1, "Query: %s", query); char temp_query[1024]; - strncpy( temp_query, query, sizeof(temp_query) ); + strncpy(temp_query, query, sizeof(temp_query)); char *q_ptr = temp_query; char *parms[16]; // Shouldn't be more than this int parm_no = 0; - while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) ) { + while( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { parm_no++; q_ptr = NULL; } for ( int p = 0; p < parm_no; p++ ) { - char *name = strtok( parms[p], "=" ); - char *value = strtok( NULL, "=" ); + char *name = strtok(parms[p], "="); + char *value = strtok(NULL, "="); if ( !value ) value = (char *)""; - if ( !strcmp( name, "source" ) ) { - source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR; - } else if ( !strcmp( name, "mode" ) ) { - mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG; - mode = !strcmp( value, "raw" )?ZMS_RAW:mode; - mode = !strcmp( value, "zip" )?ZMS_ZIP:mode; - mode = !strcmp( value, "single" )?ZMS_SINGLE:mode; + if ( !strcmp(name, "source") ) { + source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR; + } else if ( !strcmp(name, "mode") ) { + mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG; + mode = !strcmp(value, "raw")?ZMS_RAW:mode; + mode = !strcmp(value, "zip")?ZMS_ZIP:mode; + mode = !strcmp(value, "single")?ZMS_SINGLE:mode; } else if ( !strcmp( name, "format" ) ) { strncpy( format, value, sizeof(format) ); } else if ( !strcmp( name, "monitor" ) ) { @@ -182,32 +181,32 @@ int main( int argc, const char *argv[] ) { if ( config.opt_use_auth ) { User *user = 0; - if ( strcmp( config.auth_relay, "none" ) == 0 ) { + if ( strcmp(config.auth_relay, "none") == 0 ) { if ( username.length() ) { - user = zmLoadUser( username.c_str() ); + user = zmLoadUser(username.c_str()); } } else { //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) { if ( *auth ) { - user = zmLoadAuthUser( auth, config.auth_hash_ips ); + user = zmLoadAuthUser(auth, config.auth_hash_ips); } } //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) { if ( username.length() && password.length() ) { - user = zmLoadUser( username.c_str(), password.c_str() ); + user = zmLoadUser(username.c_str(), password.c_str()); } } } if ( !user ) { - Error( "Unable to authenticate user" ); + Error("Unable to authenticate user"); logTerm(); zmDbClose(); - return( -1 ); + return -1; } - ValidateAccess( user, monitor_id ); - } + ValidateAccess(user, monitor_id); + } // end if config.opt_use_auth hwcaps_detect(); zmSetDefaultTermHandler(); diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 3794e4645..3ed227a44 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -257,11 +257,14 @@ execpackpack () { fi if [ "${TRAVIS}" == "true" ]; then - utils/packpack/heartbeat.sh & - mypid=$! - packpack/packpack $parms > buildlog.txt 2>&1 - kill $mypid - tail -n 3000 buildlog.txt | grep -v ONVIF + # Travis will fail the build if the output gets too long + # To mitigate that, use grep to filter out some of the noise + if [ "${ARCH}" != "armhf" ]; then + 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:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])' + fi else packpack/packpack $parms fi diff --git a/utils/zmeditconfigdata.sh b/utils/zmeditconfigdata.sh index c95569f34..32b000730 100755 --- a/utils/zmeditconfigdata.sh +++ b/utils/zmeditconfigdata.sh @@ -82,7 +82,7 @@ fi # Don't stare too closely. You will burn your eyes out. sed -i '/.*'${variable}'.*/{ $!{ N - s/\(.*'${variable}'.*\n.*\)\"\(.*\)\"/\1\"'"${default}"'\"/ + s/\(.*'${variable}'.*\n.*\)'\''\(.*\)'\''/\1'\'''"${default}"''\''/ t yes P D diff --git a/version b/version index 856307b22..6701dcb1c 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.44 +1.31.45 diff --git a/web/ajax/alarm.php b/web/ajax/alarm.php index 2d4690866..002f9f784 100644 --- a/web/ajax/alarm.php +++ b/web/ajax/alarm.php @@ -1,40 +1,28 @@ diff --git a/web/ajax/event.php b/web/ajax/event.php index 04ebca9ad..cf8c59aa3 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -1,28 +1,28 @@ $videoFile ) ); + if ( !($event = dbFetchOne($sql, NULL, array( $_REQUEST['id']))) ) { + ajaxError('Video Generation Failure, Unable to load event'); + } else { + if ( $videoFile = createVideo($event, $_REQUEST['videoFormat'], $_REQUEST['rate'], $_REQUEST['scale'], !empty($_REQUEST['overwrite'])) ) + ajaxResponse(array('response'=>$videoFile)); else - ajaxError( 'Video Generation Failed' ); + ajaxError('Video Generation Failed'); } } $ok = true; @@ -37,7 +37,7 @@ if ( canView( 'Events' ) ) { } case 'export' : { - require_once( ZM_SKIN_PATH.'/includes/export_functions.php' ); + require_once(ZM_SKIN_PATH.'/includes/export_functions.php'); # We use session vars in here, so we need to restart the session because we stopped it in index.php to improve concurrency. session_start(); @@ -46,22 +46,27 @@ if ( canView( 'Events' ) ) { $exportDetail = $_SESSION['export']['detail'] = $_REQUEST['exportDetail']; else $exportDetail = false; + if ( !empty($_REQUEST['exportFrames']) ) $exportFrames = $_SESSION['export']['frames'] = $_REQUEST['exportFrames']; else $exportFrames = false; + if ( !empty($_REQUEST['exportImages']) ) $exportImages = $_SESSION['export']['images'] = $_REQUEST['exportImages']; else $exportImages = false; + if ( !empty($_REQUEST['exportVideo']) ) $exportVideo = $_SESSION['export']['video'] = $_REQUEST['exportVideo']; else $exportVideo = false; + if ( !empty($_REQUEST['exportMisc']) ) $exportMisc = $_SESSION['export']['misc'] = $_REQUEST['exportMisc']; else $exportMisc = false; + if ( !empty($_REQUEST['exportFormat']) ) $exportFormat = $_SESSION['export']['format'] = $_REQUEST['exportFormat']; else @@ -70,67 +75,84 @@ if ( canView( 'Events' ) ) { session_write_close(); $exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id']; - if ( $exportFile = exportEvents( $exportIds, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat ) ) - ajaxResponse( array( 'exportFile'=>$exportFile ) ); + if ( $exportFile = exportEvents( + $exportIds, + (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), + $exportDetail, + $exportFrames, + $exportImages, + $exportVideo, + $exportMisc, + $exportFormat + ) ) + ajaxResponse(array('exportFile'=>$exportFile)); else - ajaxError( 'Export Failed' ); + ajaxError('Export Failed'); break; } case 'download' : { - require_once( ZM_SKIN_PATH.'/includes/export_functions.php' ); + require_once(ZM_SKIN_PATH.'/includes/export_functions.php'); $exportVideo = 1; $exportFormat = $_REQUEST['exportFormat']; $exportStructure = 'flat'; $exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id']; - if ( $exportFile = exportEvents( $exportIds, false, false, false, $exportVideo, false, $exportFormat, $exportStructure ) ) - ajaxResponse( array( 'exportFile'=>$exportFile ) ); + if ( $exportFile = exportEvents( + $exportIds, + (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), + false,false, false, $exportVideo, false, $exportFormat, $exportStructure ) ) + ajaxResponse(array('exportFile'=>$exportFile)); else - ajaxError( 'Export Failed' ); + ajaxError('Export Failed'); break; } } -} +} // end if canView('Events') -if ( canEdit( 'Events' ) ) { +if ( canEdit('Events') ) { switch ( $_REQUEST['action'] ) { case 'rename' : { if ( !empty($_REQUEST['eventName']) ) - dbQuery( 'UPDATE Events SET Name = ? WHERE Id = ?', array( $_REQUEST['eventName'], $_REQUEST['id'] ) ); + dbQuery('UPDATE Events SET Name = ? WHERE Id = ?', array($_REQUEST['eventName'], $_REQUEST['id'])); else - ajaxError( 'No new event name supplied' ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); + ajaxError('No new event name supplied'); + ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>true)); break; } case 'eventdetail' : { - dbQuery( 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id'] ) ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); + dbQuery( + 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', + array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id']) + ); + ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>true)); break; } case 'archive' : case 'unarchive' : { $archiveVal = ($_REQUEST['action'] == 'archive')?1:0; - dbQuery( 'UPDATE Events SET Archived = ? WHERE Id = ?', array( $archiveVal, $_REQUEST['id']) ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>false ) ); + dbQuery( + 'UPDATE Events SET Archived = ? WHERE Id = ?', + array($archiveVal, $_REQUEST['id']) + ); + ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>false)); break; } case 'delete' : { - $Event = new Event( $_REQUEST['id'] ); + $Event = new Event($_REQUEST['id']); if ( ! $Event->Id() ) { - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.' ) ); + ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.')); } else { $Event->delete(); - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); + ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true)); } break; } } -} - -ajaxError( 'Unrecognised action or insufficient permissions' ); +} // end if canEdit('Events') +ajaxError('Unrecognised action or insufficient permissions'); ?> diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 1c2fb3d62..44aaec8e6 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -22,13 +22,9 @@ if ( sem_acquire($semaphore,1) !== false ) { if ( file_exists( $localSocketFile ) ) { Warning("sock file $localSocketFile already exists?! Is someone else talking to zms?"); // They could be. We can maybe have concurrent requests from a browser. - } else { - Logger::Debug("socket file does not exist, we should be good to connect."); } if ( ! socket_bind( $socket, $localSocketFile ) ) { - ajaxError( "socket_bind( $localSocketFile ) failed: ".socket_strerror(socket_last_error()) ); - } else { - Logger::Debug("Bound to $localSocketFile"); + ajaxError("socket_bind( $localSocketFile ) failed: ".socket_strerror(socket_last_error()) ); } switch ( $_REQUEST['command'] ) { @@ -81,7 +77,6 @@ if ( sem_acquire($semaphore,1) !== false ) { $eSockets = NULL; $timeout = MSG_TIMEOUT - ( time() - $start_time ); - Logger::Debug("TImeout is: $timeout/1000 seconds. " ); $numSockets = socket_select( $rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000 ); 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/ConfigsController.php b/web/api/app/Controller/ConfigsController.php index 48f50ae8e..2084a84b3 100644 --- a/web/api/app/Controller/ConfigsController.php +++ b/web/api/app/Controller/ConfigsController.php @@ -21,15 +21,18 @@ class ConfigsController extends AppController { * * @return void */ - public function index() { - $this->Config->recursive = 0; - $configs = $this->Config->find('all'); - $this->set(array( - 'configs' => $configs, - '_serialize' => array('configs') - )); - } - + public function index() { + global $configvals; + $this->Config->recursive = 0; + $configs = $this->Config->find('all'); + foreach ( $configvals as $k=>$v ) { + $configs[] = array( 'Config'=>array('Name'=>$k, 'Value'=>$v ) ); + } + $this->set(array( + 'configs' => $configs, + '_serialize' => array('configs') + )); + } /** * view method @@ -53,8 +56,14 @@ class ConfigsController extends AppController { public function viewByName($name = null) { $config = $this->Config->findByName($name, array('fields' => 'Value')); - if (!$config) { - throw new NotFoundException(__('Invalid config')); + if ( !$config ) { + global $configvals; + if ( $configvals[$name] ) { + $config = array( 'Config'=>array('Value'=>$configvals[$name]) ); + } else { + throw new NotFoundException(__('Invalid config')); + } + } else { } $this->set(array( 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 660bee5ce..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 @@ -332,25 +348,18 @@ class MonitorsController extends AppController { } public function daemonControl($id, $command, $monitor=null, $daemon=null) { - $args = ''; $daemons = array(); - if (!$monitor) { + if ( !$monitor ) { // Need to see if it is local or remote $monitor = $this->Monitor->find('first', array( - 'fields' => array('Type', 'Function'), + 'fields' => array('Type', 'Function', 'Device'), 'conditions' => array('Id' => $id) )); $monitor = $monitor['Monitor']; } - if ($monitor['Type'] == 'Local') { - $args = '-d ' . $monitor['Device']; - } else { - $args = '-m ' . $id; - } - - if ($monitor['Function'] == 'Monitor') { + if ( $monitor['Function'] == 'Monitor' ) { array_push($daemons, 'zmc'); } else { array_push($daemons, 'zmc', 'zma'); @@ -358,10 +367,16 @@ 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' ) { + $args = '-d ' . $monitor['Device']; + } else { + $args = '-m ' . $id; + } + $shellcmd = escapeshellcmd("$zm_path_bin/zmdc.pl $command $daemon $args"); $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/Event.php b/web/includes/Event.php index d7ce25dc4..18adff226 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -246,7 +246,7 @@ class Event { if ( is_null($new) or ( $new != '' ) ) { $this->{'DiskSpace'} = $new; } - if ( null === $this->{'DiskSpace'} ) { + if ( (!array_key_exists('DiskSpace',$this)) or (null === $this->{'DiskSpace'}) ) { $this->{'DiskSpace'} = folder_size($this->Path()); dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'})); } diff --git a/web/includes/Filter.php b/web/includes/Filter.php index de51b9876..721302edb 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -20,7 +20,7 @@ public $defaults = array( 'limit' => 100, 'Query' => array(), 'sort_field' => ZM_WEB_EVENT_SORT_FIELD, - 'sort_asc' => ZM_WEB_EVENT_SORT_ORDER == 'asc' ? 'asc' : 'desc', + 'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, ); public function __construct( $IdOrRow=NULL ) { diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 8f43cda9f..fc33988a7 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -9,8 +9,10 @@ private $defaults = array( 'Name' => '', 'StorageId' => 0, 'ServerId' => 0, + 'Type' => 'Ffmpeg', 'Function' => 'None', 'Enabled' => 1, + 'LinkedMonitors' => null, 'Width' => null, 'Height' => null, 'Orientation' => null, @@ -20,9 +22,9 @@ private $defaults = array( 'OutputContainer' => 'auto', 'ZoneCount' => 0, 'Triggers' => null, - 'Type' => 'Ffmpeg', 'MaxFPS' => null, 'AlarmMaxFPS' => null, + 'Refresh' => null, ); private $status_fields = array( 'AnalysisFPS' => null, @@ -218,7 +220,7 @@ private $control_fields = array( if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { - $args['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args['user'] = $_SESSION['username']; $args['pass'] = $_SESSION['password']; @@ -338,13 +340,13 @@ private $control_fields = array( } if ( $mode == 'stop' ) { - daemonControl( 'stop', 'zmc', $zmcArgs ); + daemonControl('stop', 'zmc', $zmcArgs); } else { if ( $mode == 'restart' ) { - daemonControl( 'stop', 'zmc', $zmcArgs ); + daemonControl('stop', 'zmc', $zmcArgs); } if ( $this->{'Function'} != 'None' ) { - daemonControl( 'start', 'zmc', $zmcArgs ); + daemonControl('start', 'zmc', $zmcArgs); } } } else if ( $this->ServerId() ) { @@ -381,6 +383,8 @@ private $control_fields = array( } catch ( Exception $e ) { Error("Except $e thrown trying to restart zmc"); } + } else { + Error("Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor."); } } // end function zmcControl @@ -388,9 +392,9 @@ private $control_fields = array( if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) { if ( ZM_OPT_CONTROL ) { - daemonControl( 'stop', 'zmtrack.pl', '-m '.$this->{'Id'} ); + daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'}); } - daemonControl( 'stop', 'zma', '-m '.$this->{'Id'} ); + daemonControl('stop', 'zma', '-m '.$this->{'Id'}); } else { if ( $mode == 'restart' ) { if ( ZM_OPT_CONTROL ) { @@ -407,7 +411,8 @@ private $control_fields = array( } } } // end if we are on the recording server - } + } // end public function zmaControl + public function GroupIds( $new='') { if ( $new != '' ) { if(!is_array($new)) { @@ -468,6 +473,8 @@ private $control_fields = array( $this->{'Storage'} = isset($this->{'StorageId'}) ? Storage::find_one(array('Id'=>$this->{'StorageId'})) : new Storage(NULL); + if ( ! $this->{'Storage'} ) + $this->{'Storage'} = new Storage(NULL); } return $this->{'Storage'}; } @@ -485,14 +492,20 @@ private $control_fields = array( $source = preg_replace( '/^.*\//', '', $this->{'Path'} ); } elseif ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) { $url_parts = parse_url( $this->{'Path'} ); - unset($url_parts['user']); - unset($url_parts['pass']); - #unset($url_parts['scheme']); - unset($url_parts['query']); - #unset($url_parts['path']); - if ( isset($url_parts['port']) and ( $url_parts['port'] == '80' or $url_parts['port'] == '554' ) ) - unset($url_parts['port']); - $source = unparse_url($url_parts); + if ( ZM_WEB_FILTER_SOURCE == "Hostname" ) { # Filter out everything but the hostname + $source = $url_parts['host']; + } elseif ( ZM_WEB_FILTER_SOURCE == "NoCredentials" ) { # Filter out sensitive and common items + unset($url_parts['user']); + unset($url_parts['pass']); + #unset($url_parts['scheme']); + unset($url_parts['query']); + #unset($url_parts['path']); + if ( isset($url_parts['port']) and ( $url_parts['port'] == '80' or $url_parts['port'] == '554' ) ) + unset($url_parts['port']); + $source = unparse_url($url_parts); + } else { # Don't filter anything + $source = $this->{'Path'}; + } } if ( $source == '' ) { $source = 'Monitor ' . $this->{'Id'}; diff --git a/web/includes/Server.php b/web/includes/Server.php index d76984598..591563b52 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -1,5 +1,5 @@ {'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 89d87ae0e..04fc6fbab 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'; @@ -476,15 +476,12 @@ if ( canEdit( 'Monitors' ) ) { ); if ( $_REQUEST['newMonitor']['ServerId'] == 'auto' ) { - Logger::Debug("Auto selecting server"); $_REQUEST['newMonitor']['ServerId'] = dbFetchOne('SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id'); Logger::Debug("Auto selecting server: Got " . $_REQUEST['newMonitor']['ServerId'] ); if ( ( ! $_REQUEST['newMonitor'] ) and defined('ZM_SERVER_ID') ) { $_REQUEST['newMonitor']['ServerId'] = ZM_SERVER_ID; Logger::Debug("Auto selecting server to " . ZM_SERVER_ID); } - } else { - Logger::Debug("NOT Auto selecting server" . $_REQUEST['newMonitor']['ServerId']); } $columns = getTableColumns('Monitors'); @@ -495,8 +492,8 @@ if ( canEdit( 'Monitors' ) ) { # If we change anything that changes the shared mem size, zma can complain. So let's stop first. if ( $monitor['Type'] != 'WebSite' ) { - zmaControl( $monitor, 'stop' ); - zmcControl( $monitor, 'stop' ); + zmaControl($monitor, 'stop'); + zmcControl($monitor, 'stop'); } dbQuery( 'UPDATE Monitors SET '.implode( ', ', $changes ).' WHERE Id=?', array($mid) ); // Groups will be added below @@ -570,23 +567,26 @@ if ( canEdit( 'Monitors' ) ) { } $restart = true; + } else { + Logger::Debug("No action due to no changes to Monitor"); } # end if count(changes) - if ( - ( !isset($_POST['newMonitor']['GroupIds']) ) - or - ( count($_POST['newMonitor']['GroupIds']) != count($Monitor->GroupIds()) ) - or - array_diff($_POST['newMonitor']['GroupIds'], $Monitor->GroupIds()) - ) { - if ( $Monitor->Id() ) - dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', array($mid)); - if ( isset($_POST['newMonitor']['GroupIds']) ) { - foreach ( $_POST['newMonitor']['GroupIds'] as $group_id ) { - dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); - } + if ( + ( !isset($_POST['newMonitor']['GroupIds']) ) + or + ( count($_POST['newMonitor']['GroupIds']) != count($Monitor->GroupIds()) ) + or + array_diff($_POST['newMonitor']['GroupIds'], $Monitor->GroupIds()) + ) { + if ( $Monitor->Id() ) + dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', array($mid)); + + if ( isset($_POST['newMonitor']['GroupIds']) ) { + foreach ( $_POST['newMonitor']['GroupIds'] as $group_id ) { + dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); } - } // end if there has been a change of groups + } + } // end if there has been a change of groups if ( ZM_OPT_X10 ) { $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); @@ -732,7 +732,7 @@ if ( canEdit( 'System' ) ) { $_SESSION['zmMontageLayout'] = $Layout->Id(); setcookie('zmMontageLayout', $Layout->Id(), 1 ); session_write_close(); - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=montagereview'; + $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=montage'; } // end if save } else if ( $_REQUEST['object'] == 'server' ) { diff --git a/web/includes/auth.php b/web/includes/auth.php index 297dcaec9..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() { @@ -103,13 +110,13 @@ function getAuthUser($auth) { return false; } // end getAuthUser($auth) -function generateAuthHash($useRemoteAddr) { +function generateAuthHash($useRemoteAddr, $force=false) { if ( ZM_OPT_USE_AUTH and ZM_AUTH_RELAY == 'hashed' and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) { # regenerate a hash at half the liftetime of a hash, an hour is 3600 so half is 1800 $time = time(); $mintime = $time - ( ZM_AUTH_HASH_TTL * 1800 ); - if ( ( !isset($_SESSION['AuthHash']) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) { + if ( $force or ( !isset($_SESSION['AuthHash']) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) { # Don't both regenerating Auth Hash if an hour hasn't gone by yet $local_time = localtime(); $authKey = ''; @@ -120,19 +127,25 @@ function generateAuthHash($useRemoteAddr) { } #Logger::Debug("Generated using hour:".$local_time[2] . ' mday:' . $local_time[3] . ' month:'.$local_time[4] . ' year: ' . $local_time[5] ); $auth = md5($authKey); - session_start(); - $_SESSION['AuthHash'] = $auth; - $_SESSION['AuthHashGeneratedAt'] = $time; - session_write_close(); + if ( !$force ) { + $close_session = 0; + if ( !is_session_started() ) { + session_start(); + $close_session = 1; + } + $_SESSION['AuthHash'] = $auth; + $_SESSION['AuthHashGeneratedAt'] = $time; + session_write_close(); + } else { + return $auth; + } #Logger::Debug("Generated new auth $auth at " . $_SESSION['AuthHashGeneratedAt']. " using $authKey" ); - #} else { + #} else { #Logger::Debug("Using cached auth " . $_SESSION['AuthHash'] ." beacuse generatedat:" . $_SESSION['AuthHashGeneratedAt'] . ' < now:'. $time . ' - ' . ZM_AUTH_HASH_TTL . ' * 1800 = '. $mintime); } # end if AuthHash is not cached return $_SESSION['AuthHash']; - } else { - $auth = ''; - } - return $auth; + } # end if using AUTH and AUTH_RELAY + return ''; } function visibleMonitor($mid) { @@ -153,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/functions.php b/web/includes/functions.php index 20d8e2db4..5af957004 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -227,7 +227,7 @@ function getImageStreamHTML( $id, $src, $width, $height, $title='' ) { if ( canStreamIframe() ) { return ' - - - + echo ''; + } # end foreach monitor +?> + + + + + - - + + Path(); + $eventPath = $event->Path(); + $eventRelativePath = $event->Relative_Path(); $files = array(); if ( $dir = opendir($eventPath) ) { while ( ($file = readdir($dir)) !== false ) { @@ -754,29 +756,29 @@ function exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exp if ( $exportDetail ) { $file = 'zmEventDetail.html'; - if ( !($fp = fopen( $eventPath.'/'.$file, 'w' )) ) { - Fatal( "Can't open event detail export file '$file'" ); + if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) { + Fatal("Can't open event detail export file '$file'"); } - fwrite( $fp, exportEventDetail( $event, $exportFrames, $exportImages ) ); - fclose( $fp ); - $exportFileList[$file] = $eventPath."/".$file; + fwrite($fp, exportEventDetail($event, $exportFrames, $exportImages)); + fclose($fp); + $exportFileList[$file] = $event->Id().'/'.$file; } if ( $exportFrames ) { $file = 'zmEventFrames.html'; - if ( !($fp = fopen( $eventPath.'/'.$file, 'w' )) ) { - Fatal( "Can't open event frames export file '$file'" ); + if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) { + Fatal("Can't open event frames export file '$file'"); } - fwrite( $fp, exportEventFrames( $event, $exportDetail, $exportImages ) ); - fclose( $fp ); - $exportFileList[$file] = $eventPath."/".$file; + fwrite($fp, exportEventFrames($event, $exportDetail, $exportImages)); + fclose($fp); + $exportFileList[$file] = $event->Id().'/'.$file; } + if ( $exportImages ) { $filesLeft = array(); $myfilelist = array(); foreach ( $files as $file ) { - if ( preg_match( '/-(?:capture|analyse).jpg$/', $file ) ) { - $exportFileList[$file] = $eventPath."/".$file; - $myfilelist[$file] = $eventPath."/".$file; + if ( preg_match('/-(?:capture|analyse).jpg$/', $file ) ) { + $myfilelist[$file] = $exportFileList[$file] = $event->Id().'/'.$file; } else { $filesLeft[$file] = $file; } @@ -785,109 +787,160 @@ function exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exp // create an image slider if ( !empty($myfilelist) ) { - $file = 'zmEventImages.html'; - if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) Fatal( "Can't open event images export file '$file'" ); - fwrite( $fp, exportEventImages( $event, $exportDetail, $exportFrames, $myfilelist ) ); - fclose( $fp ); - $exportFileList[$file] = $eventPath."/".$file; + $file = $event->Id().'/zmEventImages.html'; + if ( !($fp = fopen($file, 'w')) ) + Fatal("Can't open event images export file '$file'"); + fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)); + fclose($fp); + $exportFileList[$file] = $event->Id().'/'.$file; } } # end if exportImages if ( $exportVideo ) { $filesLeft = array(); foreach ( $files as $file ) { - if ( preg_match( '/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file ) ) { - $exportFileList[$file] = $eventPath.'/'.$file; + if ( preg_match('/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file) ) { + $exportFileList[$file] = $event->Id().'/'.$file; } else { $filesLeft[$file] = $file; } } $files = $filesLeft; - } # end if exportVideo + } # end if exportVideo if ( $exportMisc ) { foreach ( $files as $file ) { - $exportFileList[$file] = $eventPath.'/'.$file; + $exportFileList[$file] = $event->Id().'/'.$file; } $files = array(); } return array_values($exportFileList); -} +} # end exportFileList() -function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat, $exportStructure = false ) { +function exportEvents( + $eids, + $connkey, + $exportDetail, + $exportFrames, + $exportImages, + $exportVideo, + $exportMisc, + $exportFormat, + $exportStructure = false +) { - if ( (!canView('Events')) || empty($eids) ) { + if ( !canView('Events') ) { + Error("You do not have permission to view events."); + return false; + } else if ( empty($eids) ) { + Error("Attempt to export an empty list of events."); return false; } + + # Ensure that we are going to be able to do this. + if ( ! file_exists(ZM_DIR_EXPORTS) ) { + if ( ! mkdir(ZM_DIR_EXPORTS) ) { + Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'"); + } + } + $export_dir = ZM_DIR_EXPORTS.'/zmExport_'.$connkey; + + # Ensure that we are going to be able to do this. + if ( ! file_exists($export_dir) ) { + if ( ! mkdir($export_dir) ) { + Fatal("Can't create exports dir at '$export_dir'"); + } else { + Logger::Debug("Successfully created dir '$export_dir'"); + } + } + if ( !chdir($export_dir) ) + Fatal("Can't chdir to $export_dir"); + $export_root = 'zmExport'; $export_listFile = 'zmFileList.txt'; $exportFileList = array(); $html_eventMaster = ''; - if ( is_array($eids) ) { - foreach ( $eids as $eid ) { - $exportFileList = array_merge( $exportFileList, exportFileList( $eid , $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc ) ); - } - } else { - $eid = $eids; - $exportFileList = exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc ); + if ( !is_array($eids) ) { + $eids = array($eids); } - - // create an master image slider - if ( $exportImages ) { - if ( !is_array($eids) ) { - $eids = array($eids); + foreach ( $eids as $eid ) { + $event = new Event($eid); + $event_dir = $export_dir.'/'.$event->Id(); + if ( !mkdir($event_dir) ) + Error("Can't mkdir $event_dir"); + $event_exportFileList = exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc); + $exportFileList = array_merge($exportFileList,$event_exportFileList); + foreach ( $event_exportFileList as $file ) { + if ( preg_match('/\.html$/', $file ) ) + continue; + Logger::Debug('cp -as '.$event->Path().'/../'.$file.' '.$export_dir.'/'.$file); + exec('cp -as '.$event->Path().'/../'.$file.' '.$export_dir.'/'.$file); } - $monitorPath = ZM_DIR_EVENTS.'/'; - $html_eventMaster = 'zmEventImagesMaster_'.date('Ymd_His'). '.html'; - if ( !($fp = fopen( $monitorPath.'/'.$html_eventMaster, 'w' )) ) Fatal( "Can't open event images export file '$html_eventMaster'" ); - fwrite($fp, exportEventImagesMaster($eids)); - fclose($fp); - $exportFileList[] = $monitorPath.'/'.$html_eventMaster; } - $listFile = ZM_DIR_EXPORTS.'/'.$export_listFile; + // create an master image + if ( $exportImages ) { + if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/jquery.js', $export_dir.'/jquery.js') ) + Error("Failed linking jquery.js"); + //if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/video.js', $export_dir.'/video.js') ) + //Error("Failed linking video.js"); + + $html_eventMaster_file = 'zmEventImagesMaster_'.date('Ymd_His'). '.html'; + $html_eventMaster_path = $export_dir.'/'.$html_eventMaster_file; + + if ( ($fp = fopen($html_eventMaster_path, 'w')) ) { + fwrite($fp, exportEventImagesMaster($eids)); + fclose($fp); + $exportFileList[] = $html_eventMaster_file; + } else { + Error("Can't open event images export file '$html_eventMaster_path'"); + } + } + + $listFile = $export_dir.'/'.$export_listFile; if ( !($fp = fopen($listFile, 'w')) ) { - Fatal( "Can't open event export list file '$listFile'" ); + Fatal("Can't open event export list file '$listFile'"); } foreach ( $exportFileList as $exportFile ) { - fwrite( $fp, "$exportFile\n" ); + $exportFile = 'zmExport'.$connkey.'/'.$exportFile; + fwrite($fp, "$exportFile\n"); } - fclose( $fp ); + fwrite($fp, "$listFile\n"); + fclose($fp); + + chdir(ZM_DIR_EXPORTS); $archive = ''; if ( $exportFormat == 'tar' ) { - $archive = ZM_DIR_EXPORTS.'/'.$export_root.'.tar.gz'; - @unlink( $archive ); - if ( $exportStructure == 'flat' ) { //strip file paths if we choose - $command = "nice -10 tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile)." --xform='s#^.+/##x'"; - } else { - $command = "nice -10 tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile); - } - exec( $command, $output, $status ); - if ( $status ) { - Error( "Command '$command' returned with status $status" ); - if ( $output[0] ) - Error( "First line of output is '".$output[0]."'" ); - return( false ); + $archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.tar.gz'; + @unlink($archive); + $command = 'nice -10 tar --create --gzip --dereference --file='.escapeshellarg($archive).' zmExport_'.$connkey.'/'; + #$command = 'nice -10 tar --create --gzip --file='.escapeshellarg($archive).' --files-from='.escapeshellarg($listFile); + if ( $exportStructure == 'flat' ) { + //strip file paths if we + $command .= " --xform='s#^.+/##x'"; } } elseif ( $exportFormat == 'zip' ) { - $archive = ZM_DIR_EXPORTS.'/'.$export_root.'.zip'; - @unlink( $archive ); - if ($exportStructure == 'flat') { - $command = "cat ".escapeshellarg($listFile)." | nice -10 zip -q -j ".escapeshellarg($archive)." -@"; + $archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.zip'; + @unlink($archive); + if ( $exportStructure == 'flat' ) { + $command = 'nice -10 zip -j '.escapeshellarg($archive).' zmExport_'.$connkey.'/'; + #$command = 'cat '.escapeshellarg($listFile).' | nice -10 zip -q -j '.escapeshellarg($archive).' -@'; } else { - $command = "cat ".escapeshellarg($listFile)." | nice -10 zip -q ".escapeshellarg($archive)." -@"; - } - //cat zmFileList.txt | zip -q zm_export.zip -@ - //-bash: zip: command not found - - exec( $command, $output, $status ); - if ( $status ) { - Error("Command '$command' returned with status $status"); - if ( $output[0] ) - Error("First line of output is '".$output[0]."'"); - return false; + $command = 'nice -10 zip -r '.escapeshellarg($archive).' zmExport_' . $connkey.'/'; + #$command = 'cat '.escapeshellarg($listFile).' | nice -10 zip -q '.escapeshellarg($archive).' -@'; } + } else { + Error("No exportFormat specified."); + return false; + } // if $exportFormat + Logger::Debug("Command is $command"); + exec($command, $output, $status); + if ( $status ) { + Error("Command '$command' returned with status $status"); + if ( isset($output[0]) ) + Error("First line of output is '".$output[0]."'"); + return false; } //clean up temporary files @@ -895,5 +948,5 @@ function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $expo unlink($monitorPath.'/'.$html_eventMaster); } - return '?view=archive%26type='.$exportFormat; + return '?view=archive%26type='.$exportFormat.'%26connkey='.$connkey; } diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index e1808410a..66243daf3 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -317,9 +317,6 @@ if ($reload == 'reload') ob_start();
v'.ZM_VERSION.'', canEdit( 'System' ) ) ?> - -

-
  • trending_up :
  • @@ -359,6 +356,9 @@ if ($reload == 'reload') ob_start(); echo ' ' . ZM_PATH_MAP .': '. getDiskPercent(ZM_PATH_MAP).'%'; ?>
+ +

+ - - - + + 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/bootstrap.js b/web/skins/classic/js/bootstrap.js new file mode 100644 index 000000000..01fbbcbaa --- /dev/null +++ b/web/skins/classic/js/bootstrap.js @@ -0,0 +1,2363 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 2)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.6 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.6 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.6' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.6 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.6' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.6 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.6' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.6 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.6' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.6 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.6' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger($.Event('shown.bs.dropdown', relatedTarget)) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.6 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.6' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.6 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.6' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.6 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.6' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.6 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.6' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.6 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.6' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.6 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.6' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/web/skins/classic/js/bootstrap.js.URL b/web/skins/classic/js/bootstrap.js.URL new file mode 100644 index 000000000..f374e4610 --- /dev/null +++ b/web/skins/classic/js/bootstrap.js.URL @@ -0,0 +1 @@ +https://github.com/twbs/bootstrap/releases/download/v3.3.6/bootstrap-3.3.6-dist.zip 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/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( "